new best
This commit is contained in:
@@ -16,9 +16,13 @@ use crate::config::{
|
||||
DRAWDOWN_TIER2_PCT, DRAWDOWN_TIER2_BARS,
|
||||
DRAWDOWN_TIER3_PCT, DRAWDOWN_TIER3_BARS,
|
||||
DRAWDOWN_TIER3_REQUIRE_BULL,
|
||||
HOURLY_DRAWDOWN_TIER1_PCT, HOURLY_DRAWDOWN_TIER1_BARS,
|
||||
HOURLY_DRAWDOWN_TIER2_PCT, HOURLY_DRAWDOWN_TIER2_BARS,
|
||||
HOURLY_DRAWDOWN_TIER3_PCT, HOURLY_DRAWDOWN_TIER3_BARS,
|
||||
EQUITY_CURVE_SMA_PERIOD,
|
||||
REGIME_SPY_SYMBOL, REGIME_EMA_SHORT, REGIME_EMA_LONG,
|
||||
REGIME_CAUTION_SIZE_FACTOR, REGIME_CAUTION_THRESHOLD_BUMP,
|
||||
HOURLY_REGIME_CAUTION_SIZE_FACTOR, HOURLY_REGIME_CAUTION_THRESHOLD_BUMP,
|
||||
};
|
||||
use crate::indicators::{calculate_all_indicators, calculate_ema, determine_market_regime, generate_signal};
|
||||
use crate::strategy::Strategy;
|
||||
@@ -112,23 +116,41 @@ impl Backtester {
|
||||
///
|
||||
/// On resume, the peak is reset to the current portfolio value to prevent
|
||||
/// cascading re-triggers from the same drawdown event.
|
||||
/// Get the drawdown tier thresholds for the current timeframe.
|
||||
fn drawdown_tiers(&self) -> (f64, usize, f64, usize, f64, usize) {
|
||||
if self.timeframe == Timeframe::Hourly {
|
||||
(
|
||||
HOURLY_DRAWDOWN_TIER1_PCT, HOURLY_DRAWDOWN_TIER1_BARS,
|
||||
HOURLY_DRAWDOWN_TIER2_PCT, HOURLY_DRAWDOWN_TIER2_BARS,
|
||||
HOURLY_DRAWDOWN_TIER3_PCT, HOURLY_DRAWDOWN_TIER3_BARS,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
DRAWDOWN_TIER1_PCT, DRAWDOWN_TIER1_BARS,
|
||||
DRAWDOWN_TIER2_PCT, DRAWDOWN_TIER2_BARS,
|
||||
DRAWDOWN_TIER3_PCT, DRAWDOWN_TIER3_BARS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_drawdown_state(&mut self, portfolio_value: f64) {
|
||||
if portfolio_value > self.peak_portfolio_value {
|
||||
self.peak_portfolio_value = portfolio_value;
|
||||
}
|
||||
|
||||
let drawdown_pct = (self.peak_portfolio_value - portfolio_value) / self.peak_portfolio_value;
|
||||
let (t1_pct, t1_bars, t2_pct, t2_bars, t3_pct, t3_bars) = self.drawdown_tiers();
|
||||
|
||||
// Trigger halt at the lowest tier that matches (if not already halted)
|
||||
if !self.drawdown_halt && drawdown_pct >= DRAWDOWN_TIER1_PCT {
|
||||
if !self.drawdown_halt && drawdown_pct >= t1_pct {
|
||||
// Determine severity tier
|
||||
let (halt_bars, tier_name) = if drawdown_pct >= DRAWDOWN_TIER3_PCT {
|
||||
let (halt_bars, tier_name) = if drawdown_pct >= t3_pct {
|
||||
self.drawdown_requires_bull = DRAWDOWN_TIER3_REQUIRE_BULL;
|
||||
(DRAWDOWN_TIER3_BARS, "TIER 3 (SEVERE)")
|
||||
} else if drawdown_pct >= DRAWDOWN_TIER2_PCT {
|
||||
(DRAWDOWN_TIER2_BARS, "TIER 2")
|
||||
(t3_bars, "TIER 3 (SEVERE)")
|
||||
} else if drawdown_pct >= t2_pct {
|
||||
(t2_bars, "TIER 2")
|
||||
} else {
|
||||
(DRAWDOWN_TIER1_BARS, "TIER 1")
|
||||
(t1_bars, "TIER 1")
|
||||
};
|
||||
|
||||
tracing::warn!(
|
||||
@@ -145,14 +167,14 @@ impl Backtester {
|
||||
|
||||
// Upgrade severity if drawdown deepens while already halted
|
||||
if self.drawdown_halt && drawdown_pct > self.drawdown_halt_severity {
|
||||
if drawdown_pct >= DRAWDOWN_TIER3_PCT && self.drawdown_halt_severity < DRAWDOWN_TIER3_PCT {
|
||||
if drawdown_pct >= t3_pct && self.drawdown_halt_severity < t3_pct {
|
||||
self.drawdown_requires_bull = DRAWDOWN_TIER3_REQUIRE_BULL;
|
||||
self.drawdown_halt_start = Some(self.current_bar); // Reset timer for deeper tier
|
||||
tracing::warn!(
|
||||
"Drawdown deepened to {:.2}% — UPGRADED to TIER 3. Requires BULL regime.",
|
||||
drawdown_pct * 100.0
|
||||
);
|
||||
} else if drawdown_pct >= DRAWDOWN_TIER2_PCT && self.drawdown_halt_severity < DRAWDOWN_TIER2_PCT {
|
||||
} else if drawdown_pct >= t2_pct && self.drawdown_halt_severity < t2_pct {
|
||||
self.drawdown_halt_start = Some(self.current_bar);
|
||||
tracing::warn!(
|
||||
"Drawdown deepened to {:.2}% — upgraded to TIER 2.",
|
||||
@@ -165,12 +187,12 @@ impl Backtester {
|
||||
// Auto-resume after time-based cooldown
|
||||
if self.drawdown_halt {
|
||||
if let Some(halt_start) = self.drawdown_halt_start {
|
||||
let required_bars = if self.drawdown_halt_severity >= DRAWDOWN_TIER3_PCT {
|
||||
DRAWDOWN_TIER3_BARS
|
||||
} else if self.drawdown_halt_severity >= DRAWDOWN_TIER2_PCT {
|
||||
DRAWDOWN_TIER2_BARS
|
||||
let required_bars = if self.drawdown_halt_severity >= t3_pct {
|
||||
t3_bars
|
||||
} else if self.drawdown_halt_severity >= t2_pct {
|
||||
t2_bars
|
||||
} else {
|
||||
DRAWDOWN_TIER1_BARS
|
||||
t1_bars
|
||||
};
|
||||
|
||||
let time_served = self.current_bar >= halt_start + required_bars;
|
||||
@@ -693,9 +715,16 @@ impl Backtester {
|
||||
self.current_regime = regime;
|
||||
|
||||
// Regime-based sizing factor and threshold adjustment
|
||||
// Use timeframe-specific parameters: hourly needs defensiveness, daily needs aggression
|
||||
let regime_size_factor = match regime {
|
||||
MarketRegime::Bull => 1.0,
|
||||
MarketRegime::Caution => REGIME_CAUTION_SIZE_FACTOR,
|
||||
MarketRegime::Caution => {
|
||||
if self.timeframe == Timeframe::Hourly {
|
||||
HOURLY_REGIME_CAUTION_SIZE_FACTOR
|
||||
} else {
|
||||
REGIME_CAUTION_SIZE_FACTOR
|
||||
}
|
||||
},
|
||||
MarketRegime::Bear => 0.0, // No new longs
|
||||
};
|
||||
|
||||
@@ -775,8 +804,15 @@ impl Backtester {
|
||||
// In Bear regime, skip the entire buy phase (no new longs).
|
||||
if regime.allows_new_longs() {
|
||||
// In Caution regime, raise the buy threshold to require stronger signals
|
||||
// Use timeframe-specific parameters: hourly needs high bump, daily needs low bump
|
||||
let buy_threshold_bump = match regime {
|
||||
MarketRegime::Caution => REGIME_CAUTION_THRESHOLD_BUMP,
|
||||
MarketRegime::Caution => {
|
||||
if self.timeframe == Timeframe::Hourly {
|
||||
HOURLY_REGIME_CAUTION_THRESHOLD_BUMP
|
||||
} else {
|
||||
REGIME_CAUTION_THRESHOLD_BUMP
|
||||
}
|
||||
},
|
||||
_ => 0.0,
|
||||
};
|
||||
|
||||
@@ -1523,15 +1559,13 @@ impl Backtester {
|
||||
" Max Per Sector: {:>15}",
|
||||
MAX_SECTOR_POSITIONS
|
||||
);
|
||||
println!(
|
||||
" Drawdown Halt: {:>13.0}%/{:.0}%/{:.0}% ({}/{}/{} bars)",
|
||||
DRAWDOWN_TIER1_PCT * 100.0,
|
||||
DRAWDOWN_TIER2_PCT * 100.0,
|
||||
DRAWDOWN_TIER3_PCT * 100.0,
|
||||
DRAWDOWN_TIER1_BARS,
|
||||
DRAWDOWN_TIER2_BARS,
|
||||
DRAWDOWN_TIER3_BARS,
|
||||
);
|
||||
{
|
||||
let (t1p, t1b, t2p, t2b, t3p, t3b) = self.drawdown_tiers();
|
||||
println!(
|
||||
" Drawdown Halt: {:>13.0}%/{:.0}%/{:.0}% ({}/{}/{} bars)",
|
||||
t1p * 100.0, t2p * 100.0, t3p * 100.0, t1b, t2b, t3b,
|
||||
);
|
||||
}
|
||||
println!(
|
||||
" Market Regime Filter: {:>15}",
|
||||
format!("SPY EMA-{}/EMA-{}", REGIME_EMA_SHORT, REGIME_EMA_LONG)
|
||||
|
||||
Reference in New Issue
Block a user