rust #10
| @ -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<dyn Connector + 'a>, | ||||
| pub struct BfxBot { | ||||
|     ticker: Ticker, | ||||
|     pair_statuses: Vec<PairStatus<'a>>, | ||||
|     quote: Symbol, | ||||
|     trading_symbols: Vec<Symbol>, | ||||
|     price_managers: Vec<PriceManager>, | ||||
|     order_managers: Vec<OrderManager>, | ||||
|     pos_managers: Vec<PositionManager>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> BfxBot<'a> { | ||||
|     pub fn new<C: Connector + 'a>( | ||||
|         connector: C, | ||||
| impl BfxBot { | ||||
|     pub fn new( | ||||
|         exchanges: Vec<ExchangeKind>, | ||||
|         trading_symbols: Vec<Symbol>, | ||||
|         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::<Vec<PriceManager>>(), | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         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<dyn PositionStrategy>) -> 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<TradingPairTicker, BoxError> { | ||||
|         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<Option<Vec<Event>>, 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) | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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<Box<dyn Connector>>, | ||||
| } | ||||
| 
 | ||||
| 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<Vec<Position>, BoxError> { | ||||
|         self.inner.active_positions(pair).await | ||||
|     } | ||||
| 
 | ||||
|     pub async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> { | ||||
|         self.inner.current_prices(pair).await | ||||
|     } | ||||
| 
 | ||||
|     pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, 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<Vec<Position>, BoxError>; | ||||
|     async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>; | ||||
|     async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, 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<String>, | ||||
| @ -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<Vec<Position>, 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<TradingPairTicker, BoxError> { | ||||
|         let ticker: TradingPairTicker = self | ||||
|             .bfx | ||||
|             .ticker | ||||
|             .trading_pair(self.format_trading_pair(pair)) | ||||
|             .await?; | ||||
| 
 | ||||
|         Ok(ticker) | ||||
|     } | ||||
| 
 | ||||
|     async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, 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<Position> 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<Vec<Position>, 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<TradingPairTicker, BoxError> { | ||||
|         let ticker: TradingPairTicker = self.bfx.ticker.trading_pair(pair.clone()).await?; | ||||
| 
 | ||||
|         Ok(ticker) | ||||
|     } | ||||
| 
 | ||||
|     async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, 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<TradingPairTicker> 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, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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<EventKind, Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>>, | ||||
|     event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>, | ||||
|     profit_state_handlers: | ||||
|         HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PairStatus) -> JoinHandle<()>>>>, | ||||
|         HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>>, | ||||
|     signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>, | ||||
|     on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>, | ||||
|     on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PairStatus) -> JoinHandle<()>>>, | ||||
|     on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>, | ||||
|     on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> 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<F: 'static, Fut: 'static>(&mut self, event: EventKind, f: F) | ||||
|     where | ||||
|         F: Fn(&Event, &PairStatus) -> Fut, | ||||
|         F: Fn(&Event, &PriceManager) -> Fut, | ||||
|         Fut: Future<Output = ()> + 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<Output = ()> + Send, | ||||
|     { | ||||
|         match state { | ||||
|  | ||||
| @ -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<dyn std::error::Error + Send + Sync>; | ||||
| 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?) | ||||
| } | ||||
|  | ||||
| @ -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<Event>, | ||||
| } | ||||
| 
 | ||||
| struct PositionManager { | ||||
| pub struct PositionManager { | ||||
|     queue: VecDeque<Position>, | ||||
|     open_positions: Vec<Position>, | ||||
|     client: Client, | ||||
|     strategy: Option<Box<dyn PositionStrategy>>, | ||||
| } | ||||
| 
 | ||||
| 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<dyn PositionStrategy>) -> Self { | ||||
|         self.strategy = Some(strategy); | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     pub fn update(&self) -> Option<Vec<Event>> { | ||||
|         unimplemented!() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub struct OrderManager { | ||||
|     queue: VecDeque<Order>, | ||||
|     open_orders: Vec<Order>, | ||||
|     client: Client, | ||||
| } | ||||
| 
 | ||||
| impl OrderManager { | ||||
|     pub fn new(client: Client) -> Self { | ||||
|         OrderManager { | ||||
|             queue: VecDeque::new(), | ||||
|             open_orders: Vec::new(), | ||||
|             client, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn update(&self) -> Option<Vec<Event>> { | ||||
|         unimplemented!() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -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<i32>, | ||||
| @ -22,6 +45,7 @@ pub struct Order { | ||||
|     pub placed_id: Option<i32>, | ||||
| } | ||||
| 
 | ||||
| #[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, | ||||
|  | ||||
| @ -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<u64, f64>, | ||||
|     events: Vec<Event>, | ||||
|     orders: HashMap<u64, Vec<Order>>, | ||||
|     positions: HashMap<u64, Vec<Position>>, | ||||
|     current_tick: u64, | ||||
|     strategy: Option<Box<dyn PositionStrategy + 'a>>, | ||||
|     signals: HashMap<u64, SignalKind>, | ||||
|     prices: Vec<PriceEntry>, | ||||
|     client: Client, | ||||
| } | ||||
| 
 | ||||
| impl<'a> PairStatus<'a> { | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct PriceEntry { | ||||
|     tick: u64, | ||||
|     pair: SymbolPair, | ||||
|     price: PriceTicker, | ||||
|     events: Option<Vec<Event>>, | ||||
|     signals: Option<Vec<SignalKind>>, | ||||
| } | ||||
| 
 | ||||
| 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<Vec<Event>> { | ||||
|         &self.events | ||||
|     } | ||||
|     pub fn signals(&self) -> &Option<Vec<SignalKind>> { | ||||
|         &self.signals | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl PriceEntry { | ||||
|     pub fn new( | ||||
|         tick: u64, | ||||
|         price: PriceTicker, | ||||
|         pair: SymbolPair, | ||||
|         current_tick: u64, | ||||
|         strategy: Option<Box<dyn PositionStrategy>>, | ||||
|         events: Option<Vec<Event>>, | ||||
|         signals: Option<Vec<SignalKind>>, | ||||
|     ) -> 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<PriceEntry, BoxError> { | ||||
|         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<dyn PositionStrategy + 'a>) { | ||||
|         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<u64>) -> 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<u64>) -> 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))
 | ||||
|     // }
 | ||||
| } | ||||
|  | ||||
| @ -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<Event>, Vec<SignalKind>); | ||||
| } | ||||
| 
 | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct TrailingStop { | ||||
|     stop_percentages: HashMap<u64, f64>, | ||||
| } | ||||
| 
 | ||||
| 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<Event>, Vec<SignalKind>) { | ||||
|         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<u64, f64>,
 | ||||
| // }
 | ||||
| //
 | ||||
| // 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<Event>, Vec<SignalKind>) {
 | ||||
| //         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);
 | ||||
| //     }
 | ||||
| // }
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user