PineForge v0.13.1-379-g9b50973
Deterministic PineScript v6 backtest runtime — C ABI reference
Loading...
Searching...
No Matches
trail_ticks.hpp
Go to the documentation of this file.
1#pragma once
2// TradingView's trailing-exit tick arithmetic: how strategy.exit's
3// trail_points / trail_offset are read as tick counts and how a trailing
4// level is materialized on the tick grid. Every constant here is a
5// TradingView calibration (the pins are quoted below), so it belongs to the
6// source layer; the kernel's own trail is TrailTicks (native_order.hpp),
7// which needs none of it (R5 lane N14: moved verbatim from
8// src/engine_internal.hpp, where no kernel translation unit used it).
9#include <cmath>
10
12
13// ── Trailing-exit tick arithmetic ─────────────────────────────────────
14//
15// TradingView reads strategy.exit's trail_points / trail_offset as TICK
16// COUNTS: trail_points is ceiled WITH a tolerance that swallows floating-
17// point residue — a value computed as close * 0.02 / syminfo.mintick that
18// lands a few ulps past a whole number is that whole number, not the next
19// one — while trail_offset is floored EXACTLY. Pinned with `lab tv` on
20// NYSE:F 15m 2025-04-01..05, short from the 04-02 19:00Z signal (entry
21// 10.11, mintick 0.01; scratchpad/r7/pins/trail-eq-*), each zero-offset
22// exit filling at its activation level:
23// trail_points 14.00001 -> 14 ticks (9.97)
24// trail_points 14.0001 / 14.001 -> 15 ticks (9.96)
25// trail_points 0.14 / syminfo.mintick (= 14.000000000000002) -> 14 (9.97)
26// trail_points 14.0000001 -> 14 (9.97)
27// trail_points 18.2 -> 19 (9.92)
28// trail_offset 0.3 / (syminfo.mintick * 10) (= 2.9999999999999996)
29// -> 2 ticks (trail_points 18: fills 04-03 14:15Z @9.85 = trough 9.83
30// + 2t; a tolerant floor would trail 3t and print 9.86)
31// and BINANCE:BTCUSDT 15m short 2025-08-17 23:30Z @117559.99 with
32// trail_points = 117560 * 0.02 / 0.01 = 235120.00000000003: TV exits
33// 08-18 03:30Z @115208.79 (235120 ticks); std::ceil gave 235121 -> .78.
34// The ceil tolerance is bounded in [1e-5, 1e-4) by the 14.00001 / 14.0001
35// pair; 5e-5 sits in the middle. Round 5's sub-tick pins hold: 0.0006 ->
36// 1 tick, offsets 0 / 0.5 / 0.9 -> 0, 1.4 -> 1.
37constexpr double kTrailPointsCeilEps = 5e-5;
38
39inline double trail_points_to_ticks(double trail_points) {
40 return std::ceil(trail_points - kTrailPointsCeilEps); // NaN stays NaN
41}
42
43inline double trail_offset_to_ticks(double trail_offset) {
44 return std::floor(trail_offset); // exact; NaN stays NaN
45}
46
47// The broker keeps trailing levels ON the tick grid: an activation level
48// entry -/+ N ticks and a trailing level best -/+ K ticks are tick counts,
49// and a bar extreme landing exactly on the level TOUCHES it. Built in
50// doubles, 10.11 - 21 * 0.01 is 9.899999999999999 — one ulp under the 9.9
51// low — and the inclusive segment test reads it as "not reached" (NYSE:F
52// 15m 2025-04-03 13:45Z, bar O 10.165 H 10.18 L 9.9 C 9.9: TV fills the
53// trail there @9.90, `lab tv` trail-eq-S-off0-tp21; the engine gap-filled
54// the next open @9.89). Materialize a level within a millionth of a tick
55// of the grid as the grid point, spelled the way tick_grid_price
56// (engine.hpp) spells it so it compares EQUAL to a feed print bit for bit;
57// a genuinely sub-tick level (best 196.135 - 1 tick = 196.125) stays raw
58// and takes the directional fill snap downstream.
59inline double snap_trail_level_to_tick_grid(double price, double mintick) {
60 if (std::isnan(price) || !(mintick > 0.0)) return price;
61 const double r = price / mintick;
62 const double k = std::floor(r + 0.5);
63 if (std::abs(r - k) > 1e-6) return price;
64 const double inv = 1.0 / mintick;
65 const double inv_int = std::floor(inv + 0.5);
66 if (inv_int > 0.0 && std::abs(inv - inv_int) <= 1e-6 * inv_int) {
67 return k / inv_int;
68 }
69 return k * mintick;
70}
71
72} // namespace pineforge::compat::pine
double trail_offset_to_ticks(double trail_offset)
double trail_points_to_ticks(double trail_points)
double snap_trail_level_to_tick_grid(double price, double mintick)
constexpr double kTrailPointsCeilEps