just a checkpoint
This commit is contained in:
@@ -26,7 +26,13 @@ impl Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
/// Volatility-adjusted position sizing using ATR.
|
||||
/// Volatility-adjusted position sizing using ATR (Kelly-inspired).
|
||||
///
|
||||
/// Position size = (Risk per trade / ATR stop distance) * confidence.
|
||||
/// The confidence scaling now has a much wider range (0.4 to 1.0) so that
|
||||
/// weak Buy signals (confidence ~0.4) get 40% size while StrongBuy signals
|
||||
/// (confidence ~1.0) get full size. This is a fractional Kelly approach:
|
||||
/// bet more when conviction is higher, less when marginal.
|
||||
pub fn calculate_position_size(
|
||||
&self,
|
||||
price: f64,
|
||||
@@ -42,8 +48,9 @@ impl Strategy {
|
||||
let atr_stop_pct = signal.atr_pct * ATR_STOP_MULTIPLIER;
|
||||
let risk_amount = portfolio_value * RISK_PER_TRADE;
|
||||
let vol_adjusted = risk_amount / atr_stop_pct;
|
||||
// Scale by confidence
|
||||
let confidence_scale = 0.7 + 0.3 * signal.confidence;
|
||||
// Wide confidence scaling: 0.4x for weak signals, 1.0x for strongest.
|
||||
// Old code used 0.7 + 0.3*conf which barely differentiated.
|
||||
let confidence_scale = 0.4 + 0.6 * signal.confidence;
|
||||
let sized = vol_adjusted * confidence_scale;
|
||||
sized.min(portfolio_value * MAX_POSITION_SIZE)
|
||||
} else {
|
||||
@@ -51,12 +58,26 @@ impl Strategy {
|
||||
};
|
||||
|
||||
let position_value = position_value.min(available_cash);
|
||||
// Use fractional shares — Alpaca supports them for paper trading.
|
||||
// Use fractional shares -- Alpaca supports them for paper trading.
|
||||
// Truncate to 4 decimal places to avoid floating point dust.
|
||||
((position_value / price) * 10000.0).floor() / 10000.0
|
||||
}
|
||||
|
||||
/// Check if stop-loss, trailing stop, or time exit should trigger.
|
||||
///
|
||||
/// Exit priority (checked in order):
|
||||
/// 1. Hard max-loss cap (MAX_LOSS_PCT) -- absolute worst-case, gap protection
|
||||
/// 2. ATR-based stop-loss (ATR_STOP_MULTIPLIER * ATR) -- primary risk control
|
||||
/// 3. Fixed % stop-loss (STOP_LOSS_PCT) -- fallback when ATR unavailable
|
||||
/// 4. ATR trailing stop (ATR_TRAIL_MULTIPLIER * ATR from HWM) -- profit protection
|
||||
/// 5. Time-based exit (TIME_EXIT_BARS) -- only if position is LOSING
|
||||
///
|
||||
/// Key design decisions:
|
||||
/// - Trailing stop activates early (1.5x ATR) but has wide distance (2.5x ATR)
|
||||
/// so winners have room to breathe but profits are protected.
|
||||
/// - Time exit ONLY sells losers. Winners at the time limit are doing fine;
|
||||
/// the trailing stop handles profit-taking on them.
|
||||
/// - Max loss is wide enough to avoid being hit by normal ATR-level moves.
|
||||
pub fn check_stop_loss_take_profit(
|
||||
&mut self,
|
||||
symbol: &str,
|
||||
@@ -78,34 +99,25 @@ impl Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
// Hard max-loss cap
|
||||
// 1. Hard max-loss cap (catastrophic gap protection)
|
||||
if pnl_pct <= -MAX_LOSS_PCT {
|
||||
return Some(Signal::StrongSell);
|
||||
}
|
||||
|
||||
// ATR-based stop loss
|
||||
// 2. ATR-based initial stop-loss (primary risk control)
|
||||
if entry_atr > 0.0 {
|
||||
let atr_stop_price = entry_price - ATR_STOP_MULTIPLIER * entry_atr;
|
||||
if current_price <= atr_stop_price {
|
||||
return Some(Signal::StrongSell);
|
||||
}
|
||||
} else if pnl_pct <= -STOP_LOSS_PCT {
|
||||
// 3. Fixed percentage fallback
|
||||
return Some(Signal::StrongSell);
|
||||
}
|
||||
|
||||
// Time-based exit
|
||||
if bars_held >= TIME_EXIT_BARS {
|
||||
let activation = if entry_atr > 0.0 {
|
||||
(ATR_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price
|
||||
} else {
|
||||
TRAILING_STOP_ACTIVATION
|
||||
};
|
||||
if pnl_pct < activation {
|
||||
return Some(Signal::Sell);
|
||||
}
|
||||
}
|
||||
|
||||
// ATR-based trailing stop
|
||||
// 4. ATR-based trailing stop (profit protection)
|
||||
// Activates earlier than before (1.5x ATR gain) so profits are locked in.
|
||||
// Distance is wider (2.5x ATR from HWM) so normal retracements don't trigger it.
|
||||
let activation_gain = if entry_atr > 0.0 {
|
||||
(ATR_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price
|
||||
} else {
|
||||
@@ -126,6 +138,14 @@ impl Strategy {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Time-based exit: only for LOSING positions (capital efficiency)
|
||||
// Winners at the time limit are managed by the trailing stop.
|
||||
// This prevents the old behavior of dumping winners just because they
|
||||
// haven't hit an arbitrary activation threshold in N bars.
|
||||
if bars_held >= TIME_EXIT_BARS && pnl_pct < 0.0 {
|
||||
return Some(Signal::Sell);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user