Appearance
Personalize your experience
Curvature Turn
FreeEnters when a quadratic fit turns: the earliest sign of a reversal
Live engine preview
The QuaScript engine's own output on the chart the terminal uses. Change the settings or edit the code and press Run to recompute it.
About
Enters on the earliest measurable sign of a turn: the curvature of a quadratic least-squares fit changing sign. When a downtrend starts to decelerate, the quadratic term turns positive before the slope does; the strategy buys once the slope also turns up, and sells the mirror case. It exits when the curvature flips back. Entry: curvature positive for `confirm` bars and the fitted slope crossing above zero (mirror for shorts). Exit: curvature flips against the trade, ATR stop, or a target of `reward` R. Risk: 1 % per trade. Method: ordinary least squares on 1, x, x squared.
Settings
Fit window: 80
Curvature confirmation (bars): 3
Stop (ATR): 2.5
Target (R): 3
Measured backtests
Run by the backtest engine on public candles, 2025-11-01 to 2026-11-01, with the script's own commission and slippage, on BTCUSDT 1h (Binance) and ETHUSDT 1h (Binance). The figures per market are in the Measured backtest panel below. Past results do not predict future returns.
Backtest it yourself
Open it on the backtesting page to run it on any symbol, interval and date range, then use Monte Carlo, optimisation, walk-forward and robustness tests.
Strategy Rules
Entry Conditions
- ▸ curvature positive for `confirm` bars and the fitted slope crossing above zero (mirror for shorts)
Exit Conditions
- ▸ curvature flips against the trade, ATR stop, or a target of `reward` R
Measured backtest
BTCUSDT 1h (Binance), 2025-11-01 to 2026-11-01 · backtest engine, script costs included
-6.5%
Return
-0.38
Sharpe
-13.2%
Max DD
35.8%
Win Rate
134
Trades
| Market | Trades | Return | Sharpe | Max DD | Win rate | Profit factor |
|---|---|---|---|---|---|---|
| BTCUSDT 1h | 134 | -6.5% | -0.38 | -13.2% | 35.8% | 0.90 |
| ETHUSDT 1h | 130 | 15.1% | 1.03 | -9.5% | 43.1% | 1.19 |
Past results on historical data do not predict future returns.
Source Code
Edit in IDE| 1 | strategy "Curvature Turn" { |
| 2 | overlay = true |
| 3 | capital = 100000 |
| 4 | commission = 0.05 |
| 5 | slippage = 1 |
| 6 | sizing = risk_percent |
| 7 | max_leverage = 1 |
| 8 | size = 1 |
| 9 | pyramiding = 1 |
| 10 | } |
| 11 | /// Enters on the earliest measurable sign of a turn: the curvature of a quadratic least-squares fit |
| 12 | /// changing sign. When a downtrend starts to decelerate, the quadratic term turns positive before |
| 13 | /// the slope does; the strategy buys once the slope also turns up, and sells the mirror case. It |
| 14 | /// exits when the curvature flips back. |
| 15 | /// Entry: curvature positive for `confirm` bars and the fitted slope crossing above zero (mirror for |
| 16 | /// shorts). Exit: curvature flips against the trade, ATR stop, or a target of `reward` R. |
| 17 | /// Risk: 1 % per trade. Method: ordinary least squares on 1, x, x squared. |
| 18 | use "viz/premium" as pr |
| 19 | len = param.int(80, title = "Fit window", min = 20, max = 500, group = "Fit") |
| 20 | confirm = param.int(3, title = "Curvature confirmation (bars)", min = 1, max = 20, group = "Entry") |
| 21 | stop_k = param.float(2.5, title = "Stop (ATR)", min = 0.5, max = 8, step = 0.1, group = "Risk") |
| 22 | reward = param.float(3, title = "Target (R)", min = 0.5, max = 10, step = 0.1, group = "Risk") |
| 23 | s0 = 0.0 |
| 24 | s1 = 0.0 |
| 25 | s2 = 0.0 |
| 26 | s3 = 0.0 |
| 27 | s4 = 0.0 |
| 28 | t0 = 0.0 |
| 29 | t1 = 0.0 |
| 30 | t2 = 0.0 |
| 31 | for i in 0..len { |
| 32 | y = close[i] |
| 33 | if not math.is_na(y) { |
| 34 | x = -i |
| 35 | s0 := s0 + 1 |
| 36 | s1 := s1 + x |
| 37 | s2 := s2 + x * x |
| 38 | s3 := s3 + x * x * x |
| 39 | s4 := s4 + x * x * x * x |
| 40 | t0 := t0 + y |
| 41 | t1 := t1 + x * y |
| 42 | t2 := t2 + x * x * y |
| 43 | } |
| 44 | } |
| 45 | det = s0 * (s2 * s4 - s3 * s3) - s1 * (s1 * s4 - s3 * s2) + s2 * (s1 * s3 - s2 * s2) |
| 46 | c0 = det != 0 ? (t0 * (s2 * s4 - s3 * s3) - s1 * (t1 * s4 - s3 * t2) + s2 * (t1 * s3 - s2 * t2)) / det : na |
| 47 | c1 = det != 0 ? (s0 * (t1 * s4 - t2 * s3) - t0 * (s1 * s4 - s3 * s2) + s2 * (s1 * t2 - t1 * s2)) / det : na |
| 48 | c2 = det != 0 ? (s0 * (s2 * t2 - s3 * t1) - s1 * (s1 * t2 - s2 * t1) + t0 * (s1 * s3 - s2 * s2)) / det : na |
| 49 | a = ta.atr(20) |
| 50 | pos_c = ta.bars_since(c2 <= 0) |
| 51 | neg_c = ta.bars_since(c2 >= 0) |
| 52 | long_sig = pos_c >= confirm and c1 > 0 and c1[1] <= 0 |
| 53 | short_sig = neg_c >= confirm and c1 < 0 and c1[1] >= 0 |
| 54 | flat = position.size == 0 |
| 55 | if flat and long_sig { |
| 56 | order.market("L", side.long) |
| 57 | exit.bracket("L exit", entry = "L", stop = close - stop_k * a, target = close + stop_k * a * reward) |
| 58 | } |
| 59 | if flat and short_sig { |
| 60 | order.market("S", side.short) |
| 61 | exit.bracket("S exit", entry = "S", stop = close + stop_k * a, target = close - stop_k * a * reward) |
| 62 | } |
| 63 | if (position.side > 0 and c2 < 0) or (position.side < 0 and c2 > 0) { exit.close_all() } |
| 64 | risk.max_drawdown(25) |
| 65 | render.line(c0, "Fit end", color = c2 > 0 ? color.sky.300 : color.amber.300, width = 2) |
| 66 | render.mark(long_sig, shape = shape.triangle_up, at = anchor.below_bar, color = color.sky.300, text = "Turn", title = "Turn up") |
| 67 | render.mark(short_sig, shape = shape.triangle_down, at = anchor.above_bar, color = color.amber.300, text = "Turn", title = "Turn down") |
| 68 | persist t = draw.table.create(anchor.top_right, 4, 2) |
| 69 | if bar.is_last { |
| 70 | pr.heading(t, 2, "Curvature Turn", "Strategy · quadratic fit turns") |
| 71 | pr.row(t, 2, "Slope", str.format("{:+.3f}", c1), c1 > 0 ? pr.BULL : pr.BEAR) |
| 72 | pr.row(t, 3, "Curvature", c2 > 0 ? "Positive" : "Negative", c2 > 0 ? color.sky.300 : color.amber.300) |
| 73 | } |
| 74 |
Reviews
No reviews yet. Be the first to review this listing.