new best
This commit is contained in:
@@ -16,9 +16,13 @@ use crate::config::{
|
|||||||
DRAWDOWN_TIER2_PCT, DRAWDOWN_TIER2_BARS,
|
DRAWDOWN_TIER2_PCT, DRAWDOWN_TIER2_BARS,
|
||||||
DRAWDOWN_TIER3_PCT, DRAWDOWN_TIER3_BARS,
|
DRAWDOWN_TIER3_PCT, DRAWDOWN_TIER3_BARS,
|
||||||
DRAWDOWN_TIER3_REQUIRE_BULL,
|
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,
|
EQUITY_CURVE_SMA_PERIOD,
|
||||||
REGIME_SPY_SYMBOL, REGIME_EMA_SHORT, REGIME_EMA_LONG,
|
REGIME_SPY_SYMBOL, REGIME_EMA_SHORT, REGIME_EMA_LONG,
|
||||||
REGIME_CAUTION_SIZE_FACTOR, REGIME_CAUTION_THRESHOLD_BUMP,
|
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::indicators::{calculate_all_indicators, calculate_ema, determine_market_regime, generate_signal};
|
||||||
use crate::strategy::Strategy;
|
use crate::strategy::Strategy;
|
||||||
@@ -112,23 +116,41 @@ impl Backtester {
|
|||||||
///
|
///
|
||||||
/// On resume, the peak is reset to the current portfolio value to prevent
|
/// On resume, the peak is reset to the current portfolio value to prevent
|
||||||
/// cascading re-triggers from the same drawdown event.
|
/// 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) {
|
fn update_drawdown_state(&mut self, portfolio_value: f64) {
|
||||||
if portfolio_value > self.peak_portfolio_value {
|
if portfolio_value > self.peak_portfolio_value {
|
||||||
self.peak_portfolio_value = portfolio_value;
|
self.peak_portfolio_value = portfolio_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
let drawdown_pct = (self.peak_portfolio_value - portfolio_value) / self.peak_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)
|
// 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
|
// 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;
|
self.drawdown_requires_bull = DRAWDOWN_TIER3_REQUIRE_BULL;
|
||||||
(DRAWDOWN_TIER3_BARS, "TIER 3 (SEVERE)")
|
(t3_bars, "TIER 3 (SEVERE)")
|
||||||
} else if drawdown_pct >= DRAWDOWN_TIER2_PCT {
|
} else if drawdown_pct >= t2_pct {
|
||||||
(DRAWDOWN_TIER2_BARS, "TIER 2")
|
(t2_bars, "TIER 2")
|
||||||
} else {
|
} else {
|
||||||
(DRAWDOWN_TIER1_BARS, "TIER 1")
|
(t1_bars, "TIER 1")
|
||||||
};
|
};
|
||||||
|
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
@@ -145,14 +167,14 @@ impl Backtester {
|
|||||||
|
|
||||||
// Upgrade severity if drawdown deepens while already halted
|
// Upgrade severity if drawdown deepens while already halted
|
||||||
if self.drawdown_halt && drawdown_pct > self.drawdown_halt_severity {
|
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_requires_bull = DRAWDOWN_TIER3_REQUIRE_BULL;
|
||||||
self.drawdown_halt_start = Some(self.current_bar); // Reset timer for deeper tier
|
self.drawdown_halt_start = Some(self.current_bar); // Reset timer for deeper tier
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Drawdown deepened to {:.2}% — UPGRADED to TIER 3. Requires BULL regime.",
|
"Drawdown deepened to {:.2}% — UPGRADED to TIER 3. Requires BULL regime.",
|
||||||
drawdown_pct * 100.0
|
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);
|
self.drawdown_halt_start = Some(self.current_bar);
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Drawdown deepened to {:.2}% — upgraded to TIER 2.",
|
"Drawdown deepened to {:.2}% — upgraded to TIER 2.",
|
||||||
@@ -165,12 +187,12 @@ impl Backtester {
|
|||||||
// Auto-resume after time-based cooldown
|
// Auto-resume after time-based cooldown
|
||||||
if self.drawdown_halt {
|
if self.drawdown_halt {
|
||||||
if let Some(halt_start) = self.drawdown_halt_start {
|
if let Some(halt_start) = self.drawdown_halt_start {
|
||||||
let required_bars = if self.drawdown_halt_severity >= DRAWDOWN_TIER3_PCT {
|
let required_bars = if self.drawdown_halt_severity >= t3_pct {
|
||||||
DRAWDOWN_TIER3_BARS
|
t3_bars
|
||||||
} else if self.drawdown_halt_severity >= DRAWDOWN_TIER2_PCT {
|
} else if self.drawdown_halt_severity >= t2_pct {
|
||||||
DRAWDOWN_TIER2_BARS
|
t2_bars
|
||||||
} else {
|
} else {
|
||||||
DRAWDOWN_TIER1_BARS
|
t1_bars
|
||||||
};
|
};
|
||||||
|
|
||||||
let time_served = self.current_bar >= halt_start + required_bars;
|
let time_served = self.current_bar >= halt_start + required_bars;
|
||||||
@@ -693,9 +715,16 @@ impl Backtester {
|
|||||||
self.current_regime = regime;
|
self.current_regime = regime;
|
||||||
|
|
||||||
// Regime-based sizing factor and threshold adjustment
|
// Regime-based sizing factor and threshold adjustment
|
||||||
|
// Use timeframe-specific parameters: hourly needs defensiveness, daily needs aggression
|
||||||
let regime_size_factor = match regime {
|
let regime_size_factor = match regime {
|
||||||
MarketRegime::Bull => 1.0,
|
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
|
MarketRegime::Bear => 0.0, // No new longs
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -775,8 +804,15 @@ impl Backtester {
|
|||||||
// In Bear regime, skip the entire buy phase (no new longs).
|
// In Bear regime, skip the entire buy phase (no new longs).
|
||||||
if regime.allows_new_longs() {
|
if regime.allows_new_longs() {
|
||||||
// In Caution regime, raise the buy threshold to require stronger signals
|
// 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 {
|
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,
|
_ => 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1523,15 +1559,13 @@ impl Backtester {
|
|||||||
" Max Per Sector: {:>15}",
|
" Max Per Sector: {:>15}",
|
||||||
MAX_SECTOR_POSITIONS
|
MAX_SECTOR_POSITIONS
|
||||||
);
|
);
|
||||||
println!(
|
{
|
||||||
" Drawdown Halt: {:>13.0}%/{:.0}%/{:.0}% ({}/{}/{} bars)",
|
let (t1p, t1b, t2p, t2b, t3p, t3b) = self.drawdown_tiers();
|
||||||
DRAWDOWN_TIER1_PCT * 100.0,
|
println!(
|
||||||
DRAWDOWN_TIER2_PCT * 100.0,
|
" Drawdown Halt: {:>13.0}%/{:.0}%/{:.0}% ({}/{}/{} bars)",
|
||||||
DRAWDOWN_TIER3_PCT * 100.0,
|
t1p * 100.0, t2p * 100.0, t3p * 100.0, t1b, t2b, t3b,
|
||||||
DRAWDOWN_TIER1_BARS,
|
);
|
||||||
DRAWDOWN_TIER2_BARS,
|
}
|
||||||
DRAWDOWN_TIER3_BARS,
|
|
||||||
);
|
|
||||||
println!(
|
println!(
|
||||||
" Market Regime Filter: {:>15}",
|
" Market Regime Filter: {:>15}",
|
||||||
format!("SPY EMA-{}/EMA-{}", REGIME_EMA_SHORT, REGIME_EMA_LONG)
|
format!("SPY EMA-{}/EMA-{}", REGIME_EMA_SHORT, REGIME_EMA_LONG)
|
||||||
|
|||||||
@@ -73,17 +73,11 @@ pub const STOP_LOSS_PCT: f64 = 0.025;
|
|||||||
pub const MAX_LOSS_PCT: f64 = 0.08; // Gap protection only — ATR stop handles normal exits
|
pub const MAX_LOSS_PCT: f64 = 0.08; // Gap protection only — ATR stop handles normal exits
|
||||||
pub const TRAILING_STOP_ACTIVATION: f64 = 0.04; // Activate earlier to protect profits
|
pub const TRAILING_STOP_ACTIVATION: f64 = 0.04; // Activate earlier to protect profits
|
||||||
pub const TRAILING_STOP_DISTANCE: f64 = 0.05; // Wider trail to let winners run
|
pub const TRAILING_STOP_DISTANCE: f64 = 0.05; // Wider trail to let winners run
|
||||||
// ATR-based risk management (DAILY timeframe - wider stops for longer-term holds)
|
// ATR-based risk management
|
||||||
pub const RISK_PER_TRADE: f64 = 0.015; // 1.5% risk per trade (8 positions * 1.5% = 12% worst-case)
|
pub const RISK_PER_TRADE: f64 = 0.015; // 1.5% risk per trade (8 positions * 1.5% = 12% worst-case)
|
||||||
pub const ATR_STOP_MULTIPLIER: f64 = 3.5; // Wide stops reduce false stop-outs on daily
|
pub const ATR_STOP_MULTIPLIER: f64 = 3.5; // Wide stops reduce false stop-outs (the #1 loss source)
|
||||||
pub const ATR_TRAIL_MULTIPLIER: f64 = 3.0; // Wide trail so winners run longer
|
pub const ATR_TRAIL_MULTIPLIER: f64 = 3.0; // Wide trail so winners run longer
|
||||||
pub const ATR_TRAIL_ACTIVATION_MULTIPLIER: f64 = 2.0; // Don't activate trail too early
|
pub const ATR_TRAIL_ACTIVATION_MULTIPLIER: f64 = 2.0; // Don't activate trail too early
|
||||||
|
|
||||||
// ATR-based risk management (HOURLY timeframe - much tighter to prevent 70-90% losses)
|
|
||||||
// Hourly intraday noise requires stops 40-50% tighter than daily to avoid catastrophic drawdowns
|
|
||||||
pub const HOURLY_ATR_STOP_MULTIPLIER: f64 = 1.8; // Tight stops prevent -$9k NVDA disasters
|
|
||||||
pub const HOURLY_ATR_TRAIL_MULTIPLIER: f64 = 1.5; // Tight trail locks in hourly gains quickly
|
|
||||||
pub const HOURLY_ATR_TRAIL_ACTIVATION_MULTIPLIER: f64 = 1.2; // Activate trail early on hourly
|
|
||||||
// Portfolio-level controls
|
// Portfolio-level controls
|
||||||
pub const MAX_CONCURRENT_POSITIONS: usize = 8; // Fewer positions = higher conviction per trade
|
pub const MAX_CONCURRENT_POSITIONS: usize = 8; // Fewer positions = higher conviction per trade
|
||||||
pub const MAX_SECTOR_POSITIONS: usize = 2;
|
pub const MAX_SECTOR_POSITIONS: usize = 2;
|
||||||
@@ -108,11 +102,19 @@ pub const RAMPUP_PERIOD_BARS: usize = 15; // Faster ramp-up
|
|||||||
pub const REGIME_SPY_SYMBOL: &str = "SPY";
|
pub const REGIME_SPY_SYMBOL: &str = "SPY";
|
||||||
pub const REGIME_EMA_SHORT: usize = 50; // Fast regime EMA
|
pub const REGIME_EMA_SHORT: usize = 50; // Fast regime EMA
|
||||||
pub const REGIME_EMA_LONG: usize = 200; // Slow regime EMA (the "golden cross" line)
|
pub const REGIME_EMA_LONG: usize = 200; // Slow regime EMA (the "golden cross" line)
|
||||||
/// In Caution regime, multiply position size by this factor.
|
/// In Caution regime, multiply position size by this factor (DAILY bars).
|
||||||
/// Reduced from 0.5 to 0.25: the 2022 bear showed Caution still bleeds at 50% size.
|
/// Daily benefits from being more aggressive in Caution (60% size) to capture bull markets.
|
||||||
pub const REGIME_CAUTION_SIZE_FACTOR: f64 = 0.25;
|
pub const REGIME_CAUTION_SIZE_FACTOR: f64 = 0.6;
|
||||||
/// In Caution regime, add this to buy thresholds (require near-StrongBuy signals).
|
/// In Caution regime, add this to buy thresholds (DAILY bars).
|
||||||
pub const REGIME_CAUTION_THRESHOLD_BUMP: f64 = 3.0;
|
/// Daily needs lower bump (1.0) to participate in bull rallies.
|
||||||
|
pub const REGIME_CAUTION_THRESHOLD_BUMP: f64 = 1.0;
|
||||||
|
|
||||||
|
/// In Caution regime, multiply position size by this factor (HOURLY bars).
|
||||||
|
/// Hourly needs to be very defensive (25% size) due to intraday noise.
|
||||||
|
pub const HOURLY_REGIME_CAUTION_SIZE_FACTOR: f64 = 0.25;
|
||||||
|
/// In Caution regime, add this to buy thresholds (HOURLY bars).
|
||||||
|
/// Hourly needs high bump (3.0) to avoid whipsaws.
|
||||||
|
pub const HOURLY_REGIME_CAUTION_THRESHOLD_BUMP: f64 = 3.0;
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// Scaled Drawdown Circuit Breaker
|
// Scaled Drawdown Circuit Breaker
|
||||||
@@ -120,16 +122,25 @@ pub const REGIME_CAUTION_THRESHOLD_BUMP: f64 = 3.0;
|
|||||||
// The old fixed 10-bar cooldown is inadequate for real bear markets.
|
// The old fixed 10-bar cooldown is inadequate for real bear markets.
|
||||||
// Scale the halt duration with severity so that deeper drawdowns force
|
// Scale the halt duration with severity so that deeper drawdowns force
|
||||||
// longer cooling periods. At 25%+ DD, also require bull regime to resume.
|
// longer cooling periods. At 25%+ DD, also require bull regime to resume.
|
||||||
pub const DRAWDOWN_TIER1_PCT: f64 = 0.12; // 12% → 15 bars (catch earlier)
|
// Daily drawdown tiers: relaxed to avoid halting on normal 10-15% bull pullbacks
|
||||||
pub const DRAWDOWN_TIER1_BARS: usize = 15;
|
pub const DRAWDOWN_TIER1_PCT: f64 = 0.18; // 18% → 10 bars
|
||||||
pub const DRAWDOWN_TIER2_PCT: f64 = 0.18; // 18% → 40 bars
|
pub const DRAWDOWN_TIER1_BARS: usize = 10;
|
||||||
pub const DRAWDOWN_TIER2_BARS: usize = 40;
|
pub const DRAWDOWN_TIER2_PCT: f64 = 0.25; // 25% → 30 bars
|
||||||
pub const DRAWDOWN_TIER3_PCT: f64 = 0.25; // 25%+ → 60 bars + require bull regime
|
pub const DRAWDOWN_TIER2_BARS: usize = 30;
|
||||||
pub const DRAWDOWN_TIER3_BARS: usize = 60;
|
pub const DRAWDOWN_TIER3_PCT: f64 = 0.35; // 35%+ → 50 bars + require bull
|
||||||
/// If true, after a Tier 3 drawdown (>=25%), require bull market regime
|
pub const DRAWDOWN_TIER3_BARS: usize = 50;
|
||||||
/// before resuming new entries even after the bar cooldown expires.
|
/// If true, after a Tier 3 drawdown, require bull market regime to resume.
|
||||||
pub const DRAWDOWN_TIER3_REQUIRE_BULL: bool = true;
|
pub const DRAWDOWN_TIER3_REQUIRE_BULL: bool = true;
|
||||||
|
|
||||||
|
// Hourly drawdown tiers: tighter because hourly has more whipsaw exposure
|
||||||
|
// and the bot needs to cut losses faster to preserve capital in bear periods.
|
||||||
|
pub const HOURLY_DRAWDOWN_TIER1_PCT: f64 = 0.12; // 12% → 15 bars
|
||||||
|
pub const HOURLY_DRAWDOWN_TIER1_BARS: usize = 15;
|
||||||
|
pub const HOURLY_DRAWDOWN_TIER2_PCT: f64 = 0.18; // 18% → 40 bars
|
||||||
|
pub const HOURLY_DRAWDOWN_TIER2_BARS: usize = 40;
|
||||||
|
pub const HOURLY_DRAWDOWN_TIER3_PCT: f64 = 0.25; // 25%+ → 60 bars + require bull
|
||||||
|
pub const HOURLY_DRAWDOWN_TIER3_BARS: usize = 60;
|
||||||
|
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
// Trailing Equity Curve Stop
|
// Trailing Equity Curve Stop
|
||||||
// ═══════════════════════════════════════════════════════════════════════
|
// ═══════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use crate::config::{
|
|||||||
ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER, MAX_LOSS_PCT, MAX_POSITION_SIZE,
|
ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER, MAX_LOSS_PCT, MAX_POSITION_SIZE,
|
||||||
MIN_ATR_PCT, RISK_PER_TRADE, STOP_LOSS_PCT, TIME_EXIT_BARS,
|
MIN_ATR_PCT, RISK_PER_TRADE, STOP_LOSS_PCT, TIME_EXIT_BARS,
|
||||||
TRAILING_STOP_ACTIVATION, TRAILING_STOP_DISTANCE,
|
TRAILING_STOP_ACTIVATION, TRAILING_STOP_DISTANCE,
|
||||||
HOURLY_ATR_STOP_MULTIPLIER, HOURLY_ATR_TRAIL_MULTIPLIER, HOURLY_ATR_TRAIL_ACTIVATION_MULTIPLIER,
|
|
||||||
};
|
};
|
||||||
use crate::types::{Signal, TradeSignal};
|
use crate::types::{Signal, TradeSignal};
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ pub struct Strategy {
|
|||||||
pub high_water_marks: HashMap<String, f64>,
|
pub high_water_marks: HashMap<String, f64>,
|
||||||
pub entry_atrs: HashMap<String, f64>,
|
pub entry_atrs: HashMap<String, f64>,
|
||||||
pub entry_prices: HashMap<String, f64>,
|
pub entry_prices: HashMap<String, f64>,
|
||||||
pub timeframe: Timeframe,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Strategy {
|
impl Strategy {
|
||||||
@@ -25,7 +23,6 @@ impl Strategy {
|
|||||||
high_water_marks: HashMap::new(),
|
high_water_marks: HashMap::new(),
|
||||||
entry_atrs: HashMap::new(),
|
entry_atrs: HashMap::new(),
|
||||||
entry_prices: HashMap::new(),
|
entry_prices: HashMap::new(),
|
||||||
timeframe,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,14 +105,8 @@ impl Strategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. ATR-based initial stop-loss (primary risk control)
|
// 2. ATR-based initial stop-loss (primary risk control)
|
||||||
// Use tighter stops for hourly to prevent catastrophic 70-90% losses
|
|
||||||
if entry_atr > 0.0 {
|
if entry_atr > 0.0 {
|
||||||
let stop_multiplier = if self.timeframe == Timeframe::Hourly {
|
let atr_stop_price = entry_price - ATR_STOP_MULTIPLIER * entry_atr;
|
||||||
HOURLY_ATR_STOP_MULTIPLIER
|
|
||||||
} else {
|
|
||||||
ATR_STOP_MULTIPLIER
|
|
||||||
};
|
|
||||||
let atr_stop_price = entry_price - stop_multiplier * entry_atr;
|
|
||||||
if current_price <= atr_stop_price {
|
if current_price <= atr_stop_price {
|
||||||
return Some(Signal::StrongSell);
|
return Some(Signal::StrongSell);
|
||||||
}
|
}
|
||||||
@@ -125,15 +116,10 @@ impl Strategy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 4. ATR-based trailing stop (profit protection)
|
// 4. ATR-based trailing stop (profit protection)
|
||||||
// Hourly uses much tighter trail to lock in gains quickly
|
// Activates earlier than before (1.5x ATR gain) so profits are locked in.
|
||||||
let (trail_activation_mult, trail_mult) = if self.timeframe == Timeframe::Hourly {
|
// Distance is wider (2.5x ATR from HWM) so normal retracements don't trigger it.
|
||||||
(HOURLY_ATR_TRAIL_ACTIVATION_MULTIPLIER, HOURLY_ATR_TRAIL_MULTIPLIER)
|
|
||||||
} else {
|
|
||||||
(ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER)
|
|
||||||
};
|
|
||||||
|
|
||||||
let activation_gain = if entry_atr > 0.0 {
|
let activation_gain = if entry_atr > 0.0 {
|
||||||
(trail_activation_mult * entry_atr) / entry_price
|
(ATR_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price
|
||||||
} else {
|
} else {
|
||||||
TRAILING_STOP_ACTIVATION
|
TRAILING_STOP_ACTIVATION
|
||||||
};
|
};
|
||||||
@@ -141,7 +127,7 @@ impl Strategy {
|
|||||||
if pnl_pct >= activation_gain {
|
if pnl_pct >= activation_gain {
|
||||||
if let Some(&high_water) = self.high_water_marks.get(symbol) {
|
if let Some(&high_water) = self.high_water_marks.get(symbol) {
|
||||||
let trail_distance = if entry_atr > 0.0 {
|
let trail_distance = if entry_atr > 0.0 {
|
||||||
trail_mult * entry_atr
|
ATR_TRAIL_MULTIPLIER * entry_atr
|
||||||
} else {
|
} else {
|
||||||
high_water * TRAILING_STOP_DISTANCE
|
high_water * TRAILING_STOP_DISTANCE
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user