/* * force_exit_timer.cl * ───────────────────────────────────────────────────────────────────────────── * GPU Kernel: Time-Based Trade Force Exit * * Responsibility: * Evaluate three time-based exit conditions for each open position simultaneously. * This is the kill switch that overrides price-based reasoning when time expires. * * Rule 1 — Natural time cap: * elapsed_nat >= max_nat_s → exit * Hard stop. The cycle window has closed. No edge remains. * * Rule 2 — Price linger: * elapsed_nat >= LINGER_CHECK_S AND |price_move_pct| < LINGER_TOL * → exit (LINGER) * Price returned to entry zone without moving. Thesis invalidated quietly. * * Rule 3 — Synthetic budget exhausted: * elapsed_syn >= max_nat_s * vol_ratio * SYN_BUDGET_MUL → exit * Regime-adjusted version of Rule 1. Catches low-vol "stall" scenarios * where natural time passes slowly but market is information-dense. * * Inputs (global read): * elapsed_nat [M] float — natural seconds since entry per trade * elapsed_syn [M] float — synthetic seconds since entry per trade * price_move_pct [M] float — (current - entry) / entry signed * max_nat_s [M] float — per-trade natural time cap (seconds) * vol_ratio [1] float — current rolling_vol / baseline (from volclock) * is_open [M] int — 1 if trade is open, 0 if already closed * * Outputs (global write): * exit_flag [M] int — 1 = force close this trade * exit_reason [M] int — reason code: * 0 = HOLD * 1 = NATURAL_TIME_EXPIRED * 2 = PRICE_LINGER * 3 = SYNTHETIC_BUDGET * * Parameters (compile-time): * LINGER_CHECK_S — seconds before linger rule activates (default 30s) * LINGER_TOL — price move threshold to consider "lingering" (default 0.05%) * SYN_BUDGET_MUL — synthetic budget multiplier (default 1.0) * * Design notes: * • M = max concurrent open positions (e.g. 64 for single-instrument strategies). * • Fully parallel across M positions. No cross-item dependency. * • Rule priority: 1 > 2 > 3. First triggered rule wins. * • vol_ratio is a scalar broadcast (one value for current market regime). * ───────────────────────────────────────────────────────────────────────────── */ #ifndef LINGER_CHECK_S #define LINGER_CHECK_S 30.0f #endif #ifndef LINGER_TOL #define LINGER_TOL 0.0005f /* 5 basis points */ #endif #ifndef SYN_BUDGET_MUL #define SYN_BUDGET_MUL 1.0f #endif /* Exit reason codes */ #define REASON_HOLD 0 #define REASON_NATURAL_TIME_EXPIRED 1 #define REASON_PRICE_LINGER 2 #define REASON_SYNTHETIC_BUDGET 3 __kernel void force_exit_timer( __global const float* elapsed_nat, /* [M] natural seconds in trade */ __global const float* elapsed_syn, /* [M] synthetic seconds in trade */ __global const float* price_move_pct, /* [M] signed % move from entry */ __global const float* max_nat_s, /* [M] per-trade natural time cap */ __global const float* vol_ratio, /* [1] current vol / baseline scalar */ __global const int* is_open, /* [M] 1 = position open */ __global int* exit_flag, /* [M] OUT: 1 = close this trade */ __global int* exit_reason /* [M] OUT: reason code */ ) { int gid = get_global_id(0); /* Skip closed trades — write hold and return */ if (!is_open[gid]) { exit_flag[gid] = 0; exit_reason[gid] = REASON_HOLD; return; } float enat = elapsed_nat[gid]; float esyn = elapsed_syn[gid]; float move = fabs(price_move_pct[gid]); float mnat = max_nat_s[gid]; float vr = vol_ratio[0]; /* ── Rule 1: Natural time cap ──────────────────────────────────────── */ if (enat >= mnat) { exit_flag[gid] = 1; exit_reason[gid] = REASON_NATURAL_TIME_EXPIRED; return; } /* ── Rule 2: Price linger ───────────────────────────────────────────── */ if (enat >= LINGER_CHECK_S && move < LINGER_TOL) { exit_flag[gid] = 1; exit_reason[gid] = REASON_PRICE_LINGER; return; } /* ── Rule 3: Synthetic budget ───────────────────────────────────────── */ float syn_budget = mnat * vr * SYN_BUDGET_MUL; if (esyn >= syn_budget) { exit_flag[gid] = 1; exit_reason[gid] = REASON_SYNTHETIC_BUDGET; return; } /* ── No exit condition triggered ────────────────────────────────────── */ exit_flag[gid] = 0; exit_reason[gid] = REASON_HOLD; }