use std::{
cmp::Ordering,
collections::BTreeSet,
error::Error,
ops::RangeInclusive,
sync::{Arc, Mutex},
};
use crate::exchange::chart_data::klines::{binance, coinbase, KlineParams, KlinesSubset};
use crate::oscillators::{models::Hlc, stochastic::stochastic};
use super::{
models::{PnL,TriggerSignal},
pnl::{simulate, SimulateParams}
};
use rayon::prelude::*;
#[derive(Default, Debug, Clone, PartialEq, Eq)]
pub struct PnlParams {
pub k_length : u16,
pub k_smoothing : u16,
pub d_length : u16,
}
impl PartialOrd for PnlParams {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for PnlParams {
fn cmp(&self, other: &Self) -> Ordering {
self.k_length.cmp(&other.k_length)
.then_with(|| self.k_smoothing.cmp(&other.k_smoothing))
.then_with(|| self.d_length.cmp(&other.d_length))
}
}
#[derive(Debug)]
pub struct PnlRange {
pub k_length : RangeInclusive<u16>,
pub k_smoothing : RangeInclusive<u16>,
pub d_length : RangeInclusive<u16>,
}
#[derive(Debug)]
pub struct Stochastic<'a> {
pub exchange : &'a str,
pub klines : Vec<KlinesSubset>,
pub lhc : Vec<Hlc>,
pub capital : f64,
pub exchange_fee: Option<f64>,
pub min_qty : Option<f64>,
pub min_price : Option<f64>,
pub asset_scale : u32,
pub funds_scale : u32,
pub pnl_fast : bool
}
impl<'a> Stochastic<'a> {
#[allow(dead_code)]
pub fn new(exchange: &'a str, params: KlineParams,) -> Result<Self, Box<dyn Error>> {
let klines = match exchange {
"coinbase" => coinbase(params)?,
"binance" => binance(params)?,
_ => return Err(Box::new(std::io::Error::new(std::io::ErrorKind::InvalidInput, "Invalid exchange"))),
};
let lhc: Vec<Hlc> = klines
.iter()
.map(|kline| Hlc {
price_high: kline.price_high,
price_low: kline.price_low,
price_close: kline.price_close,
})
.collect();
Ok(Self { exchange, klines, lhc,
capital : 1000.0,
exchange_fee : None,
min_qty : None,
min_price : None,
asset_scale : 8,
funds_scale : 8,
pnl_fast : false
})
}
pub fn capital(mut self, capital: f64) -> Self { self.capital = capital;self }
pub fn exchange_fee(mut self, exchange_fee: f64) -> Self { self.exchange_fee = Some(exchange_fee);self }
pub fn min_qty(mut self, min_qty: f64) -> Self {self.min_qty = Some(min_qty); self }
pub fn min_price(mut self, min_price: f64) -> Self {self.min_price = Some(min_price); self }
pub fn asset_scale(mut self, asset_scale: u32) -> Self {self.asset_scale = asset_scale; self }
pub fn funds_scale(mut self, funds_scale: u32) -> Self {self.funds_scale = funds_scale; self }
pub fn pnl_fast(mut self, pnl_fast: bool) -> Self {self.pnl_fast = pnl_fast; self }
#[allow(dead_code)]
pub fn pnl(&self, pnl_params:PnlParams, ) -> PnL {
let stoch_values = stochastic(
&self.lhc,
pnl_params.k_length,
pnl_params.k_smoothing,
pnl_params.d_length
);
let complete_indx: Vec<usize> = stoch_values
.iter()
.enumerate()
.filter_map(|(index, value)| {
if value.k_line.is_some() && value.d_line.is_some() {
Some(index)
} else {
None
}
})
.collect();
let data:Vec<TriggerSignal> = complete_indx
.into_iter()
.map(|indx| TriggerSignal {
signal_in : stoch_values[indx].k_line.unwrap(),
signal_out : stoch_values[indx].d_line.unwrap(),
time_open : self.klines[indx].time_open,
price_open : self.klines[indx].price_open,
time_close : self.klines[indx].time_close,
price_close : self.klines[indx].price_close,
}).collect();
let sim_params = SimulateParams::new(data)
.capital(self.capital)
.exchange_fee(self.exchange_fee)
.min_qty(self.min_qty)
.min_price(self.min_price)
.asset_scale(self.asset_scale)
.funds_scale(self.funds_scale);
simulate(sim_params)
}
#[allow(dead_code)]
pub fn top_net_profit(&self, pnl_range:PnlRange) -> Arc<Mutex<BTreeSet<(Profit, PnlParams)>>> {
let top_profits = Arc::new(Mutex::new(BTreeSet::new()));
let k_length: Vec<_> = ((*pnl_range.k_length.start())..=(*pnl_range.k_length.end())).collect();
let k_smoothing: Vec<_> = ((*pnl_range.k_smoothing.start())..=(*pnl_range.k_smoothing.end())).collect();
let d_length: Vec<_> = ((*pnl_range.d_length.start())..=(*pnl_range.d_length.end())).collect();
k_length.par_iter().for_each(|&k_period| {
println!("{}", k_period);
for &k_smooth in &k_smoothing {
for &d_smooth in &d_length {
let pnl_params = PnlParams { k_length: k_period, k_smoothing: k_smooth, d_length: d_smooth };
let pnl = self.pnl(pnl_params.clone());
let mut top_profits = top_profits.lock().unwrap();
top_profits.insert((Profit(pnl.net_profit), pnl_params));
if top_profits.len() > 100 {
let smallest = top_profits.iter().next().cloned().unwrap();
top_profits.remove(&smallest);
}
}
}
});
top_profits
}
}
#[derive(Debug, Clone)]
pub struct Profit(pub f64);
impl PartialEq for Profit {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
impl Eq for Profit {}
impl PartialOrd for Profit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl Ord for Profit {
fn cmp(&self, other: &Self) -> Ordering {
self.partial_cmp(other).unwrap_or(Ordering::Equal)
}
}