session.rs (1761B)
1 use rand::Rng; 2 use serde::{Deserialize, Serialize}; 3 4 use crate::data::Candle; 5 6 #[derive(Serialize, Deserialize, Clone, Copy, PartialEq)] 7 #[serde(rename_all = "lowercase")] 8 pub enum Direction { 9 Long, 10 Short, 11 } 12 13 pub struct ActivePrediction { 14 pub target_index: usize, 15 pub entry_price: f64, 16 pub direction: Direction, 17 pub fee_pct: f64, 18 } 19 20 #[derive(Serialize, Clone)] 21 pub struct ResolvedPrediction { 22 pub entry_price: f64, 23 pub close_price: f64, 24 pub direction: Direction, 25 pub fee_pct: f64, 26 pub profit: f64, 27 } 28 29 #[derive(Clone)] 30 pub enum SseEvent { 31 Candles { candles: Vec<Candle>, resolved: Option<ResolvedPrediction> }, 32 Predict { entry_price: f64, direction: Direction, bars_ahead: u32, fee_pct: f64 }, 33 PredictCancel, 34 } 35 36 pub struct SessionState { 37 pub start_index: usize, 38 pub current_index: usize, 39 pub prediction: Option<ActivePrediction>, 40 pub tx: tokio::sync::broadcast::Sender<SseEvent>, 41 } 42 43 impl SessionState { 44 pub fn new(start_index: usize) -> Self { 45 let (tx, _) = tokio::sync::broadcast::channel(256); 46 Self { 47 start_index, 48 current_index: start_index + 99, 49 prediction: None, 50 tx, 51 } 52 } 53 } 54 55 pub fn random_start_index(total: usize, rng: &mut impl Rng) -> usize { 56 let max = total.saturating_sub(5100); 57 rng.random_range(0..=max) 58 } 59 60 pub fn resolve_prediction(pred: &ActivePrediction, close_price: f64) -> f64 { 61 let gross_pct = match pred.direction { 62 Direction::Long => (close_price - pred.entry_price) / pred.entry_price, 63 Direction::Short => (pred.entry_price - close_price) / pred.entry_price, 64 }; 65 let fee_factor = (1.0 - pred.fee_pct / 100.0).powi(2); 66 gross_pct * fee_factor * 100.0 67 }