feat(rl): adaptive gate threshold controller from trade frequency

New rl_gate_threshold_controller.cu watches dones EMA and adjusts
confidence + FRD gate thresholds via Schulman bounded step:
- dones_ema < target×0.5 → relax thresholds (×0.95)
- dones_ema > target×2.0 → tighten thresholds (÷0.95)

ISV-driven: target (0.02), conf bounds (0.001-0.50), FRD bounds
(0.05-0.50), adjust rate (0.95). Runs per step after warmup.

Also lowers default thresholds: conf 0.10→0.01, FRD 0.35→0.15.
Post-warmup, the controller adapts these based on actual trade
flow instead of relying on static defaults.

ISV slots 525-531. RL_SLOTS_END → 532.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-05-25 10:26:10 +02:00
parent 0fdc06df83
commit 88abf8185c
4 changed files with 143 additions and 5 deletions

View File

@@ -86,6 +86,7 @@ const KERNELS: &[&str] = &[
"rl_trade_context_update", // P1: per-batch trade-arc features (time_in_trade, unrealized_R, pos_mag, entry_dist)
"rl_multires_features_update", // P0: per-batch multi-resolution streaming features (3 horizons × 4 features)
"rl_encoder_context_broadcast", // P2: broadcast per-batch context (16 dims) into encoder input [B,K,56] at cols 40-55
"rl_gate_threshold_controller", // adaptive gate thresholds from trade frequency (dones EMA)
];
// Cache bust v31 — five new reduce / derive kernels populate the input

View File

@@ -0,0 +1,80 @@
// rl_gate_threshold_controller.cu — adaptive gate threshold controller.
//
// Watches trade frequency (dones EMA) and adjusts confidence + FRD
// gate thresholds to maintain a target trade rate. When trades dry up
// (dones_ema < target), thresholds DECREASE (more permissive). When
// trades are abundant (dones_ema > target), thresholds INCREASE
// (more selective). Schulman bounded discrete step pattern.
//
// Runs once per step, single thread. Reads/writes ISV.
// Per `feedback_no_atomicadd`, `feedback_cpu_is_read_only`.
#include <stdint.h>
#define RL_CONF_GATE_THRESHOLD_INDEX 512
#define RL_FRD_GATE_THR_LONG_INDEX 516
#define RL_FRD_GATE_THR_SHORT_INDEX 517
#define RL_GATE_WARMUP_STEPS_INDEX 524
#define RL_GATE_DONES_EMA_INDEX 525
#define RL_GATE_DONES_TARGET_INDEX 526
#define RL_GATE_CONF_MIN_INDEX 527
#define RL_GATE_CONF_MAX_INDEX 528
#define RL_GATE_FRD_MIN_INDEX 529
#define RL_GATE_FRD_MAX_INDEX 530
#define RL_GATE_ADJUST_RATE_INDEX 531
extern "C" __global__ void rl_gate_threshold_controller(
float* __restrict__ isv,
const float* __restrict__ dones, // [B]
int b_size,
int current_step
) {
if (threadIdx.x != 0 || blockIdx.x != 0) return;
const int warmup = (int)isv[RL_GATE_WARMUP_STEPS_INDEX];
if (current_step < warmup) return;
// Update dones EMA (count of done events this step / b_size).
float done_count = 0.0f;
for (int b = 0; b < b_size; ++b) {
if (dones[b] > 0.5f) done_count += 1.0f;
}
const float done_rate = done_count / (float)b_size;
const float alpha = 0.01f;
const float prev_ema = isv[RL_GATE_DONES_EMA_INDEX];
const float dones_ema = (prev_ema == 0.0f && done_rate > 0.0f)
? done_rate
: (1.0f - alpha) * prev_ema + alpha * done_rate;
isv[RL_GATE_DONES_EMA_INDEX] = dones_ema;
const float target = isv[RL_GATE_DONES_TARGET_INDEX];
const float adjust = isv[RL_GATE_ADJUST_RATE_INDEX];
// Schulman bounded step: if dones_ema < target, DECREASE thresholds
// (more permissive); if > target, INCREASE (more selective).
float conf_thr = isv[RL_CONF_GATE_THRESHOLD_INDEX];
float frd_long = isv[RL_FRD_GATE_THR_LONG_INDEX];
float frd_short = isv[RL_FRD_GATE_THR_SHORT_INDEX];
if (dones_ema < target * 0.5f) {
// Trades well below target — relax gates.
conf_thr *= adjust;
frd_long *= adjust;
frd_short *= adjust;
} else if (dones_ema > target * 2.0f) {
// Trades well above target — tighten gates.
conf_thr /= adjust;
frd_long /= adjust;
frd_short /= adjust;
}
// Bilateral clamp.
const float conf_min = isv[RL_GATE_CONF_MIN_INDEX];
const float conf_max = isv[RL_GATE_CONF_MAX_INDEX];
const float frd_min = isv[RL_GATE_FRD_MIN_INDEX];
const float frd_max = isv[RL_GATE_FRD_MAX_INDEX];
isv[RL_CONF_GATE_THRESHOLD_INDEX] = fmaxf(conf_min, fminf(conf_thr, conf_max));
isv[RL_FRD_GATE_THR_LONG_INDEX] = fmaxf(frd_min, fminf(frd_long, frd_max));
isv[RL_FRD_GATE_THR_SHORT_INDEX] = fmaxf(frd_min, fminf(frd_short, frd_max));
}

View File

@@ -893,6 +893,22 @@ pub const RL_MULTIRES_HORIZON_3_INDEX: usize = 523;
/// Default 10000.
pub const RL_GATE_WARMUP_STEPS_INDEX: usize = 524;
/// Gate threshold controller: dones EMA (streaming trade frequency).
pub const RL_GATE_DONES_EMA_INDEX: usize = 525;
/// Gate threshold controller: target dones rate per step. Default 0.02
/// (1 trade per 50 steps at b=16 — ~2% of steps have a done event).
pub const RL_GATE_DONES_TARGET_INDEX: usize = 526;
/// Gate threshold controller: confidence gate minimum.
pub const RL_GATE_CONF_MIN_INDEX: usize = 527;
/// Gate threshold controller: confidence gate maximum.
pub const RL_GATE_CONF_MAX_INDEX: usize = 528;
/// Gate threshold controller: FRD gate minimum.
pub const RL_GATE_FRD_MIN_INDEX: usize = 529;
/// Gate threshold controller: FRD gate maximum.
pub const RL_GATE_FRD_MAX_INDEX: usize = 530;
/// Gate threshold controller: Schulman step rate. Default 0.95.
pub const RL_GATE_ADJUST_RATE_INDEX: usize = 531;
/// Last RL-allocated slot index (exclusive). The integrated trainer
/// extends `ISV_TOTAL_DIM` to at least this value at trainer init time.
pub const RL_SLOTS_END: usize = 525;
pub const RL_SLOTS_END: usize = 532;

View File

@@ -200,6 +200,8 @@ const RL_FRD_GATE_CUBIN: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/rl_frd_gate.cubin"));
const RL_RECENT_OUTCOME_UPDATE_CUBIN: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/rl_recent_outcome_update.cubin"));
const RL_GATE_THRESHOLD_CONTROLLER_CUBIN: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/rl_gate_threshold_controller.cubin"));
const RL_TRADE_CONTEXT_UPDATE_CUBIN: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/rl_trade_context_update.cubin"));
const RL_MULTIRES_FEATURES_UPDATE_CUBIN: &[u8] =
@@ -459,6 +461,8 @@ pub struct IntegratedTrainer {
rl_frd_gate_fn: CudaFunction,
_rl_recent_outcome_update_module: Arc<CudaModule>,
rl_recent_outcome_update_fn: CudaFunction,
_rl_gate_threshold_controller_module: Arc<CudaModule>,
rl_gate_threshold_controller_fn: CudaFunction,
_rl_trade_context_update_module: Arc<CudaModule>,
rl_trade_context_update_fn: CudaFunction,
_rl_multires_features_update_module: Arc<CudaModule>,
@@ -969,6 +973,12 @@ impl IntegratedTrainer {
let rl_recent_outcome_update_fn = rl_recent_outcome_update_module
.load_function("rl_recent_outcome_update")
.context("load rl_recent_outcome_update")?;
let rl_gate_threshold_controller_module = ctx
.load_cubin(RL_GATE_THRESHOLD_CONTROLLER_CUBIN.to_vec())
.context("load rl_gate_threshold_controller cubin")?;
let rl_gate_threshold_controller_fn = rl_gate_threshold_controller_module
.load_function("rl_gate_threshold_controller")
.context("load rl_gate_threshold_controller")?;
let rl_trade_context_update_module = ctx
.load_cubin(RL_TRADE_CONTEXT_UPDATE_CUBIN.to_vec())
.context("load rl_trade_context_update cubin")?;
@@ -1391,6 +1401,8 @@ impl IntegratedTrainer {
rl_frd_gate_fn,
_rl_recent_outcome_update_module: rl_recent_outcome_update_module,
rl_recent_outcome_update_fn,
_rl_gate_threshold_controller_module: rl_gate_threshold_controller_module,
rl_gate_threshold_controller_fn,
_rl_trade_context_update_module: rl_trade_context_update_module,
rl_trade_context_update_fn,
_rl_multires_features_update_module: rl_multires_features_update_module,
@@ -1535,7 +1547,7 @@ impl IntegratedTrainer {
// (slot, value) pair — pure device write, no HtoD per
// `feedback_no_htod_htoh_only_mapped_pinned`.
{
let isv_constants: [(usize, f32); 62] = [
let isv_constants: [(usize, f32); 68] = [
// Static seeds for the adaptive reward-clamp controller —
// these are the initial values that
// `rl_reward_clamp_controller` will replace once it
@@ -1596,14 +1608,20 @@ impl IntegratedTrainer {
(crate::rl::isv_slots::RL_ANTIMARTINGALE_KAPPA_INDEX, 1.0),
(crate::rl::isv_slots::RL_ANTIMARTINGALE_MIN_INDEX, 0.5),
(crate::rl::isv_slots::RL_ANTIMARTINGALE_MAX_INDEX, 2.0),
(crate::rl::isv_slots::RL_CONF_GATE_THRESHOLD_INDEX, 0.10),
(crate::rl::isv_slots::RL_CONF_GATE_THRESHOLD_INDEX, 0.01),
(crate::rl::isv_slots::RL_CONF_GATE_LAMBDA_INDEX, 1.0),
(crate::rl::isv_slots::RL_CONF_GATE_SIGMA_NORM_INDEX, 1.0),
(crate::rl::isv_slots::RL_FRD_GATE_THR_LONG_INDEX, 0.35),
(crate::rl::isv_slots::RL_FRD_GATE_THR_SHORT_INDEX, 0.35),
(crate::rl::isv_slots::RL_FRD_GATE_THR_LONG_INDEX, 0.15),
(crate::rl::isv_slots::RL_FRD_GATE_THR_SHORT_INDEX, 0.15),
(crate::rl::isv_slots::RL_PI_SAMPLE_P_MIN_INDEX, 0.015),
(crate::rl::isv_slots::RL_OUTCOME_ALPHA_INDEX, 0.1),
(crate::rl::isv_slots::RL_GATE_WARMUP_STEPS_INDEX, 10000.0),
(crate::rl::isv_slots::RL_GATE_DONES_TARGET_INDEX, 0.02),
(crate::rl::isv_slots::RL_GATE_CONF_MIN_INDEX, 0.001),
(crate::rl::isv_slots::RL_GATE_CONF_MAX_INDEX, 0.50),
(crate::rl::isv_slots::RL_GATE_FRD_MIN_INDEX, 0.05),
(crate::rl::isv_slots::RL_GATE_FRD_MAX_INDEX, 0.50),
(crate::rl::isv_slots::RL_GATE_ADJUST_RATE_INDEX, 0.95),
(crate::rl::isv_slots::RL_MULTIRES_HORIZON_1_INDEX, 1.0),
(crate::rl::isv_slots::RL_MULTIRES_HORIZON_2_INDEX, 10.0),
(crate::rl::isv_slots::RL_MULTIRES_HORIZON_3_INDEX, 600.0),
@@ -4263,6 +4281,29 @@ impl IntegratedTrainer {
self.launch_rl_controllers_per_step()
.context("launch_rl_controllers_per_step")?;
// Gate threshold controller — adapts conf + FRD thresholds from
// trade frequency. Runs after other controllers, before reward scale.
{
let cfg = LaunchConfig {
grid_dim: (1, 1, 1),
block_dim: (1, 1, 1),
shared_mem_bytes: 0,
};
let b_size_i = b_size as i32;
let current_step_i = (self.step_counter as i32).max(0);
let mut launch = self.stream.launch_builder(&self.rl_gate_threshold_controller_fn);
launch
.arg(&self.isv_d)
.arg(&self.dones_d)
.arg(&b_size_i)
.arg(&current_step_i);
unsafe {
launch
.launch(cfg)
.context("rl_gate_threshold_controller launch")?;
}
}
// Apply the reward scale on device (in place on rewards_d) +
// adaptive clamp + per-step pre-clamp diag + per-step
// positive-tail max for the adaptive clamp controller.