use std::collections::HashMap;
use crate::backtesting::engine::SizingSeries;
use crate::backtesting::position::{Position, Trade};
use crate::backtesting::result::{EquityPoint, SignalRecord};
use crate::backtesting::strategy::Strategy;
use crate::models::chart::{Candle, Dividend};
pub(super) struct SymbolState<S: Strategy> {
pub(super) candles: Vec<Candle>,
pub(super) dividends: Vec<Dividend>,
pub(super) ts_index: HashMap<i64, usize>,
pub(super) indicators: HashMap<String, Vec<Option<f64>>>,
pub(super) sizing_series: SizingSeries,
pub(super) strategy: S,
pub(super) warmup: usize,
pub(super) position: Option<Position>,
pub(super) hwm: Option<f64>,
pub(super) extremes: Option<crate::backtesting::strategy::PositionExtremes>,
pub(super) track_extremes: bool,
pub(super) div_idx: usize,
pub(super) trades: Vec<Trade>,
pub(super) signals: Vec<SignalRecord>,
pub(super) realized_pnl: f64,
pub(super) equity_curve: Vec<EquityPoint>,
pub(super) sym_peak: f64,
pub(super) sym_max_leverage: f64,
pub(super) sym_initial_capital: f64,
pub(super) strategy_name: String,
}
pub(super) fn compute_buying_power<S: Strategy>(
cash: f64,
states: &HashMap<String, SymbolState<S>>,
timestamp: i64,
max_leverage: f64,
) -> f64 {
let (value, gross) = states
.values()
.filter_map(|s| {
s.position.as_ref().and_then(|pos| {
close_at_or_before(s, timestamp).map(|close| {
(
pos.current_value(close) + pos.unreinvested_dividends,
pos.quantity * close,
)
})
})
})
.fold((0.0, 0.0), |(v, g), (pv, pg)| (v + pv, g + pg));
(cash + value) * max_leverage - gross
}
pub(super) fn compute_portfolio_equity<S: Strategy>(
cash: f64,
states: &HashMap<String, SymbolState<S>>,
timestamp: i64,
) -> f64 {
cash + states
.values()
.filter_map(|s| {
s.position.as_ref().and_then(|pos| {
close_at_or_before(s, timestamp)
.map(|close| pos.current_value(close) + pos.unreinvested_dividends)
})
})
.sum::<f64>()
}
pub(super) fn close_at_or_before<S: Strategy>(
state: &SymbolState<S>,
timestamp: i64,
) -> Option<f64> {
if let Some(&idx) = state.ts_index.get(×tamp) {
return Some(state.candles[idx].close);
}
match state
.candles
.binary_search_by_key(×tamp, |c| c.timestamp)
{
Ok(idx) | Err(idx) if idx > 0 => Some(state.candles[idx.saturating_sub(1)].close),
_ => None,
}
}