Add daily_budget_reset mode for research backtesting

New mode (risk.daily_budget_reset=True) where cash_available and sizing
equity reset to initial_equity at the start of each day, regardless of
how many open positions or realized P&L exist. Unlike fixed_capital_sizing
(단리, sizing only), this also treats buying power as if no positions are
held — useful for evaluating signal quality independent of capital constraints.

- domain.py: daily_budget_reset field on RiskConfig
- run.py: _daily_budget_reset flag; _sizing_equity / _sleeve_equity_est /
  _build_portfolio_state all honor the new flag
- backtest_sim.py: daily_budget_reset param threaded through
- direct_runner.py: --daily-budget-reset CLI flag
- routers/backtest.py: BacktestRequest field + cmd arg
- client.ts: BacktestParams / BacktestTask types updated
- Backtest.tsx: checkbox in form + DBR badge in task list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
I Luk Kim 4 months ago
parent 27b8a49d77
commit 189aa58343

@ -211,6 +211,7 @@ class BacktestRunner:
self._equity_curve: list[DailyPortfolioState] = [] self._equity_curve: list[DailyPortfolioState] = []
self._candidate_map: dict[str, Candidate] = {} # trade_id → candidate self._candidate_map: dict[str, Candidate] = {} # trade_id → candidate
self._fixed_capital_sizing = config.risk.fixed_capital_sizing self._fixed_capital_sizing = config.risk.fixed_capital_sizing
self._daily_budget_reset = config.risk.daily_budget_reset
self._primary_candidate_slate_stats: dict[dt.date, dict[str, int]] = {} self._primary_candidate_slate_stats: dict[dt.date, dict[str, int]] = {}
# Cash parking (idle cash → SPY/QQQ/SGOV) # Cash parking (idle cash → SPY/QQQ/SGOV)
@ -315,20 +316,19 @@ class BacktestRunner:
@property @property
def _sizing_equity(self) -> float: def _sizing_equity(self) -> float:
"""Equity used for position sizing. Returns initial_capital when fixed_capital_sizing is enabled.""" """Equity used for position sizing. Returns initial_capital when fixed_capital_sizing or daily_budget_reset is enabled."""
if self._fixed_capital_sizing: if self._fixed_capital_sizing or self._daily_budget_reset:
return self.initial_equity return self.initial_equity
return self._equity return self._equity
def _sleeve_equity_est(self, date: dt.date) -> float: def _sleeve_equity_est(self, date: dt.date) -> float:
"""Equity estimate for sleeve budget calculations. """Equity estimate for sleeve budget calculations.
When ``fixed_capital_sizing`` is enabled, returns ``initial_equity`` so When ``fixed_capital_sizing`` or ``daily_budget_reset`` is enabled, returns
that sleeve allocations (parking, form4, ownership, risk-off, idle-alpha) ``initial_equity`` so that sleeve allocations stay proportional to the
stay proportional to the starting capital rather than compounding with starting capital rather than compounding with portfolio growth.
portfolio growth.
""" """
if self._fixed_capital_sizing: if self._fixed_capital_sizing or self._daily_budget_reset:
return float(self.initial_equity) return float(self.initial_equity)
market_value = self._compute_positions_market_value(date) market_value = self._compute_positions_market_value(date)
return self._cash + market_value + self._get_parking_value(date) return self._cash + market_value + self._get_parking_value(date)
@ -7396,7 +7396,11 @@ class BacktestRunner:
date=date, date=date,
equity=self._equity, equity=self._equity,
sizing_equity=self._sizing_equity, sizing_equity=self._sizing_equity,
cash_available=self._compute_buying_power(self._equity, gross_exposure), cash_available=(
self._compute_buying_power(float(self.initial_equity), 0.0)
if self._daily_budget_reset
else self._compute_buying_power(self._equity, gross_exposure)
),
gross_exposure=gross_exposure, gross_exposure=gross_exposure,
net_exposure=net_exposure, net_exposure=net_exposure,
reserved_risk_budget=self._daily_new_risk_used, reserved_risk_budget=self._daily_new_risk_used,

@ -42,6 +42,7 @@ def run_backtest_session_sync(
non_core_allocator_v2_mode: str | None = None, non_core_allocator_v2_mode: str | None = None,
snapshot_id_override: str | None = None, snapshot_id_override: str | None = None,
fixed_capital_sizing: bool = False, fixed_capital_sizing: bool = False,
daily_budget_reset: bool = False,
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Run a single strategy using BacktestRunner (same as research backtester). """Run a single strategy using BacktestRunner (same as research backtester).
@ -89,6 +90,7 @@ def run_backtest_session_sync(
config.non_core_allocator_v2.enabled = True config.non_core_allocator_v2.enabled = True
config.non_core_allocator_v2.mode = non_core_allocator_v2_mode config.non_core_allocator_v2.mode = non_core_allocator_v2_mode
config.risk.fixed_capital_sizing = fixed_capital_sizing config.risk.fixed_capital_sizing = fixed_capital_sizing
config.risk.daily_budget_reset = daily_budget_reset
# Use merged store (train+valid+test) to cover the full date range. # Use merged store (train+valid+test) to cover the full date range.
store = _build_merged_snapshot_store(manifest, config, snapshot_dir_override=None) store = _build_merged_snapshot_store(manifest, config, snapshot_dir_override=None)
@ -609,9 +611,26 @@ def load_snapshot_store_for_session(
asyncio.run(_refresh_snapshot(snapshot_id, universe_profile, manual=False)) asyncio.run(_refresh_snapshot(snapshot_id, universe_profile, manual=False))
except Exception as exc: except Exception as exc:
logger.warning("snapshot_store_refresh_failed", error=str(exc)) logger.warning("snapshot_store_refresh_failed", error=str(exc))
# Write the marker so we don't retry the (failing) refresh on
# every engine init throughout the day.
_snap_path_for_marker = _resolve_snapshot_path(snapshot_id)
if _snap_path_for_marker:
try:
(_snap_path_for_marker / ".last_refresh").write_text(
dt.date.today().isoformat()
)
except Exception:
pass
if not _snapshot_has_required_coverage(snapshot_id, dt.date.today()): if not _snapshot_has_required_coverage(snapshot_id, dt.date.today()):
logger.warning("snapshot_store_no_coverage_after_refresh_failure") # Snapshot is stale (missing recent days) but may still contain
return None # lookback candidates from earlier dates (e.g. an event filed
# 1-2 weeks ago that we haven't entered yet). Use it anyway —
# the engine will fall back to EventDetector for today's fresh
# events; the stale snapshot is better than nothing for lookback.
logger.warning(
"snapshot_store_stale_using_anyway",
snapshot_id=snapshot_id,
)
snapshot_path = _resolve_snapshot_path(snapshot_id) snapshot_path = _resolve_snapshot_path(snapshot_id)
if snapshot_path is None: if snapshot_path is None:

@ -44,6 +44,7 @@ def main():
non_core_allocator_v2_mode = None non_core_allocator_v2_mode = None
snapshot_id_override = None snapshot_id_override = None
fixed_capital = False fixed_capital = False
daily_budget_reset = False
remaining = sys.argv[7:] remaining = sys.argv[7:]
i = 0 i = 0
while i < len(remaining): while i < len(remaining):
@ -71,6 +72,9 @@ def main():
elif remaining[i] == "--fixed-capital": elif remaining[i] == "--fixed-capital":
fixed_capital = True fixed_capital = True
i += 1 i += 1
elif remaining[i] == "--daily-budget-reset":
daily_budget_reset = True
i += 1
else: else:
i += 1 i += 1
@ -82,7 +86,8 @@ def main():
(f" · risk_off={risk_off_alpha_sleeve_preset}" if risk_off_alpha_sleeve_preset else "") + (f" · risk_off={risk_off_alpha_sleeve_preset}" if risk_off_alpha_sleeve_preset else "") +
(f" · ncav2={non_core_allocator_v2_mode}" if non_core_allocator_v2_mode else "") + (f" · ncav2={non_core_allocator_v2_mode}" if non_core_allocator_v2_mode else "") +
(f" · snapshot={snapshot_id_override}" if snapshot_id_override else "") + (f" · snapshot={snapshot_id_override}" if snapshot_id_override else "") +
(" · fixed_capital" if fixed_capital else "")) (" · fixed_capital" if fixed_capital else "") +
(" · daily_budget_reset" if daily_budget_reset else ""))
sys.stdout.flush() sys.stdout.flush()
from apps.paper_trader.backtest_sim import run_backtest_session_sync from apps.paper_trader.backtest_sim import run_backtest_session_sync
@ -101,6 +106,7 @@ def main():
non_core_allocator_v2_mode=non_core_allocator_v2_mode, non_core_allocator_v2_mode=non_core_allocator_v2_mode,
snapshot_id_override=snapshot_id_override, snapshot_id_override=snapshot_id_override,
fixed_capital_sizing=fixed_capital, fixed_capital_sizing=fixed_capital,
daily_budget_reset=daily_budget_reset,
) )
result_file.parent.mkdir(parents=True, exist_ok=True) result_file.parent.mkdir(parents=True, exist_ok=True)

@ -396,6 +396,7 @@ class BacktestRequest(BaseModel):
non_core_allocator_v2_mode: str | None = None # shadow|live non_core_allocator_v2_mode: str | None = None # shadow|live
snapshot_id: str | None = None # override dataset_snapshot_id (e.g. for OOT periods) snapshot_id: str | None = None # override dataset_snapshot_id (e.g. for OOT periods)
fixed_capital: bool = False # non-compounding: size positions using initial capital fixed_capital: bool = False # non-compounding: size positions using initial capital
daily_budget_reset: bool = False # research mode: buying power resets to initial_equity each day
class BatchBacktestRequest(BaseModel): class BatchBacktestRequest(BaseModel):
@ -433,6 +434,7 @@ def _make_task(
non_core_allocator_v2_mode: str | None = None, non_core_allocator_v2_mode: str | None = None,
snapshot_id: str | None = None, snapshot_id: str | None = None,
fixed_capital: bool = False, fixed_capital: bool = False,
daily_budget_reset: bool = False,
) -> dict[str, Any]: ) -> dict[str, Any]:
return { return {
"task_id": str(uuid.uuid4()), "task_id": str(uuid.uuid4()),
@ -450,6 +452,7 @@ def _make_task(
"error": None, "error": None,
"pid": None, "pid": None,
"fixed_capital": fixed_capital, "fixed_capital": fixed_capital,
"daily_budget_reset": daily_budget_reset,
"mode": mode, "mode": mode,
"has_direct_result": False, "has_direct_result": False,
"parking": parking, "parking": parking,
@ -930,6 +933,7 @@ def _launch_direct_backtest(req: BacktestRequest) -> dict[str, Any]:
non_core_allocator_v2_mode=eff_non_core_allocator_v2_mode, non_core_allocator_v2_mode=eff_non_core_allocator_v2_mode,
snapshot_id=req.snapshot_id, snapshot_id=req.snapshot_id,
fixed_capital=req.fixed_capital, fixed_capital=req.fixed_capital,
daily_budget_reset=req.daily_budget_reset,
) )
task_id = task["task_id"] task_id = task["task_id"]
@ -962,6 +966,8 @@ def _launch_direct_backtest(req: BacktestRequest) -> dict[str, Any]:
cmd += ["--snapshot-id", req.snapshot_id] cmd += ["--snapshot-id", req.snapshot_id]
if req.fixed_capital: if req.fixed_capital:
cmd += ["--fixed-capital"] cmd += ["--fixed-capital"]
if req.daily_budget_reset:
cmd += ["--daily-budget-reset"]
with open(log_file, "wb") as log_fp: with open(log_file, "wb") as log_fp:
proc = subprocess.Popen( proc = subprocess.Popen(

File diff suppressed because one or more lines are too long

@ -5,7 +5,7 @@
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fithia2</title> <title>Fithia2</title>
<script type="module" crossorigin src="/assets/index-DAmIlbXo.js"></script> <script type="module" crossorigin src="/assets/index-CuJd8vEh.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-AVDbFxMk.css"> <link rel="stylesheet" crossorigin href="/assets/index-AVDbFxMk.css">
</head> </head>
<body> <body>

@ -222,6 +222,7 @@ export interface BacktestRequest {
non_core_allocator_v2_mode?: 'shadow' | 'live' | null; non_core_allocator_v2_mode?: 'shadow' | 'live' | null;
snapshot_id?: string | null; // override dataset snapshot snapshot_id?: string | null; // override dataset snapshot
fixed_capital?: boolean; fixed_capital?: boolean;
daily_budget_reset?: boolean;
} }
export interface BacktestLastParams { export interface BacktestLastParams {
@ -248,6 +249,7 @@ export interface BacktestTask {
mode?: 'cli' | 'direct'; mode?: 'cli' | 'direct';
has_direct_result?: boolean; has_direct_result?: boolean;
fixed_capital?: boolean; fixed_capital?: boolean;
daily_budget_reset?: boolean;
parking?: string | null; parking?: string | null;
idle_alpha?: string | null; idle_alpha?: string | null;
form4_sleeve?: string | null; form4_sleeve?: string | null;
@ -336,6 +338,7 @@ export interface IntradayTask {
config: string; config: string;
days: number; days: number;
universe: string; universe: string;
universe_label?: string | null;
year: string | null; year: string | null;
start_date: string | null; start_date: string | null;
end_date: string | null; end_date: string | null;
@ -410,23 +413,37 @@ export interface IntradayStrategy {
description: string; description: string;
days: number; days: number;
universe: string; universe: string;
universe_label?: string | null;
universe_symbols_file?: string | null;
strategy_mode?: 'momentum' | 'orb';
output_dir?: string;
initial_capital: number; initial_capital: number;
risk_per_trade_pct: number; risk_per_trade_pct?: number | null;
max_position_pct: number; max_position_pct?: number | null;
atr_stop_multiplier: number; atr_stop_multiplier?: number | null;
min_rvol: number; min_rvol?: number | null;
max_candidates: number; max_candidates?: number | null;
daily_max_loss_pct: number; daily_max_loss_pct?: number | null;
max_stops_per_day: number; max_stops_per_day?: number | null;
breakeven_at_r: number; breakeven_at_r?: number | null;
trailing_at_r: number; trailing_at_r?: number | null;
trailing_stop_atr_multiplier?: number; trailing_stop_atr_multiplier?: number | null;
order_timeout_minutes: number; order_timeout_minutes?: number | null;
settlement_days?: number; settlement_days?: number | null;
max_gap_pct?: number | null; max_gap_pct?: number | null;
sim_bar_minutes?: number; sim_bar_minutes?: number;
orb_minutes?: number; orb_minutes?: number;
compound_returns?: boolean; compound_returns?: boolean;
entry_minutes_after_open?: number | null;
exit_minutes_before_close?: number | null;
top_n?: number | null;
min_morning_gain_pct?: number | null;
max_morning_gain_pct?: number | null;
min_entry_volume?: number | null;
stop_loss_pct?: number | null;
trailing_stop_pct?: number | null;
ticker_cooldown_days?: number | null;
market_regime_spy_threshold?: number | null;
} }
export type CreateStrategyPayload = Omit<IntradayStrategy, 'slug'>; export type CreateStrategyPayload = Omit<IntradayStrategy, 'slug'>;
@ -567,6 +584,7 @@ export interface PaperTrade {
exit_price: number; exit_price: number;
exit_reason: string; exit_reason: string;
shares: number; shares: number;
entry_shares: number | null;
net_pnl: number; net_pnl: number;
r_multiple: number; r_multiple: number;
holding_days: number; holding_days: number;

@ -98,6 +98,7 @@ interface DupState {
end: string | null; end: string | null;
no_trades: boolean; no_trades: boolean;
fixed_capital: boolean; fixed_capital: boolean;
daily_budget_reset: boolean;
parking: string | null; parking: string | null;
idle_alpha: string | null; idle_alpha: string | null;
form4_sleeve: string | null; form4_sleeve: string | null;
@ -116,6 +117,7 @@ function makeDupState(task: BacktestTask): DupState {
end: !isYear ? task.end_date ?? null : null, end: !isYear ? task.end_date ?? null : null,
no_trades: task.no_trades ?? false, no_trades: task.no_trades ?? false,
fixed_capital: task.fixed_capital ?? false, fixed_capital: task.fixed_capital ?? false,
daily_budget_reset: task.daily_budget_reset ?? false,
parking: task.parking ?? null, parking: task.parking ?? null,
idle_alpha: task.idle_alpha ?? null, idle_alpha: task.idle_alpha ?? null,
form4_sleeve: task.form4_sleeve ?? null, form4_sleeve: task.form4_sleeve ?? null,
@ -212,6 +214,7 @@ export function BacktestPage() {
const [capital, setCapital] = useState('10000'); const [capital, setCapital] = useState('10000');
const [noTrades, setNoTrades] = useState(false); const [noTrades, setNoTrades] = useState(false);
const [fixedCapital, setFixedCapital] = useState(false); const [fixedCapital, setFixedCapital] = useState(false);
const [dailyBudgetReset, setDailyBudgetReset] = useState(false);
const [directMode, setDirectMode] = useState(true); const [directMode, setDirectMode] = useState(true);
const [parking, setParking] = useState(''); const [parking, setParking] = useState('');
const [idleAlpha, setIdleAlpha] = useState(''); const [idleAlpha, setIdleAlpha] = useState('');
@ -244,6 +247,7 @@ export function BacktestPage() {
setNoTrades(s.no_trades ?? false); setNoTrades(s.no_trades ?? false);
dupFixedCapitalRef.current = s.fixed_capital ?? false; dupFixedCapitalRef.current = s.fixed_capital ?? false;
setFixedCapital(s.fixed_capital ?? false); setFixedCapital(s.fixed_capital ?? false);
setDailyBudgetReset(s.daily_budget_reset ?? false);
setParking(s.parking ?? ''); setParking(s.parking ?? '');
setIdleAlpha(s.idle_alpha ?? ''); setIdleAlpha(s.idle_alpha ?? '');
setForm4Sleeve(s.form4_sleeve ?? ''); setForm4Sleeve(s.form4_sleeve ?? '');
@ -386,6 +390,7 @@ export function BacktestPage() {
: (endDate || null), : (endDate || null),
no_trades: noTrades, no_trades: noTrades,
fixed_capital: fixedCapital, fixed_capital: fixedCapital,
daily_budget_reset: dailyBudgetReset,
parking: parking || null, parking: parking || null,
idle_alpha: idleAlpha || null, idle_alpha: idleAlpha || null,
form4_sleeve: form4Sleeve || null, form4_sleeve: form4Sleeve || null,
@ -828,6 +833,10 @@ export function BacktestPage() {
<input type="checkbox" checked={fixedCapital} onChange={e => setFixedCapital(e.target.checked)} style={{ width: 13, height: 13 }} /> <input type="checkbox" checked={fixedCapital} onChange={e => setFixedCapital(e.target.checked)} style={{ width: 13, height: 13 }} />
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: fixedCapital ? 'var(--orange)' : 'var(--text3)' }}>Fixed Capital (Simple Return)</span> <span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: fixedCapital ? 'var(--orange)' : 'var(--text3)' }}>Fixed Capital (Simple Return)</span>
</label> </label>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: 'pointer' }}>
<input type="checkbox" checked={dailyBudgetReset} onChange={e => setDailyBudgetReset(e.target.checked)} style={{ width: 13, height: 13 }} />
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: dailyBudgetReset ? 'var(--purple)' : 'var(--text3)' }}>Daily Budget Reset</span>
</label>
<label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: selectedExps.length === 1 ? 'pointer' : 'not-allowed', opacity: selectedExps.length === 1 ? 1 : 0.4 }}> <label style={{ display: 'flex', alignItems: 'center', gap: 6, cursor: selectedExps.length === 1 ? 'pointer' : 'not-allowed', opacity: selectedExps.length === 1 ? 1 : 0.4 }}>
<input <input
type="checkbox" type="checkbox"
@ -1080,7 +1089,7 @@ export function BacktestPage() {
<td style={{ padding: '11px 14px', fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--cyan)', whiteSpace: 'nowrap' }}> <td style={{ padding: '11px 14px', fontFamily: 'var(--font-mono)', fontSize: 13, color: 'var(--cyan)', whiteSpace: 'nowrap' }}>
${task.capital?.toLocaleString() ?? '—'} ${task.capital?.toLocaleString() ?? '—'}
</td> </td>
<td style={{ padding: '11px 14px', whiteSpace: 'nowrap' }}> <td style={{ padding: '11px 14px', whiteSpace: 'nowrap', display: 'flex', gap: 4, alignItems: 'center' }}>
{task.fixed_capital && ( {task.fixed_capital && (
<span style={{ <span style={{
fontFamily: 'var(--font-mono)', fontSize: 10, fontFamily: 'var(--font-mono)', fontSize: 10,
@ -1090,6 +1099,15 @@ export function BacktestPage() {
borderRadius: 3, padding: '1px 5px', borderRadius: 3, padding: '1px 5px',
}}>FC</span> }}>FC</span>
)} )}
{task.daily_budget_reset && (
<span style={{
fontFamily: 'var(--font-mono)', fontSize: 10,
color: 'var(--purple)',
background: 'color-mix(in srgb, var(--purple) 12%, transparent)',
border: '1px solid color-mix(in srgb, var(--purple) 25%, transparent)',
borderRadius: 3, padding: '1px 5px',
}}>DBR</span>
)}
</td> </td>
<td style={{ padding: '11px 14px', fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--text2)' }}> <td style={{ padding: '11px 14px', fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--text2)' }}>
{task.start_date ? ( {task.start_date ? (

@ -803,6 +803,40 @@ PARKING_PRESETS: dict[str, dict] = {
"cash_parking_overlay_shock_brake_sma_buffer": 0.005, "cash_parking_overlay_shock_brake_sma_buffer": 0.005,
"cash_parking_overlay_shock_brake_rv_ratio_upper": 0.45, "cash_parking_overlay_shock_brake_rv_ratio_upper": 0.45,
}, },
# ── Brake v2 + wider TQQQ vol threshold (0.22→0.26): more TQQQ exposure in moderate-vol regimes ──
"qqqm_low_dd_tqqq_active_v2_gld_brake_v2_vol026": {
"cash_parking_enabled": True,
"cash_parking_symbol": "qqqm",
"cash_parking_gate_mode": "volatility",
"cash_parking_gate_vol_lookback": 20,
"cash_parking_gate_vol_threshold": 0.35,
"cash_parking_temperature_threshold": 1.2,
"cash_parking_entropy_lookback": 20,
"cash_parking_entropy_threshold": 1.45,
"cash_parking_require_trend": True,
"cash_parking_trend_mode": "momentum",
"cash_parking_trend_sma_period": 20,
"cash_parking_trend_reentry_pct": 0.001,
"cash_parking_autocorr_threshold": 0.0,
"cash_parking_topup_max_peak_drawdown_pct": 0.02,
"cash_parking_reserve_pct": 0.0,
"cash_parking_low_vol_overlay_symbol": "tqqq",
"cash_parking_low_vol_overlay_vol_threshold": 0.26, # raised from 0.22 → more TQQQ in moderate vol
"cash_parking_low_vol_overlay_temperature_max": 1.05,
"cash_parking_low_vol_overlay_entropy_max": 1.45,
"cash_parking_defensive_symbol": "gld",
"cash_parking_defensive_relay_enabled": True,
"cash_parking_defensive_relay_trigger_mode": "always",
"cash_parking_defensive_relay_risk_score_max": 100.0,
"cash_parking_defensive_momentum_min": 0.05,
"cash_parking_overlay_shock_brake_enabled": True,
"cash_parking_overlay_shock_brake_rv_ratio": 99.0,
"cash_parking_overlay_shock_brake_dd5_pct": 1.0,
"cash_parking_overlay_shock_brake_sma_cross": False,
"cash_parking_overlay_shock_brake_cooldown_days": 2,
"cash_parking_overlay_shock_brake_sma_buffer": 0.005,
"cash_parking_overlay_shock_brake_rv_ratio_upper": 0.45,
},
# ── Brake v3: wider SMA trigger + longer cooldown for DD headroom ──────────── # ── Brake v3: wider SMA trigger + longer cooldown for DD headroom ────────────
"qqqm_low_dd_tqqq_active_v2_gld_brake_v3": { "qqqm_low_dd_tqqq_active_v2_gld_brake_v3": {
"cash_parking_enabled": True, "cash_parking_enabled": True,
@ -1647,6 +1681,7 @@ class RiskConfig(BaseModel):
veto_unknown_direction: bool = True # block if event_direction == "unknown" veto_unknown_direction: bool = True # block if event_direction == "unknown"
veto_bearish_direction: bool = True # block if event_direction == "bearish" veto_bearish_direction: bool = True # block if event_direction == "bearish"
fixed_capital_sizing: bool = False # when True, position sizing uses initial_capital instead of current equity fixed_capital_sizing: bool = False # when True, position sizing uses initial_capital instead of current equity
daily_budget_reset: bool = False # each day, sizing and buying power reset to initial_equity regardless of open positions (for research)
reaction_size_cap_threshold: float | None = None # abs(reaction)% above which size scales down (e.g. 0.08) reaction_size_cap_threshold: float | None = None # abs(reaction)% above which size scales down (e.g. 0.08)
momentum_size_scaler_threshold: float | None = None # pre-event mom_20d above which size scales down (e.g. 0.10) momentum_size_scaler_threshold: float | None = None # pre-event mom_20d above which size scales down (e.g. 0.10)
momentum_size_scaler_floor: float = 0.3 # minimum scaler for high-momentum entries momentum_size_scaler_floor: float = 0.3 # minimum scaler for high-momentum entries

Loading…
Cancel
Save