From 2c2f164e187fc7e0fe799e616ee54f0b45b83249 Mon Sep 17 00:00:00 2001 From: Giulio De Pasquale Date: Thu, 14 Jan 2021 12:42:23 +0000 Subject: [PATCH] price manager working --- rustybot/src/bot.rs | 116 +++++++++++--------- rustybot/src/connectors.rs | 155 ++++++++++++++++++-------- rustybot/src/events.rs | 18 +-- rustybot/src/main.rs | 24 ++-- rustybot/src/managers.rs | 45 +++++++- rustybot/src/models.rs | 28 +++++ rustybot/src/pairs.rs | 208 ++++++++++++++++++++--------------- rustybot/src/strategy.rs | 218 ++++++++++++++++++------------------- 8 files changed, 495 insertions(+), 317 deletions(-) diff --git a/rustybot/src/bot.rs b/rustybot/src/bot.rs index 886d224..e832c3e 100644 --- a/rustybot/src/bot.rs +++ b/rustybot/src/bot.rs @@ -4,93 +4,107 @@ use std::collections::HashMap; use bitfinex::api::Bitfinex; use bitfinex::positions::Position; use bitfinex::ticker::TradingPairTicker; +use futures_util::stream::FuturesUnordered; use tokio::time::delay_for; -use crate::connectors::Connector; +use crate::connectors::{Client, Connector, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; -use crate::events::EventKind; -use crate::pairs::PairStatus; +use crate::events::{Event, EventKind}; +use crate::managers::{OrderManager, PositionManager}; +use crate::pairs::PriceManager; use crate::strategy::PositionStrategy; use crate::ticker::Ticker; use crate::BoxError; +use tokio::stream::StreamExt; -pub struct BfxBot<'a> { - connector: Box, +pub struct BfxBot { ticker: Ticker, - pair_statuses: Vec>, quote: Symbol, trading_symbols: Vec, + price_managers: Vec, + order_managers: Vec, + pos_managers: Vec, } -impl<'a> BfxBot<'a> { - pub fn new( - connector: C, +impl BfxBot { + pub fn new( + exchanges: Vec, trading_symbols: Vec, quote: Symbol, tick_duration: Duration, ) -> Self { - let pair_statuses = trading_symbols - .iter() - .map(|x| SymbolPair::new(quote.clone(), x.clone())) - .map(|x| PairStatus::new(x, 1, None)) - .collect(); + let clients: Vec<_> = exchanges.iter().map(|x| Client::new(x)).collect(); + + let mut pos_managers = Vec::new(); + let mut order_managers = Vec::new(); + let mut pair_statuses = Vec::new(); + + for c in clients { + pos_managers.push(PositionManager::new(c.clone())); + order_managers.push(OrderManager::new(c.clone())); + pair_statuses.extend( + trading_symbols + .iter() + .map(|x| SymbolPair::new(quote.clone(), x.clone())) + .map(|x| PriceManager::new(x, c.clone())) + .collect::>(), + ) + } BfxBot { - connector: Box::new(connector), ticker: Ticker::new(tick_duration), - pair_statuses, + price_managers: pair_statuses, quote, trading_symbols, + order_managers, + pos_managers, } } - pub fn with_strategy(mut self, strategy: Box) -> Self { - self.pair_statuses - .iter_mut() - .for_each(|x| x.set_strategy(dyn_clone::clone_box(&*strategy))); - - self - } - - pub async fn current_prices(&self, symbol: Symbol) -> Result { - let trading_pair = SymbolPair::new(self.quote.clone(), symbol.clone()); - - if !self.trading_symbols.contains(&symbol) { - return Err("Symbol not supported.".into()); - } - - self.connector.current_prices(&trading_pair).await - } - pub async fn start_loop(&mut self) -> Result<(), BoxError> { - if let Err(e) = self.update_pair_statuses().await { - println!("Error while updating pairs at first start: {}", e); - } - loop { self.update().await; } } - async fn update(&mut self) { + async fn update<'a>(&'a mut self) { delay_for(self.ticker.duration()).await; self.ticker.inc(); - if let Err(e) = self.update_pair_statuses().await { - println!("Error while updating pairs: {}", e); - } + self.update_price_managers().await.unwrap(); } - async fn update_pair_statuses(&mut self) -> Result<(), BoxError> { - for status in &mut self.pair_statuses { - // add positions for each pair - self.connector - .active_positions(status.pair()) - .await? - .into_iter() - .for_each(|x| status.add_position(x)); + async fn update_price_managers(&mut self) -> Result>, BoxError> { + let futures: Vec<_> = self + .price_managers + .clone() + .into_iter() + // the only reason you need the async block is that the future + // returned by x.update(tick) borrows from x + // so we create a future that first takes ownership of x, then uses it to call x.update + .map(|mut x| { + let tick = self.ticker.current_tick(); + async move { x.update(tick).await } + }) + .map(tokio::spawn) + .collect(); + + let mut price_entries = vec![]; + + for f in futures { + price_entries.push(f.await??); } - Ok(()) + for mut manager in &mut self.price_managers { + let prices: Vec<_> = price_entries + .drain_filter(|x| x.pair() == manager.pair()) + .collect(); + + for p in prices { + manager.add_entry(p); + } + } + + Ok(None) } } diff --git a/rustybot/src/connectors.rs b/rustybot/src/connectors.rs index 09ec11e..bbe5725 100644 --- a/rustybot/src/connectors.rs +++ b/rustybot/src/connectors.rs @@ -1,4 +1,5 @@ use std::convert::{TryFrom, TryInto}; +use std::sync::Arc; use async_trait::async_trait; use bitfinex::api::Bitfinex; @@ -6,11 +7,11 @@ use bitfinex::orders::{OrderForm, OrderMeta}; use bitfinex::ticker::TradingPairTicker; use crate::currency::{Symbol, SymbolPair}; -use crate::models::{Order, OrderKind, Position, PositionState}; +use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker}; use crate::BoxError; -use std::sync::Arc; +use std::fmt::{Debug, Formatter}; -#[derive(Eq, PartialEq, Hash, Clone)] +#[derive(Eq, PartialEq, Hash, Clone, Debug)] pub enum ExchangeKind { Bitfinex { api_key: String, @@ -19,31 +20,56 @@ pub enum ExchangeKind { }, } -/// You do **not** have to wrap the `Client` it in an [`Rc`] or [`Arc`] to **reuse** it, +/// You do **not** have to wrap the `Client` in an [`Rc`] or [`Arc`] to **reuse** it, /// because it already uses an [`Arc`] internally. -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Client { + exchange: ExchangeKind, inner: Arc>, } impl Client { - pub fn new(exchange: ExchangeKind) -> Self { - let inner = match exchange { + pub fn new(exchange: &ExchangeKind) -> Self { + let inner = match &exchange { ExchangeKind::Bitfinex { api_key, api_secret, affiliate_code, - } => BitfinexConnector::new(&api_key, &api_secret).with_affiliate_code(affiliate_code), + } => BitfinexConnector::new(&api_key, &api_secret) + .with_affiliate_code(affiliate_code.clone()), }; Client { + exchange: exchange.clone(), inner: Arc::new(Box::new(inner)), } } + + pub async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + self.inner.active_positions(pair).await + } + + pub async fn current_prices(&self, pair: &SymbolPair) -> Result { + self.inner.current_prices(pair).await + } + + pub async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + self.inner.active_orders(pair).await + } + + pub async fn submit_order( + &self, + pair: &SymbolPair, + amount: f64, + price: f64, + kind: &OrderKind, + ) -> Result<(), BoxError> { + self.inner.submit_order(pair, amount, price, kind).await + } } #[async_trait] -pub trait Connector { +pub trait Connector: Send + Sync { async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result; async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError>; @@ -56,6 +82,16 @@ pub trait Connector { ) -> Result<(), BoxError>; } +impl Debug for dyn Connector { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "Connector") + } +} + +/************** +* BITFINEX +**************/ + pub struct BitfinexConnector { bfx: Bitfinex, affiliate_code: Option, @@ -75,6 +111,57 @@ impl BitfinexConnector { self.affiliate_code = affiliate_code; self } + + fn format_trading_pair(&self, pair: &SymbolPair) -> String { + if pair.to_string().to_lowercase().contains("test") { + format!("{}:{}", pair.base(), pair.quote()) + } else { + format!("{}{}", pair.base(), pair.quote()) + } + } +} + +#[async_trait] +impl Connector for BitfinexConnector { + async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { + let active_positions = self.bfx.positions.active_positions().await?; + + Ok(active_positions + .into_iter() + .filter_map(|x| x.try_into().ok()) + .filter(|x: &Position| x.pair() == pair) + .collect()) + } + + async fn current_prices(&self, pair: &SymbolPair) -> Result { + let ticker: TradingPairTicker = self + .bfx + .ticker + .trading_pair(self.format_trading_pair(pair)) + .await?; + + Ok(ticker) + } + + async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { + unimplemented!() + } + + async fn submit_order( + &self, + pair: &SymbolPair, + amount: f64, + price: f64, + kind: &OrderKind, + ) -> Result<(), BoxError> { + let order_form = match &self.affiliate_code { + Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) + .with_meta(OrderMeta::new(affiliate_code.clone())), + None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), + }; + + Ok(self.bfx.orders.submit_order(&order_form).await?) + } } impl TryInto for bitfinex::positions::Position { @@ -124,41 +211,19 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind { } } -#[async_trait] -impl Connector for BitfinexConnector { - async fn active_positions(&self, pair: &SymbolPair) -> Result, BoxError> { - let active_positions = self.bfx.positions.active_positions().await?; - - Ok(active_positions - .into_iter() - .filter_map(|x| x.try_into().ok()) - .filter(|x: &Position| x.pair() == pair) - .collect()) - } - - async fn current_prices(&self, pair: &SymbolPair) -> Result { - let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?; - - Ok(ticker) - } - - async fn active_orders(&self, pair: &SymbolPair) -> Result, BoxError> { - unimplemented!() - } - - async fn submit_order( - &self, - pair: &SymbolPair, - amount: f64, - price: f64, - kind: &OrderKind, - ) -> Result<(), BoxError> { - let order_form = match &self.affiliate_code { - Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) - .with_meta(OrderMeta::new(affiliate_code.clone())), - None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), - }; - - Ok(self.bfx.orders.submit_order(&order_form).await?) +impl From for PriceTicker { + fn from(t: TradingPairTicker) -> Self { + Self { + bid: t.bid, + bid_size: t.bid_size, + ask: t.ask, + ask_size: t.ask_size, + daily_change: t.daily_change, + daily_change_perc: t.daily_change_perc, + last_price: t.last_price, + volume: t.volume, + high: t.high, + low: t.low, + } } } diff --git a/rustybot/src/events.rs b/rustybot/src/events.rs index c32b0ae..1fdfa14 100644 --- a/rustybot/src/events.rs +++ b/rustybot/src/events.rs @@ -6,7 +6,7 @@ use tokio::task::JoinHandle; use crate::bot::BfxBot; use crate::models::{Position, PositionProfitState}; -use crate::pairs::PairStatus; +use crate::pairs::PriceManager; use crate::BoxError; #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -78,12 +78,12 @@ impl Event { } pub struct EventDispatcher { - event_handlers: HashMap JoinHandle<()>>>>, + event_handlers: HashMap JoinHandle<()>>>>, profit_state_handlers: - HashMap JoinHandle<()>>>>, + HashMap JoinHandle<()>>>>, signal_handlers: HashMap JoinHandle<()>>>>, - on_any_event_handlers: Vec JoinHandle<()>>>, - on_any_profit_state_handlers: Vec JoinHandle<()>>>, + on_any_event_handlers: Vec JoinHandle<()>>>, + on_any_profit_state_handlers: Vec JoinHandle<()>>>, } impl EventDispatcher { @@ -105,7 +105,7 @@ impl EventDispatcher { } } - pub fn call_event_handlers(&self, event: &Event, status: &PairStatus) { + pub fn call_event_handlers(&self, event: &Event, status: &PriceManager) { if let Some(functions) = self.event_handlers.get(&event.kind()) { for f in functions { f(event, status); @@ -117,7 +117,7 @@ impl EventDispatcher { } } - pub fn call_position_state_handlers(&self, position: &Position, status: &PairStatus) { + pub fn call_position_state_handlers(&self, position: &Position, status: &PriceManager) { if let Some(profit_state) = &position.profit_state() { if let Some(functions) = self.profit_state_handlers.get(profit_state) { for f in functions { @@ -133,7 +133,7 @@ impl EventDispatcher { pub fn register_event_handler(&mut self, event: EventKind, f: F) where - F: Fn(&Event, &PairStatus) -> Fut, + F: Fn(&Event, &PriceManager) -> Fut, Fut: Future + Send, { match event { @@ -153,7 +153,7 @@ impl EventDispatcher { state: PositionProfitState, f: F, ) where - F: Fn(&Position, &PairStatus) -> Fut, + F: Fn(&Position, &PriceManager) -> Fut, Fut: Future + Send, { match state { diff --git a/rustybot/src/main.rs b/rustybot/src/main.rs index 22ce0de..eddc722 100644 --- a/rustybot/src/main.rs +++ b/rustybot/src/main.rs @@ -1,11 +1,13 @@ +#![feature(drain_filter)] + +use std::thread::JoinHandle; + use tokio::time::{delay_for, Duration}; use crate::bot::BfxBot; -use crate::connectors::BitfinexConnector; +use crate::connectors::{BitfinexConnector, ExchangeKind}; use crate::currency::{Symbol, SymbolPair}; use crate::events::SignalKind; -use crate::strategy::TrailingStop; -use std::thread::JoinHandle; mod bot; mod connectors; @@ -23,16 +25,20 @@ pub type BoxError = Box; async fn main() -> Result<(), BoxError> { let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; - let bfx = BitfinexConnector::new(test_api_key, test_api_secret) - .with_affiliate_code(Some("XPebOgHxA".into())); + let affiliate_code = "XPebOgHxA"; + + let bitfinex = ExchangeKind::Bitfinex { + api_key: test_api_key.into(), + api_secret: test_api_secret.into(), + affiliate_code: Some(affiliate_code.into()), + }; let mut bot = BfxBot::new( - bfx, + vec![bitfinex], vec![Symbol::TESTBTC], Symbol::TESTUSD, - Duration::new(20, 0), - ) - .with_strategy(Box::new(TrailingStop::new())); + Duration::new(1, 0), + ); Ok(bot.start_loop().await?) } diff --git a/rustybot/src/managers.rs b/rustybot/src/managers.rs index c896632..8aa16f2 100644 --- a/rustybot/src/managers.rs +++ b/rustybot/src/managers.rs @@ -1,21 +1,58 @@ +use std::collections::{HashMap, VecDeque}; + use crate::connectors::{Client, Connector}; use crate::events::Event; use crate::models::{Order, Position}; +use crate::strategy::PositionStrategy; use crate::ticker::Ticker; -use std::collections::{HashMap, VecDeque}; -struct EventManager { +pub struct EventManager { events: Vec, } -struct PositionManager { +pub struct PositionManager { queue: VecDeque, open_positions: Vec, client: Client, + strategy: Option>, } -struct OrderManager { +impl PositionManager { + pub fn new(client: Client) -> Self { + PositionManager { + queue: VecDeque::new(), + open_positions: Vec::new(), + client, + strategy: None, + } + } + + pub fn with_strategy(mut self, strategy: Box) -> Self { + self.strategy = Some(strategy); + self + } + + pub fn update(&self) -> Option> { + unimplemented!() + } +} + +pub struct OrderManager { queue: VecDeque, open_orders: Vec, client: Client, } + +impl OrderManager { + pub fn new(client: Client) -> Self { + OrderManager { + queue: VecDeque::new(), + open_orders: Vec::new(), + client, + } + } + + pub fn update(&self) -> Option> { + unimplemented!() + } +} diff --git a/rustybot/src/models.rs b/rustybot/src/models.rs index 674b1bc..f769bc6 100644 --- a/rustybot/src/models.rs +++ b/rustybot/src/models.rs @@ -1,5 +1,28 @@ use crate::currency::SymbolPair; +/*************** +* Prices +***************/ + +#[derive(Copy, Clone, Debug)] +pub struct PriceTicker { + pub bid: f64, + pub bid_size: f64, + pub ask: f64, + pub ask_size: f64, + pub daily_change: f64, + pub daily_change_perc: f64, + pub last_price: f64, + pub volume: f64, + pub high: f64, + pub low: f64, +} + +/*************** +* Orders +***************/ + +#[derive(Clone, Debug)] pub struct Order { pub id: i64, pub group_id: Option, @@ -22,6 +45,7 @@ pub struct Order { pub placed_id: Option, } +#[derive(Copy, Clone, Debug)] pub enum OrderKind { Limit, ExchangeLimit, @@ -38,6 +62,10 @@ pub enum OrderKind { ExchangeIoc, } +/*************** +* Positions +***************/ + #[derive(Clone, Debug)] pub struct Position { pair: SymbolPair, diff --git a/rustybot/src/pairs.rs b/rustybot/src/pairs.rs index 479efe5..5ca22d6 100644 --- a/rustybot/src/pairs.rs +++ b/rustybot/src/pairs.rs @@ -1,117 +1,145 @@ use std::collections::HashMap; +// use crate::strategy::PositionStrategy; +use crate::connectors::Client; use crate::currency::SymbolPair; use crate::events::{Event, EventDispatcher, SignalKind}; -use crate::models::{Order, Position}; -use crate::strategy::PositionStrategy; +use crate::models::{Order, Position, PriceTicker}; +use crate::BoxError; -pub struct PairStatus<'a> { +#[derive(Clone, Debug)] +pub struct PriceManager { pair: SymbolPair, - dispatcher: EventDispatcher, - prices: HashMap, - events: Vec, - orders: HashMap>, - positions: HashMap>, - current_tick: u64, - strategy: Option>, - signals: HashMap, + prices: Vec, + client: Client, } -impl<'a> PairStatus<'a> { +#[derive(Clone, Debug)] +pub struct PriceEntry { + tick: u64, + pair: SymbolPair, + price: PriceTicker, + events: Option>, + signals: Option>, +} + +impl PriceEntry { + pub fn tick(&self) -> u64 { + self.tick + } + pub fn pair(&self) -> &SymbolPair { + &self.pair + } + pub fn price(&self) -> PriceTicker { + self.price + } + pub fn events(&self) -> &Option> { + &self.events + } + pub fn signals(&self) -> &Option> { + &self.signals + } +} + +impl PriceEntry { pub fn new( + tick: u64, + price: PriceTicker, pair: SymbolPair, - current_tick: u64, - strategy: Option>, + events: Option>, + signals: Option>, ) -> Self { - PairStatus { + PriceEntry { + tick, pair, - dispatcher: EventDispatcher::new(), - prices: HashMap::new(), - events: Vec::new(), - positions: HashMap::new(), - orders: HashMap::new(), - signals: HashMap::new(), - current_tick, - strategy, + price, + events, + signals, + } + } +} + +impl PriceManager { + pub fn new(pair: SymbolPair, client: Client) -> Self { + PriceManager { + pair, + prices: Vec::new(), + client, } } - pub fn dispatcher(&self) -> &EventDispatcher { - &self.dispatcher - } - pub fn dispatcher_mut(&mut self) -> &mut EventDispatcher { - &mut self.dispatcher + pub fn add_entry(&mut self, entry: PriceEntry) { + self.prices.push(entry); } - pub fn add_position(&mut self, position: Position) { - let (new_position, events, signals) = { - match &self.strategy { - Some(strategy) => strategy.on_new_tick(&position, &self), - None => (position, vec![], vec![]), - } - }; + pub async fn update(&mut self, tick: u64) -> Result { + let current_prices = self.client.current_prices(&self.pair).await?.into(); - self.positions - .entry(self.current_tick) - .or_default() - .push(new_position.clone()); - - // calling position state callbacks - self.dispatcher - .call_position_state_handlers(&new_position, &self); - - // adding events and calling callbacks - for e in events { - self.add_event(e); - } - - // adding signals to current tick vector - for s in signals { - self.add_signal(s); - } - - println!( - "EVENTS: {:?} | SIGNALS: {:?} | POSITION: {:?}", - self.events, self.signals, new_position - ); + Ok(PriceEntry::new( + tick, + current_prices, + self.pair.clone(), + None, + None, + )) } - fn add_event(&mut self, event: Event) { - self.events.push(event); + // pub fn add_position(&mut self, position: Position) { + // let (new_position, events, signals) = { + // match &self.strategy { + // Some(strategy) => strategy.on_new_tick(&position, &self), + // None => (position, vec![], vec![]), + // } + // }; + // + // self.positions + // .entry(self.current_tick) + // .or_default() + // .push(new_position.clone()); + // + // // calling position state callbacks + // self.dispatcher + // .call_position_state_handlers(&new_position, &self); + // + // // adding events and calling callbacks + // for e in events { + // self.add_event(e); + // } + // + // // adding signals to current tick vector + // for s in signals { + // self.add_signal(s); + // } + // } - self.dispatcher.call_event_handlers(&event, &self); - } - - fn add_signal(&mut self, signal: SignalKind) { - self.signals.insert(self.current_tick(), signal); - } - - pub fn current_tick(&self) -> u64 { - self.current_tick - } - - pub fn set_strategy(&mut self, strategy: Box) { - self.strategy = Some(strategy); - } + // fn add_event(&mut self, event: Event) { + // self.events.push(event); + // + // self.dispatcher.call_event_handlers(&event, &self); + // } + // + // fn add_signal(&mut self, signal: SignalKind) { + // self.signals.insert(self.current_tick(), signal); + // } pub fn pair(&self) -> &SymbolPair { &self.pair } - pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { - let tick = match tick { - Some(tick) => { - if tick < 1 { - 1 - } else { - tick - } - } - None => self.current_tick() - 1, - }; - - self.positions - .get(&tick) - .and_then(|x| x.iter().find(|x| x.position_id() == id)) - } + // pub fn position_previous_tick(&self, id: u64, tick: Option) -> Option<&Position> { + // let tick = match tick { + // Some(tick) => { + // if tick < 1 { + // 1 + // } else { + // tick + // } + // } + // None => self.current_tick() - 1, + // }; + // + // self.positions + // .get(&tick) + // .and_then(|x| x.iter().find(|x| x.position_id() == id)) + // } } diff --git a/rustybot/src/strategy.rs b/rustybot/src/strategy.rs index 658dcb7..ed9589c 100644 --- a/rustybot/src/strategy.rs +++ b/rustybot/src/strategy.rs @@ -2,121 +2,121 @@ use std::collections::HashMap; use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::models::{Position, PositionProfitState}; -use crate::pairs::PairStatus; +use crate::pairs::PriceManager; use dyn_clone::DynClone; pub trait PositionStrategy: DynClone { fn on_new_tick( &self, position: &Position, - status: &PairStatus, + status: &PriceManager, ) -> (Position, Vec, Vec); } -#[derive(Clone, Debug)] -pub struct TrailingStop { - stop_percentages: HashMap, -} - -impl TrailingStop { - const BREAK_EVEN_PERC: f64 = 0.2; - const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; - const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; - const MAX_LOSS_PERC: f64 = -1.7; - - const TAKER_FEE: f64 = 0.2; - - pub fn new() -> Self { - TrailingStop { - stop_percentages: HashMap::new(), - } - } - - fn net_pl_percentage(pl: f64, fee: f64) -> f64 { - pl - fee - } -} - -impl PositionStrategy for TrailingStop { - fn on_new_tick( - &self, - position: &Position, - status: &PairStatus, - ) -> (Position, Vec, Vec) { - let mut signals = vec![]; - let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); - let events = vec![]; - - let state = { - if pl_perc > TrailingStop::GOOD_PROFIT_PERC { - PositionProfitState::Profit - } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc - && pl_perc < TrailingStop::GOOD_PROFIT_PERC - { - PositionProfitState::MinimumProfit - } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { - PositionProfitState::BreakEven - } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { - PositionProfitState::Loss - } else { - signals.push(SignalKind::ClosePosition { - position_id: position.position_id(), - }); - PositionProfitState::Critical - } - }; - - let opt_pre_pw = status.position_previous_tick(position.position_id(), None); - let event_metadata = EventMetadata::new(Some(position.position_id()), None); - let new_position = position.clone().with_profit_state(Some(state)); - - match opt_pre_pw { - Some(prev) => { - if prev.profit_state() == Some(state) { - return (new_position, events, signals); - } - } - None => return (new_position, events, signals), - }; - - let events = { - let mut events = vec![]; - - if state == PositionProfitState::Profit { - events.push(Event::new( - EventKind::ReachedGoodProfit, - status.current_tick(), - Some(event_metadata), - )); - } else if state == PositionProfitState::MinimumProfit { - events.push(Event::new( - EventKind::ReachedMinProfit, - status.current_tick(), - Some(event_metadata), - )); - } else if state == PositionProfitState::BreakEven { - events.push(Event::new( - EventKind::ReachedBreakEven, - status.current_tick(), - Some(event_metadata), - )); - } else if state == PositionProfitState::Loss { - events.push(Event::new( - EventKind::ReachedLoss, - status.current_tick(), - Some(event_metadata), - )); - } else { - events.push(Event::new( - EventKind::ReachedMaxLoss, - status.current_tick(), - Some(event_metadata), - )); - } - - events - }; - - return (new_position, events, signals); - } -} +// #[derive(Clone, Debug)] +// pub struct TrailingStop { +// stop_percentages: HashMap, +// } +// +// impl TrailingStop { +// const BREAK_EVEN_PERC: f64 = 0.2; +// const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; +// const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; +// const MAX_LOSS_PERC: f64 = -1.7; +// +// const TAKER_FEE: f64 = 0.2; +// +// pub fn new() -> Self { +// TrailingStop { +// stop_percentages: HashMap::new(), +// } +// } +// +// fn net_pl_percentage(pl: f64, fee: f64) -> f64 { +// pl - fee +// } +// } +// +// impl PositionStrategy for TrailingStop { +// fn on_new_tick( +// &self, +// position: &Position, +// status: &PairStatus, +// ) -> (Position, Vec, Vec) { +// let mut signals = vec![]; +// let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); +// let events = vec![]; +// +// let state = { +// if pl_perc > TrailingStop::GOOD_PROFIT_PERC { +// PositionProfitState::Profit +// } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc +// && pl_perc < TrailingStop::GOOD_PROFIT_PERC +// { +// PositionProfitState::MinimumProfit +// } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { +// PositionProfitState::BreakEven +// } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { +// PositionProfitState::Loss +// } else { +// signals.push(SignalKind::ClosePosition { +// position_id: position.position_id(), +// }); +// PositionProfitState::Critical +// } +// }; +// +// let opt_pre_pw = status.position_previous_tick(position.position_id(), None); +// let event_metadata = EventMetadata::new(Some(position.position_id()), None); +// let new_position = position.clone().with_profit_state(Some(state)); +// +// match opt_pre_pw { +// Some(prev) => { +// if prev.profit_state() == Some(state) { +// return (new_position, events, signals); +// } +// } +// None => return (new_position, events, signals), +// }; +// +// let events = { +// let mut events = vec![]; +// +// if state == PositionProfitState::Profit { +// events.push(Event::new( +// EventKind::ReachedGoodProfit, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::MinimumProfit { +// events.push(Event::new( +// EventKind::ReachedMinProfit, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::BreakEven { +// events.push(Event::new( +// EventKind::ReachedBreakEven, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else if state == PositionProfitState::Loss { +// events.push(Event::new( +// EventKind::ReachedLoss, +// status.current_tick(), +// Some(event_metadata), +// )); +// } else { +// events.push(Event::new( +// EventKind::ReachedMaxLoss, +// status.current_tick(), +// Some(event_metadata), +// )); +// } +// +// events +// }; +// +// return (new_position, events, signals); +// } +// }