use super::pnl_report_data_row_calculator::PnLReportDataRowCalculator;
use crate::{
converter::any_value::AnyValueConverter,
enums::{my_any_value::MyAnyValueKind, trade_and_pre_trade::TradeDataKind},
lazy_frame_operations::trait_extensions::MyLazyFrameOperations,
trading_indicator::initial_balance::{InitialBalance, InitialBalanceCalculator},
MarketSimulationDataKind,
};
use polars::prelude::{DataFrame, IntoLazy};
use std::collections::HashMap;
pub struct TradeValuesCalculator {
pub market_sim_data: DataFrame,
pub market_sim_data_kind: MarketSimulationDataKind,
entry_price: f64,
}
#[derive(Default, Clone)]
pub struct TradeValuesWithData {
pub trade: HashMap<TradeDataKind, MyAnyValueKind>,
}
impl TradeValuesWithData {
pub fn last_trade_price(&self) -> f64 {
self.trade
.get(&TradeDataKind::LastTradePrice)
.unwrap()
.unwrap_float64()
}
pub fn entry_ts(&self) -> i64 {
self.trade
.get(&TradeDataKind::EntryTimestamp)
.unwrap()
.unwrap_int64()
}
pub fn initial_balance(&self) -> InitialBalance {
self.trade
.get(&TradeDataKind::InitialBalance)
.unwrap()
.unwrap_initial_balance()
}
}
impl TradeValuesCalculator {
pub fn compute(&self) -> Option<TradeValuesWithData> {
self.market_sim_data
.clone()
.lazy()
.find_timestamp_when_price_reached(self.entry_price)
.and_then(
|entry_ts| {
let trade = TradeValuesWithData {
trade: self.get_result(entry_ts),
};
Some(trade)
},
)
}
fn get_result(&self, entry_ts: i64) -> HashMap<TradeDataKind, MyAnyValueKind> {
self.get_trade_values_since_entry_timestamp(entry_ts)
.into_iter()
.fold(
self.initialize_trade_value_map(entry_ts),
update_trade_value_map,
)
}
fn get_trade_values_since_entry_timestamp(
&self,
entry_ts: i64,
) -> Vec<(TradeDataKind, MyAnyValueKind)> {
match_trade_values_with_trade_kind(self.get_trade_values_as_df(entry_ts))
}
fn initialize_trade_value_map(&self, entry_ts: i64) -> HashMap<TradeDataKind, MyAnyValueKind> {
HashMap::from([
(
TradeDataKind::EntryPrice,
MyAnyValueKind::Float64(self.entry_price),
),
(
TradeDataKind::EntryTimestamp,
MyAnyValueKind::Int64(entry_ts),
),
(
TradeDataKind::InitialBalance,
MyAnyValueKind::InitialBalance(self.get_initial_balance()),
),
])
}
fn get_initial_balance(&self) -> InitialBalance {
let calculator: InitialBalanceCalculator = self.into();
calculator.initial_balance()
}
fn get_trade_values_as_df(&self, entry_ts: i64) -> DataFrame {
self.market_sim_data
.clone()
.lazy()
.drop_rows_before_entry_ts(entry_ts)
.filter_trade_data_kind_values()
.collect()
.unwrap()
}
}
fn match_trade_values_with_trade_kind(
trade_values: DataFrame,
) -> Vec<(TradeDataKind, MyAnyValueKind)> {
let row = trade_values.get(0).unwrap();
vec![
(
TradeDataKind::LastTradePrice,
MyAnyValueKind::Float64(row[0].unwrap_float64()),
),
(
TradeDataKind::LowestTradePriceSinceEntry,
MyAnyValueKind::Float64(row[1].unwrap_float64()),
),
(
TradeDataKind::HighestTradePriceSinceEntry,
MyAnyValueKind::Float64(row[2].unwrap_float64()),
),
(
TradeDataKind::HighestTradePriceSinceEntryTimestamp,
MyAnyValueKind::Int64(row[3].unwrap_int64()),
),
(
TradeDataKind::LowestTradePriceSinceEntryTimestamp,
MyAnyValueKind::Int64(row[4].unwrap_int64()),
),
]
}
fn update_trade_value_map(
mut trade_data_map: HashMap<TradeDataKind, MyAnyValueKind>,
val: (TradeDataKind, MyAnyValueKind),
) -> HashMap<TradeDataKind, MyAnyValueKind> {
trade_data_map.insert(val.0, val.1);
trade_data_map
}
pub struct TradeValuesCalculatorBuilder {
market_sim_data: Option<DataFrame>,
market_sim_data_kind: Option<MarketSimulationDataKind>,
entry_price: Option<f64>,
}
impl From<&PnLReportDataRowCalculator> for TradeValuesCalculatorBuilder {
fn from(value: &PnLReportDataRowCalculator) -> Self {
Self {
market_sim_data: Some(value.market_sim_data.clone()),
market_sim_data_kind: Some(value.market_sim_data_kind),
entry_price: None,
}
}
}
impl TradeValuesCalculatorBuilder {
pub fn with_entry_price(self, entry_price: f64) -> Self {
Self {
entry_price: Some(entry_price),
..self
}
}
pub fn build(self) -> TradeValuesCalculator {
TradeValuesCalculator {
market_sim_data: self.market_sim_data.unwrap(),
market_sim_data_kind: self.market_sim_data_kind.unwrap(),
entry_price: self.entry_price.unwrap(),
}
}
pub fn build_and_compute(self) -> Option<TradeValuesWithData> {
self.build().compute()
}
}
#[cfg(test)]
mod test {
use crate::cloud_api::api_for_unit_tests::download_df;
use super::*;
#[tokio::test]
async fn test_compute_trade_values() {
let market_sim_data = download_df(
"chapaty-ai-test".to_string(),
"ppp/_test_data_files/trade_data.csv".to_string(),
)
.await;
let mut calculator = TradeValuesCalculator {
entry_price: 42_000.0,
market_sim_data_kind: MarketSimulationDataKind::Ohlc1h,
market_sim_data: market_sim_data.clone(),
};
let entry_price = 42_000.0;
let ts = 1646085600000_i64;
let lst_tp = 43_578.87;
let low = 41628.99;
let high = 44_225.84;
let low_ots = 1646085600000_i64;
let high_ots = 1646085600000_i64;
let mut target = HashMap::new();
target.insert(
TradeDataKind::EntryPrice,
MyAnyValueKind::Float64(entry_price),
);
target.insert(TradeDataKind::EntryTimestamp, MyAnyValueKind::Int64(ts));
target.insert(
TradeDataKind::LastTradePrice,
MyAnyValueKind::Float64(lst_tp),
);
target.insert(
TradeDataKind::LowestTradePriceSinceEntry,
MyAnyValueKind::Float64(low),
);
target.insert(
TradeDataKind::LowestTradePriceSinceEntryTimestamp,
MyAnyValueKind::Int64(low_ots),
);
target.insert(
TradeDataKind::HighestTradePriceSinceEntry,
MyAnyValueKind::Float64(high),
);
target.insert(
TradeDataKind::HighestTradePriceSinceEntryTimestamp,
MyAnyValueKind::Int64(high_ots),
);
target.insert(
TradeDataKind::InitialBalance,
MyAnyValueKind::InitialBalance(InitialBalance {
high: 38484.84,
low: 38127.76,
}),
);
assert_eq!(target, calculator.compute().unwrap().trade);
calculator = TradeValuesCalculator {
entry_price: 0.0,
market_sim_data_kind: MarketSimulationDataKind::Ohlc1h,
market_sim_data,
};
assert!(calculator.compute().is_none())
}
}