price manager working

This commit is contained in:
Giulio De Pasquale 2021-01-14 12:42:23 +00:00
parent b677cb880f
commit 2c2f164e18
8 changed files with 495 additions and 317 deletions

View File

@ -4,93 +4,107 @@ use std::collections::HashMap;
use bitfinex::api::Bitfinex; use bitfinex::api::Bitfinex;
use bitfinex::positions::Position; use bitfinex::positions::Position;
use bitfinex::ticker::TradingPairTicker; use bitfinex::ticker::TradingPairTicker;
use futures_util::stream::FuturesUnordered;
use tokio::time::delay_for; use tokio::time::delay_for;
use crate::connectors::Connector; use crate::connectors::{Client, Connector, ExchangeKind};
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::events::EventKind; use crate::events::{Event, EventKind};
use crate::pairs::PairStatus; use crate::managers::{OrderManager, PositionManager};
use crate::pairs::PriceManager;
use crate::strategy::PositionStrategy; use crate::strategy::PositionStrategy;
use crate::ticker::Ticker; use crate::ticker::Ticker;
use crate::BoxError; use crate::BoxError;
use tokio::stream::StreamExt;
pub struct BfxBot<'a> { pub struct BfxBot {
connector: Box<dyn Connector + 'a>,
ticker: Ticker, ticker: Ticker,
pair_statuses: Vec<PairStatus<'a>>,
quote: Symbol, quote: Symbol,
trading_symbols: Vec<Symbol>, trading_symbols: Vec<Symbol>,
price_managers: Vec<PriceManager>,
order_managers: Vec<OrderManager>,
pos_managers: Vec<PositionManager>,
} }
impl<'a> BfxBot<'a> { impl BfxBot {
pub fn new<C: Connector + 'a>( pub fn new(
connector: C, exchanges: Vec<ExchangeKind>,
trading_symbols: Vec<Symbol>, trading_symbols: Vec<Symbol>,
quote: Symbol, quote: Symbol,
tick_duration: Duration, tick_duration: Duration,
) -> Self { ) -> Self {
let pair_statuses = trading_symbols let clients: Vec<_> = exchanges.iter().map(|x| Client::new(x)).collect();
.iter()
.map(|x| SymbolPair::new(quote.clone(), x.clone())) let mut pos_managers = Vec::new();
.map(|x| PairStatus::new(x, 1, None)) let mut order_managers = Vec::new();
.collect(); 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 { BfxBot {
connector: Box::new(connector),
ticker: Ticker::new(tick_duration), ticker: Ticker::new(tick_duration),
pair_statuses, price_managers: pair_statuses,
quote, quote,
trading_symbols, 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> { 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 { loop {
self.update().await; self.update().await;
} }
} }
async fn update(&mut self) { async fn update<'a>(&'a mut self) {
delay_for(self.ticker.duration()).await; delay_for(self.ticker.duration()).await;
self.ticker.inc(); self.ticker.inc();
if let Err(e) = self.update_pair_statuses().await { self.update_price_managers().await.unwrap();
println!("Error while updating pairs: {}", e);
}
} }
async fn update_pair_statuses(&mut self) -> Result<(), BoxError> { async fn update_price_managers(&mut self) -> Result<Option<Vec<Event>>, BoxError> {
for status in &mut self.pair_statuses { let futures: Vec<_> = self
// add positions for each pair .price_managers
self.connector .clone()
.active_positions(status.pair()) .into_iter()
.await? // the only reason you need the async block is that the future
.into_iter() // returned by x.update(tick) borrows from x
.for_each(|x| status.add_position(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)
} }
} }

View File

@ -1,4 +1,5 @@
use std::convert::{TryFrom, TryInto}; use std::convert::{TryFrom, TryInto};
use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use bitfinex::api::Bitfinex; use bitfinex::api::Bitfinex;
@ -6,11 +7,11 @@ use bitfinex::orders::{OrderForm, OrderMeta};
use bitfinex::ticker::TradingPairTicker; use bitfinex::ticker::TradingPairTicker;
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::models::{Order, OrderKind, Position, PositionState}; use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker};
use crate::BoxError; 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 { pub enum ExchangeKind {
Bitfinex { Bitfinex {
api_key: String, 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. /// because it already uses an [`Arc`] internally.
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct Client { pub struct Client {
exchange: ExchangeKind,
inner: Arc<Box<dyn Connector>>, inner: Arc<Box<dyn Connector>>,
} }
impl Client { impl Client {
pub fn new(exchange: ExchangeKind) -> Self { pub fn new(exchange: &ExchangeKind) -> Self {
let inner = match exchange { let inner = match &exchange {
ExchangeKind::Bitfinex { ExchangeKind::Bitfinex {
api_key, api_key,
api_secret, api_secret,
affiliate_code, 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 { Client {
exchange: exchange.clone(),
inner: Arc::new(Box::new(inner)), 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] #[async_trait]
pub trait Connector { pub trait Connector: Send + Sync {
async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError>; async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError>;
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>; async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>;
@ -56,6 +82,16 @@ pub trait Connector {
) -> Result<(), BoxError>; ) -> Result<(), BoxError>;
} }
impl Debug for dyn Connector {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "Connector")
}
}
/**************
* BITFINEX
**************/
pub struct BitfinexConnector { pub struct BitfinexConnector {
bfx: Bitfinex, bfx: Bitfinex,
affiliate_code: Option<String>, affiliate_code: Option<String>,
@ -75,6 +111,57 @@ impl BitfinexConnector {
self.affiliate_code = affiliate_code; self.affiliate_code = affiliate_code;
self 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 { impl TryInto<Position> for bitfinex::positions::Position {
@ -124,41 +211,19 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind {
} }
} }
#[async_trait] impl From<TradingPairTicker> for PriceTicker {
impl Connector for BitfinexConnector { fn from(t: TradingPairTicker) -> Self {
async fn active_positions(&self, pair: &SymbolPair) -> Result<Vec<Position>, BoxError> { Self {
let active_positions = self.bfx.positions.active_positions().await?; bid: t.bid,
bid_size: t.bid_size,
Ok(active_positions ask: t.ask,
.into_iter() ask_size: t.ask_size,
.filter_map(|x| x.try_into().ok()) daily_change: t.daily_change,
.filter(|x: &Position| x.pair() == pair) daily_change_perc: t.daily_change_perc,
.collect()) last_price: t.last_price,
} volume: t.volume,
high: t.high,
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError> { low: t.low,
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?)
} }
} }

View File

@ -6,7 +6,7 @@ use tokio::task::JoinHandle;
use crate::bot::BfxBot; use crate::bot::BfxBot;
use crate::models::{Position, PositionProfitState}; use crate::models::{Position, PositionProfitState};
use crate::pairs::PairStatus; use crate::pairs::PriceManager;
use crate::BoxError; use crate::BoxError;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@ -78,12 +78,12 @@ impl Event {
} }
pub struct EventDispatcher { 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: 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<()>>>>, signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>,
on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PairStatus) -> JoinHandle<()>>>, on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>,
on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PairStatus) -> JoinHandle<()>>>, on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>,
} }
impl EventDispatcher { 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()) { if let Some(functions) = self.event_handlers.get(&event.kind()) {
for f in functions { for f in functions {
f(event, status); 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(profit_state) = &position.profit_state() {
if let Some(functions) = self.profit_state_handlers.get(profit_state) { if let Some(functions) = self.profit_state_handlers.get(profit_state) {
for f in functions { 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) pub fn register_event_handler<F: 'static, Fut: 'static>(&mut self, event: EventKind, f: F)
where where
F: Fn(&Event, &PairStatus) -> Fut, F: Fn(&Event, &PriceManager) -> Fut,
Fut: Future<Output = ()> + Send, Fut: Future<Output = ()> + Send,
{ {
match event { match event {
@ -153,7 +153,7 @@ impl EventDispatcher {
state: PositionProfitState, state: PositionProfitState,
f: F, f: F,
) where ) where
F: Fn(&Position, &PairStatus) -> Fut, F: Fn(&Position, &PriceManager) -> Fut,
Fut: Future<Output = ()> + Send, Fut: Future<Output = ()> + Send,
{ {
match state { match state {

View File

@ -1,11 +1,13 @@
#![feature(drain_filter)]
use std::thread::JoinHandle;
use tokio::time::{delay_for, Duration}; use tokio::time::{delay_for, Duration};
use crate::bot::BfxBot; use crate::bot::BfxBot;
use crate::connectors::BitfinexConnector; use crate::connectors::{BitfinexConnector, ExchangeKind};
use crate::currency::{Symbol, SymbolPair}; use crate::currency::{Symbol, SymbolPair};
use crate::events::SignalKind; use crate::events::SignalKind;
use crate::strategy::TrailingStop;
use std::thread::JoinHandle;
mod bot; mod bot;
mod connectors; mod connectors;
@ -23,16 +25,20 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
async fn main() -> Result<(), BoxError> { async fn main() -> Result<(), BoxError> {
let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2"; let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli"; let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
let bfx = BitfinexConnector::new(test_api_key, test_api_secret) let affiliate_code = "XPebOgHxA";
.with_affiliate_code(Some("XPebOgHxA".into()));
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( let mut bot = BfxBot::new(
bfx, vec![bitfinex],
vec![Symbol::TESTBTC], vec![Symbol::TESTBTC],
Symbol::TESTUSD, Symbol::TESTUSD,
Duration::new(20, 0), Duration::new(1, 0),
) );
.with_strategy(Box::new(TrailingStop::new()));
Ok(bot.start_loop().await?) Ok(bot.start_loop().await?)
} }

View File

@ -1,21 +1,58 @@
use std::collections::{HashMap, VecDeque};
use crate::connectors::{Client, Connector}; use crate::connectors::{Client, Connector};
use crate::events::Event; use crate::events::Event;
use crate::models::{Order, Position}; use crate::models::{Order, Position};
use crate::strategy::PositionStrategy;
use crate::ticker::Ticker; use crate::ticker::Ticker;
use std::collections::{HashMap, VecDeque};
struct EventManager { pub struct EventManager {
events: Vec<Event>, events: Vec<Event>,
} }
struct PositionManager { pub struct PositionManager {
queue: VecDeque<Position>, queue: VecDeque<Position>,
open_positions: Vec<Position>, open_positions: Vec<Position>,
client: Client, 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>, queue: VecDeque<Order>,
open_orders: Vec<Order>, open_orders: Vec<Order>,
client: Client, 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!()
}
}

View File

@ -1,5 +1,28 @@
use crate::currency::SymbolPair; 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 struct Order {
pub id: i64, pub id: i64,
pub group_id: Option<i32>, pub group_id: Option<i32>,
@ -22,6 +45,7 @@ pub struct Order {
pub placed_id: Option<i32>, pub placed_id: Option<i32>,
} }
#[derive(Copy, Clone, Debug)]
pub enum OrderKind { pub enum OrderKind {
Limit, Limit,
ExchangeLimit, ExchangeLimit,
@ -38,6 +62,10 @@ pub enum OrderKind {
ExchangeIoc, ExchangeIoc,
} }
/***************
* Positions
***************/
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Position { pub struct Position {
pair: SymbolPair, pair: SymbolPair,

View File

@ -1,117 +1,145 @@
use std::collections::HashMap; use std::collections::HashMap;
// use crate::strategy::PositionStrategy;
use crate::connectors::Client;
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::events::{Event, EventDispatcher, SignalKind}; use crate::events::{Event, EventDispatcher, SignalKind};
use crate::models::{Order, Position}; use crate::models::{Order, Position, PriceTicker};
use crate::strategy::PositionStrategy; use crate::BoxError;
pub struct PairStatus<'a> { #[derive(Clone, Debug)]
pub struct PriceManager {
pair: SymbolPair, pair: SymbolPair,
dispatcher: EventDispatcher, prices: Vec<PriceEntry>,
prices: HashMap<u64, f64>, client: Client,
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>,
} }
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( pub fn new(
tick: u64,
price: PriceTicker,
pair: SymbolPair, pair: SymbolPair,
current_tick: u64, events: Option<Vec<Event>>,
strategy: Option<Box<dyn PositionStrategy>>, signals: Option<Vec<SignalKind>>,
) -> Self { ) -> Self {
PairStatus { PriceEntry {
tick,
pair, pair,
dispatcher: EventDispatcher::new(), price,
prices: HashMap::new(), events,
events: Vec::new(), signals,
positions: HashMap::new(), }
orders: HashMap::new(), }
signals: HashMap::new(), }
current_tick,
strategy, impl PriceManager {
pub fn new(pair: SymbolPair, client: Client) -> Self {
PriceManager {
pair,
prices: Vec::new(),
client,
} }
} }
pub fn dispatcher(&self) -> &EventDispatcher { pub fn add_entry(&mut self, entry: PriceEntry) {
&self.dispatcher self.prices.push(entry);
}
pub fn dispatcher_mut(&mut self) -> &mut EventDispatcher {
&mut self.dispatcher
} }
pub fn add_position(&mut self, position: Position) { pub async fn update(&mut self, tick: u64) -> Result<PriceEntry, BoxError> {
let (new_position, events, signals) = { let current_prices = self.client.current_prices(&self.pair).await?.into();
match &self.strategy {
Some(strategy) => strategy.on_new_tick(&position, &self),
None => (position, vec![], vec![]),
}
};
self.positions Ok(PriceEntry::new(
.entry(self.current_tick) tick,
.or_default() current_prices,
.push(new_position.clone()); self.pair.clone(),
None,
// calling position state callbacks None,
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
);
} }
fn add_event(&mut self, event: Event) { // pub fn add_position(&mut self, position: Position) {
self.events.push(event); // 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_event(&mut self, event: Event) {
} // self.events.push(event);
//
fn add_signal(&mut self, signal: SignalKind) { // self.dispatcher.call_event_handlers(&event, &self);
self.signals.insert(self.current_tick(), signal); // }
} //
// fn add_signal(&mut self, signal: SignalKind) {
pub fn current_tick(&self) -> u64 { // self.signals.insert(self.current_tick(), signal);
self.current_tick // }
}
pub fn set_strategy(&mut self, strategy: Box<dyn PositionStrategy + 'a>) {
self.strategy = Some(strategy);
}
pub fn pair(&self) -> &SymbolPair { pub fn pair(&self) -> &SymbolPair {
&self.pair &self.pair
} }
pub fn position_previous_tick(&self, id: u64, tick: Option<u64>) -> Option<&Position> { // pub fn position_previous_tick(&self, id: u64, tick: Option<u64>) -> Option<&Position> {
let tick = match tick { // let tick = match tick {
Some(tick) => { // Some(tick) => {
if tick < 1 { // if tick < 1 {
1 // 1
} else { // } else {
tick // tick
} // }
} // }
None => self.current_tick() - 1, // None => self.current_tick() - 1,
}; // };
//
self.positions // self.positions
.get(&tick) // .get(&tick)
.and_then(|x| x.iter().find(|x| x.position_id() == id)) // .and_then(|x| x.iter().find(|x| x.position_id() == id))
} // }
} }

View File

@ -2,121 +2,121 @@ use std::collections::HashMap;
use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::events::{Event, EventKind, EventMetadata, SignalKind};
use crate::models::{Position, PositionProfitState}; use crate::models::{Position, PositionProfitState};
use crate::pairs::PairStatus; use crate::pairs::PriceManager;
use dyn_clone::DynClone; use dyn_clone::DynClone;
pub trait PositionStrategy: DynClone { pub trait PositionStrategy: DynClone {
fn on_new_tick( fn on_new_tick(
&self, &self,
position: &Position, position: &Position,
status: &PairStatus, status: &PriceManager,
) -> (Position, Vec<Event>, Vec<SignalKind>); ) -> (Position, Vec<Event>, Vec<SignalKind>);
} }
#[derive(Clone, Debug)] // #[derive(Clone, Debug)]
pub struct TrailingStop { // pub struct TrailingStop {
stop_percentages: HashMap<u64, f64>, // stop_percentages: HashMap<u64, f64>,
} // }
//
impl TrailingStop { // impl TrailingStop {
const BREAK_EVEN_PERC: f64 = 0.2; // const BREAK_EVEN_PERC: f64 = 0.2;
const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3; // const MIN_PROFIT_PERC: f64 = TrailingStop::BREAK_EVEN_PERC + 0.3;
const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5; // const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 2.5;
const MAX_LOSS_PERC: f64 = -1.7; // const MAX_LOSS_PERC: f64 = -1.7;
//
const TAKER_FEE: f64 = 0.2; // const TAKER_FEE: f64 = 0.2;
//
pub fn new() -> Self { // pub fn new() -> Self {
TrailingStop { // TrailingStop {
stop_percentages: HashMap::new(), // stop_percentages: HashMap::new(),
} // }
} // }
//
fn net_pl_percentage(pl: f64, fee: f64) -> f64 { // fn net_pl_percentage(pl: f64, fee: f64) -> f64 {
pl - fee // pl - fee
} // }
} // }
//
impl PositionStrategy for TrailingStop { // impl PositionStrategy for TrailingStop {
fn on_new_tick( // fn on_new_tick(
&self, // &self,
position: &Position, // position: &Position,
status: &PairStatus, // status: &PairStatus,
) -> (Position, Vec<Event>, Vec<SignalKind>) { // ) -> (Position, Vec<Event>, Vec<SignalKind>) {
let mut signals = vec![]; // let mut signals = vec![];
let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE); // let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE);
let events = vec![]; // let events = vec![];
//
let state = { // let state = {
if pl_perc > TrailingStop::GOOD_PROFIT_PERC { // if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
PositionProfitState::Profit // PositionProfitState::Profit
} else if TrailingStop::MIN_PROFIT_PERC <= pl_perc // } else if TrailingStop::MIN_PROFIT_PERC <= pl_perc
&& pl_perc < TrailingStop::GOOD_PROFIT_PERC // && pl_perc < TrailingStop::GOOD_PROFIT_PERC
{ // {
PositionProfitState::MinimumProfit // PositionProfitState::MinimumProfit
} else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC { // } else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC {
PositionProfitState::BreakEven // PositionProfitState::BreakEven
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 { // } else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
PositionProfitState::Loss // PositionProfitState::Loss
} else { // } else {
signals.push(SignalKind::ClosePosition { // signals.push(SignalKind::ClosePosition {
position_id: position.position_id(), // position_id: position.position_id(),
}); // });
PositionProfitState::Critical // PositionProfitState::Critical
} // }
}; // };
//
let opt_pre_pw = status.position_previous_tick(position.position_id(), None); // let opt_pre_pw = status.position_previous_tick(position.position_id(), None);
let event_metadata = EventMetadata::new(Some(position.position_id()), None); // let event_metadata = EventMetadata::new(Some(position.position_id()), None);
let new_position = position.clone().with_profit_state(Some(state)); // let new_position = position.clone().with_profit_state(Some(state));
//
match opt_pre_pw { // match opt_pre_pw {
Some(prev) => { // Some(prev) => {
if prev.profit_state() == Some(state) { // if prev.profit_state() == Some(state) {
return (new_position, events, signals); // return (new_position, events, signals);
} // }
} // }
None => return (new_position, events, signals), // None => return (new_position, events, signals),
}; // };
//
let events = { // let events = {
let mut events = vec![]; // let mut events = vec![];
//
if state == PositionProfitState::Profit { // if state == PositionProfitState::Profit {
events.push(Event::new( // events.push(Event::new(
EventKind::ReachedGoodProfit, // EventKind::ReachedGoodProfit,
status.current_tick(), // status.current_tick(),
Some(event_metadata), // Some(event_metadata),
)); // ));
} else if state == PositionProfitState::MinimumProfit { // } else if state == PositionProfitState::MinimumProfit {
events.push(Event::new( // events.push(Event::new(
EventKind::ReachedMinProfit, // EventKind::ReachedMinProfit,
status.current_tick(), // status.current_tick(),
Some(event_metadata), // Some(event_metadata),
)); // ));
} else if state == PositionProfitState::BreakEven { // } else if state == PositionProfitState::BreakEven {
events.push(Event::new( // events.push(Event::new(
EventKind::ReachedBreakEven, // EventKind::ReachedBreakEven,
status.current_tick(), // status.current_tick(),
Some(event_metadata), // Some(event_metadata),
)); // ));
} else if state == PositionProfitState::Loss { // } else if state == PositionProfitState::Loss {
events.push(Event::new( // events.push(Event::new(
EventKind::ReachedLoss, // EventKind::ReachedLoss,
status.current_tick(), // status.current_tick(),
Some(event_metadata), // Some(event_metadata),
)); // ));
} else { // } else {
events.push(Event::new( // events.push(Event::new(
EventKind::ReachedMaxLoss, // EventKind::ReachedMaxLoss,
status.current_tick(), // status.current_tick(),
Some(event_metadata), // Some(event_metadata),
)); // ));
} // }
//
events // events
}; // };
//
return (new_position, events, signals); // return (new_position, events, signals);
} // }
} // }