rust #10
@ -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);
|
||||
|
@ -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?)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user