rust #10

Merged
peperunas merged 127 commits from rust into master 2021-02-18 09:42:16 +00:00
4 changed files with 153 additions and 129 deletions
Showing only changes of commit 2db59942eb - Show all commits

View File

@ -1,12 +1,13 @@
use core::time::Duration;
use log::{error, info};
use log::{debug, error, info};
use tokio::time::delay_for;
use crate::connectors::{Client, ExchangeKind};
use crate::currency::{Symbol, SymbolPair};
use crate::events::Event;
use crate::managers::{OrderManager, PositionManager, PriceManager};
use crate::strategy::PositionStrategy;
use crate::ticker::Ticker;
use crate::BoxError;
@ -54,6 +55,16 @@ impl BfxBot {
}
}
pub fn with_position_strategy(mut self, strategy: Box<dyn PositionStrategy>) -> Self {
self.pos_managers = self
.pos_managers
.into_iter()
.map(|x| x.with_strategy(dyn_clone::clone_box(&*strategy)))
.collect();
self
}
pub async fn start_loop(&mut self) -> Result<(), BoxError> {
if let Err(e) = self.update_managers().await {
error!("Error while starting managers: {}", e);

View File

@ -7,6 +7,7 @@ use tokio::time::Duration;
use crate::bot::BfxBot;
use crate::connectors::ExchangeKind;
use crate::currency::Symbol;
use crate::strategy::TrailingStop;
mod bot;
mod connectors;
@ -38,7 +39,8 @@ async fn main() -> Result<(), BoxError> {
vec![Symbol::TESTBTC],
Symbol::TESTUSD,
Duration::new(1, 0),
);
)
.with_position_strategy(Box::new(TrailingStop::new()));
Ok(bot.start_loop().await?)
}

View File

@ -84,23 +84,6 @@ impl PriceManager {
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))
// }
}
#[derive(Clone, Debug)]
@ -148,6 +131,7 @@ impl PriceEntry {
#[derive(Debug)]
pub struct PositionManager {
current_tick: u64,
pair: SymbolPair,
positions_history: HashMap<u64, Position>,
active_position: Option<Position>,
@ -158,6 +142,7 @@ pub struct PositionManager {
impl PositionManager {
pub fn new(pair: SymbolPair, client: Client) -> Self {
PositionManager {
current_tick: 0,
pair,
positions_history: HashMap::new(),
active_position: None,
@ -171,10 +156,16 @@ impl PositionManager {
self
}
pub fn current_tick(&self) -> u64 {
self.current_tick
}
pub async fn update(&mut self, tick: u64) -> Result<Option<Vec<Event>>, BoxError> {
let opt_active_positions = self.client.active_positions(&self.pair).await?;
let mut events = vec![];
self.current_tick = tick;
if opt_active_positions.is_none() {
return Ok(None);
}
@ -200,7 +191,8 @@ impl PositionManager {
}
};
self.positions_history.insert(tick, active_position.clone());
self.positions_history
.insert(self.current_tick(), active_position.clone());
self.active_position = Some(active_position);
}
None => {
@ -214,6 +206,24 @@ impl PositionManager {
Ok(Some(events))
}
}
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_history
.get(&tick)
.filter(|x| x.position_id() == id)
.and_then(|x| Some(x))
}
}
pub struct OrderManager {

View File

@ -1,10 +1,11 @@
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use dyn_clone::DynClone;
use crate::events::{Event, SignalKind};
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
use crate::managers::PositionManager;
use crate::models::Position;
use crate::models::{Position, PositionProfitState};
pub trait PositionStrategy: DynClone {
fn on_new_tick(
@ -20,110 +21,110 @@ impl Debug for dyn PositionStrategy {
}
}
// #[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,
manager: &PositionManager,
) -> (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 = manager.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,
manager.current_tick(),
Some(event_metadata),
));
} else if state == PositionProfitState::MinimumProfit {
events.push(Event::new(
EventKind::ReachedMinProfit,
manager.current_tick(),
Some(event_metadata),
));
} else if state == PositionProfitState::BreakEven {
events.push(Event::new(
EventKind::ReachedBreakEven,
manager.current_tick(),
Some(event_metadata),
));
} else if state == PositionProfitState::Loss {
events.push(Event::new(
EventKind::ReachedLoss,
manager.current_tick(),
Some(event_metadata),
));
} else {
events.push(Event::new(
EventKind::ReachedMaxLoss,
manager.current_tick(),
Some(event_metadata),
));
}
events
};
return (new_position, events, signals);
}
}