Files
foxhunt/crates/ml-alpha/cuda/rl_asymmetric_trail_decay.cu
jgrusewski 65d92644d6 fix(rl): asymmetric trail win threshold 100% → 25% of initial_r
The winner-ratchet condition (unrealized > initial_r) never fired
because typical price moves rarely exceed 100% of the initial stop
distance. Win/loss ratio measured at 1.01× (symmetric) despite the
asymmetric decay being active.

Fix: ISV-driven threshold factor (slot 546, default 0.25). Trail
starts ratcheting when profit reaches 25% of initial_r — much more
achievable. At 25%: a $12.50 initial_r only needs $3.12 profit
before the winner-tracking activates.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 20:59:44 +02:00

97 lines
3.9 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// rl_asymmetric_trail_decay.cu — structural asymmetric trail management.
//
// Runs EVERY STEP for each active unit. Automatically adjusts trail
// distance based on unrealized P&L direction:
//
// LOSING (mid moved against entry):
// trail *= loss_decay_rate per step (default 0.995)
// → halves in ~139 steps (~35 seconds)
// "Cut losers fast" — the longer a trade stays underwater,
// the tighter the stop gets, accelerating the exit.
//
// WINNING (unrealized > initial_r):
// trail = max(trail, unrealized_profit × win_trail_factor)
// → trail ratchets up with profit, never below current level
// "Let winners run" — the stop tracks 50% of open profit,
// locking in gains while giving room for continuation.
//
// NEUTRAL (between 0 and initial_r):
// trail unchanged — in the "proving zone" where the trade
// hasn't yet earned the right to a wider stop.
//
// This produces asymmetric P&L without the agent needing to LEARN
// when to tighten vs loosen — it's a structural edge baked into
// the mechanics. The agent's a7/a8 trail actions provide additional
// fine-tuning on top.
//
// Runs BEFORE rl_trail_stop_check so the updated trail distances
// are immediately used for breach detection.
//
// Per `feedback_no_atomicadd`: per-batch per-unit element-wise.
// Per `feedback_cpu_is_read_only`: pure device-side.
#include <stdint.h>
#define MAX_UNITS 4
#define RL_ASYM_LOSS_DECAY_RATE_INDEX 537
#define RL_ASYM_WIN_TRAIL_FACTOR_INDEX 538
#define RL_ASYM_WIN_THRESHOLD_INDEX 546
#define RL_TRAIL_MIN_INDEX 494
extern "C" __global__ void rl_asymmetric_trail_decay(
float* __restrict__ unit_trail_distance, // [B × MAX_UNITS] IN/OUT
const unsigned char* __restrict__ unit_active, // [B × MAX_UNITS]
const float* __restrict__ unit_entry_price, // [B × MAX_UNITS]
const float* __restrict__ unit_initial_r, // [B × MAX_UNITS]
const int* __restrict__ unit_lots, // [B × MAX_UNITS]
const float* __restrict__ bid_px, // [BOOK_LEVELS]
const float* __restrict__ ask_px, // [BOOK_LEVELS]
const float* __restrict__ isv,
int b_size
) {
const int b = blockIdx.x;
const int u = threadIdx.x;
if (b >= b_size || u >= MAX_UNITS) return;
const int idx = b * MAX_UNITS + u;
if (unit_active[idx] == 0) return;
const int lots = unit_lots[idx];
if (lots == 0) return;
const float entry = unit_entry_price[idx];
const float trail = unit_trail_distance[idx];
const float init_r = unit_initial_r[idx];
const float mid = 0.5f * (bid_px[0] + ask_px[0]);
const float loss_decay = isv[RL_ASYM_LOSS_DECAY_RATE_INDEX];
const float win_factor = isv[RL_ASYM_WIN_TRAIL_FACTOR_INDEX];
const float win_threshold = isv[RL_ASYM_WIN_THRESHOLD_INDEX];
const float trail_min = isv[RL_TRAIL_MIN_INDEX];
// Unrealized P&L in price units (positive = profitable).
const float direction = (lots > 0) ? 1.0f : -1.0f;
const float unrealized = direction * (mid - entry);
float new_trail = trail;
if (unrealized < 0.0f) {
// LOSING: tighten every step.
new_trail = trail * loss_decay;
} else if (unrealized > init_r * win_threshold) {
// WINNING beyond initial R: ratchet trail to track profit.
// Trail = max(current_trail, profit × win_factor).
// Never shrinks on winners — only grows.
const float profit_trail = unrealized * win_factor;
if (profit_trail > new_trail) {
new_trail = profit_trail;
}
}
// NEUTRAL (0 to init_r): trail unchanged — proving zone.
// Floor at trail_min.
new_trail = fmaxf(new_trail, trail_min);
unit_trail_distance[idx] = new_trail;
}