more profit
This commit is contained in:
@@ -78,6 +78,12 @@ pub const RISK_PER_TRADE: f64 = 0.015; // 1.5% risk per trade (8 positions * 1.5
|
|||||||
pub const ATR_STOP_MULTIPLIER: f64 = 3.5; // Wide stops reduce false stop-outs (the #1 loss source)
|
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
|
||||||
|
// Tiered trailing stop: tight trail for small gains, wide trail for big gains
|
||||||
|
pub const EARLY_TRAIL_ACTIVATION_MULTIPLIER: f64 = 0.5; // Activate tight trail after 0.5x ATR gain
|
||||||
|
pub const EARLY_TRAIL_MULTIPLIER: f64 = 1.5; // Tight trail distance for small gains
|
||||||
|
// Breakeven protection: once in profit, don't let it become a big loss
|
||||||
|
pub const BREAKEVEN_ACTIVATION_PCT: f64 = 0.02; // Activate after 2% gain (meaningful, not noise)
|
||||||
|
pub const BREAKEVEN_MAX_LOSS_PCT: f64 = 0.005; // Once activated, don't give back more than 0.5% from entry
|
||||||
// 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;
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ use tower_http::cors::CorsLayer;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
alpaca::AlpacaClient,
|
alpaca::AlpacaClient,
|
||||||
config::{ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER},
|
config::{
|
||||||
|
ATR_STOP_MULTIPLIER, ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER,
|
||||||
|
BREAKEVEN_ACTIVATION_PCT, BREAKEVEN_MAX_LOSS_PCT,
|
||||||
|
EARLY_TRAIL_ACTIVATION_MULTIPLIER, EARLY_TRAIL_MULTIPLIER,
|
||||||
|
},
|
||||||
paths::{LIVE_ENTRY_ATRS_FILE, LIVE_EQUITY_FILE, LIVE_HIGH_WATER_MARKS_FILE},
|
paths::{LIVE_ENTRY_ATRS_FILE, LIVE_EQUITY_FILE, LIVE_HIGH_WATER_MARKS_FILE},
|
||||||
types::EquitySnapshot,
|
types::EquitySnapshot,
|
||||||
};
|
};
|
||||||
@@ -580,12 +584,26 @@ async fn api_positions(State(state): State<Arc<DashboardState>>) -> impl IntoRes
|
|||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
|
|
||||||
let (trail_status, stop_loss_price) = if pnl_pct >= activation_gain && entry_atr > 0.0 {
|
let best_pnl = (high_water_mark - entry_price) / entry_price;
|
||||||
|
let big_activation = if entry_atr > 0.0 {
|
||||||
|
(ATR_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price
|
||||||
|
} else { 0.0 };
|
||||||
|
let small_activation = if entry_atr > 0.0 {
|
||||||
|
(EARLY_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price
|
||||||
|
} else { 0.0 };
|
||||||
|
|
||||||
|
let (trail_status, stop_loss_price) = if best_pnl >= BREAKEVEN_ACTIVATION_PCT && pnl_pct <= -BREAKEVEN_MAX_LOSS_PCT {
|
||||||
|
("Breakeven!".to_string(), entry_price * (1.0 - BREAKEVEN_MAX_LOSS_PCT))
|
||||||
|
} else if entry_atr > 0.0 && best_pnl >= big_activation {
|
||||||
let trail_distance = ATR_TRAIL_MULTIPLIER * entry_atr;
|
let trail_distance = ATR_TRAIL_MULTIPLIER * entry_atr;
|
||||||
let stop_price = high_water_mark - trail_distance;
|
let stop_price = high_water_mark - trail_distance;
|
||||||
("Active".to_string(), stop_price)
|
("Wide Trail".to_string(), stop_price)
|
||||||
|
} else if entry_atr > 0.0 && pnl_pct >= small_activation {
|
||||||
|
let trail_distance = EARLY_TRAIL_MULTIPLIER * entry_atr;
|
||||||
|
let stop_price = high_water_mark - trail_distance;
|
||||||
|
("Tight Trail".to_string(), stop_price)
|
||||||
} else {
|
} else {
|
||||||
("Inactive".to_string(), entry_price - ATR_TRAIL_MULTIPLIER * entry_atr)
|
("Inactive".to_string(), entry_price - ATR_STOP_MULTIPLIER * entry_atr)
|
||||||
};
|
};
|
||||||
|
|
||||||
PositionResponse {
|
PositionResponse {
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use crate::config::{
|
use crate::config::{
|
||||||
get_sector, IndicatorParams, Timeframe, ATR_STOP_MULTIPLIER,
|
get_sector, IndicatorParams, Timeframe, ATR_STOP_MULTIPLIER,
|
||||||
ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER, MAX_LOSS_PCT, MAX_POSITION_SIZE,
|
ATR_TRAIL_ACTIVATION_MULTIPLIER, ATR_TRAIL_MULTIPLIER,
|
||||||
|
BREAKEVEN_ACTIVATION_PCT, BREAKEVEN_MAX_LOSS_PCT,
|
||||||
|
EARLY_TRAIL_ACTIVATION_MULTIPLIER, EARLY_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,
|
||||||
};
|
};
|
||||||
@@ -66,18 +69,14 @@ impl Strategy {
|
|||||||
/// Check if stop-loss, trailing stop, or time exit should trigger.
|
/// Check if stop-loss, trailing stop, or time exit should trigger.
|
||||||
///
|
///
|
||||||
/// Exit priority (checked in order):
|
/// Exit priority (checked in order):
|
||||||
/// 1. Hard max-loss cap (MAX_LOSS_PCT) -- absolute worst-case, gap protection
|
/// 1. Hard max-loss cap (MAX_LOSS_PCT) -- gap protection
|
||||||
/// 2. ATR-based stop-loss (ATR_STOP_MULTIPLIER * ATR) -- primary risk control
|
/// 2. ATR-based stop-loss -- primary risk control
|
||||||
/// 3. Fixed % stop-loss (STOP_LOSS_PCT) -- fallback when ATR unavailable
|
/// 3. Fixed % stop-loss -- fallback when ATR unavailable
|
||||||
/// 4. ATR trailing stop (ATR_TRAIL_MULTIPLIER * ATR from HWM) -- profit protection
|
/// 4. Breakeven ratchet -- once in profit, never lose more than 1%
|
||||||
/// 5. Time-based exit (TIME_EXIT_BARS) -- only if position is LOSING
|
/// 5. Tiered trailing stop:
|
||||||
///
|
/// - Small gains (0.5x ATR): tight trail (1.5x ATR)
|
||||||
/// Key design decisions:
|
/// - Big gains (2.0x ATR): wide trail (3.0x ATR)
|
||||||
/// - Trailing stop activates early (1.5x ATR) but has wide distance (2.5x ATR)
|
/// 6. Time-based exit -- only if position is LOSING
|
||||||
/// 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(
|
pub fn check_stop_loss_take_profit(
|
||||||
&mut self,
|
&mut self,
|
||||||
symbol: &str,
|
symbol: &str,
|
||||||
@@ -115,22 +114,39 @@ impl Strategy {
|
|||||||
return Some(Signal::StrongSell);
|
return Some(Signal::StrongSell);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. ATR-based trailing stop (profit protection)
|
// 4. Breakeven ratchet: once we've been in profit, cap downside to -1%
|
||||||
// Activates earlier than before (1.5x ATR gain) so profits are locked in.
|
if pnl_pct <= -BREAKEVEN_MAX_LOSS_PCT {
|
||||||
// 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 {
|
|
||||||
TRAILING_STOP_ACTIVATION
|
|
||||||
};
|
|
||||||
|
|
||||||
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 best_pnl = (high_water - entry_price) / entry_price;
|
||||||
ATR_TRAIL_MULTIPLIER * entry_atr
|
if best_pnl >= BREAKEVEN_ACTIVATION_PCT {
|
||||||
|
// Was in profit but now losing > 1% — get out
|
||||||
|
return Some(Signal::Sell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Tiered ATR trailing stop (profit protection)
|
||||||
|
// Tier 1: small gains (0.5x ATR) → tight trail (1.5x ATR)
|
||||||
|
// Tier 2: big gains (2.0x ATR) → wide trail (3.0x ATR) to let winners run
|
||||||
|
if let Some(&high_water) = self.high_water_marks.get(symbol) {
|
||||||
|
let best_pnl = (high_water - entry_price) / entry_price;
|
||||||
|
|
||||||
|
let (activation_gain, trail_distance) = if entry_atr > 0.0 {
|
||||||
|
let big_activation = (ATR_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price;
|
||||||
|
let small_activation = (EARLY_TRAIL_ACTIVATION_MULTIPLIER * entry_atr) / entry_price;
|
||||||
|
|
||||||
|
if best_pnl >= big_activation {
|
||||||
|
// Tier 2: big winner — wide trail
|
||||||
|
(big_activation, ATR_TRAIL_MULTIPLIER * entry_atr)
|
||||||
} else {
|
} else {
|
||||||
high_water * TRAILING_STOP_DISTANCE
|
// Tier 1: small gain — tight trail
|
||||||
};
|
(small_activation, EARLY_TRAIL_MULTIPLIER * entry_atr)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(TRAILING_STOP_ACTIVATION, high_water * TRAILING_STOP_DISTANCE)
|
||||||
|
};
|
||||||
|
|
||||||
|
if pnl_pct >= activation_gain {
|
||||||
let trailing_stop_price = high_water - trail_distance;
|
let trailing_stop_price = high_water - trail_distance;
|
||||||
if current_price <= trailing_stop_price {
|
if current_price <= trailing_stop_price {
|
||||||
return Some(Signal::Sell);
|
return Some(Signal::Sell);
|
||||||
@@ -138,7 +154,7 @@ impl Strategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Time-based exit: only for LOSING positions (capital efficiency)
|
// 6. Time-based exit: only for LOSING positions (capital efficiency)
|
||||||
// Winners at the time limit are managed by the trailing stop.
|
// Winners at the time limit are managed by the trailing stop.
|
||||||
// This prevents the old behavior of dumping winners just because they
|
// This prevents the old behavior of dumping winners just because they
|
||||||
// haven't hit an arbitrary activation threshold in N bars.
|
// haven't hit an arbitrary activation threshold in N bars.
|
||||||
|
|||||||
Reference in New Issue
Block a user