position strategy working
This commit is contained in:
parent
c5b4aba548
commit
2db59942eb
@ -1,12 +1,13 @@
|
|||||||
use core::time::Duration;
|
use core::time::Duration;
|
||||||
|
|
||||||
use log::{error, info};
|
use log::{debug, error, info};
|
||||||
use tokio::time::delay_for;
|
use tokio::time::delay_for;
|
||||||
|
|
||||||
use crate::connectors::{Client, ExchangeKind};
|
use crate::connectors::{Client, ExchangeKind};
|
||||||
use crate::currency::{Symbol, SymbolPair};
|
use crate::currency::{Symbol, SymbolPair};
|
||||||
use crate::events::Event;
|
use crate::events::Event;
|
||||||
use crate::managers::{OrderManager, PositionManager, PriceManager};
|
use crate::managers::{OrderManager, PositionManager, PriceManager};
|
||||||
|
use crate::strategy::PositionStrategy;
|
||||||
use crate::ticker::Ticker;
|
use crate::ticker::Ticker;
|
||||||
use crate::BoxError;
|
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> {
|
pub async fn start_loop(&mut self) -> Result<(), BoxError> {
|
||||||
if let Err(e) = self.update_managers().await {
|
if let Err(e) = self.update_managers().await {
|
||||||
error!("Error while starting managers: {}", e);
|
error!("Error while starting managers: {}", e);
|
||||||
|
@ -7,6 +7,7 @@ use tokio::time::Duration;
|
|||||||
use crate::bot::BfxBot;
|
use crate::bot::BfxBot;
|
||||||
use crate::connectors::ExchangeKind;
|
use crate::connectors::ExchangeKind;
|
||||||
use crate::currency::Symbol;
|
use crate::currency::Symbol;
|
||||||
|
use crate::strategy::TrailingStop;
|
||||||
|
|
||||||
mod bot;
|
mod bot;
|
||||||
mod connectors;
|
mod connectors;
|
||||||
@ -38,7 +39,8 @@ async fn main() -> Result<(), BoxError> {
|
|||||||
vec![Symbol::TESTBTC],
|
vec![Symbol::TESTBTC],
|
||||||
Symbol::TESTUSD,
|
Symbol::TESTUSD,
|
||||||
Duration::new(1, 0),
|
Duration::new(1, 0),
|
||||||
);
|
)
|
||||||
|
.with_position_strategy(Box::new(TrailingStop::new()));
|
||||||
|
|
||||||
Ok(bot.start_loop().await?)
|
Ok(bot.start_loop().await?)
|
||||||
}
|
}
|
||||||
|
@ -84,23 +84,6 @@ impl PriceManager {
|
|||||||
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> {
|
|
||||||
// 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)]
|
#[derive(Clone, Debug)]
|
||||||
@ -148,6 +131,7 @@ impl PriceEntry {
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PositionManager {
|
pub struct PositionManager {
|
||||||
|
current_tick: u64,
|
||||||
pair: SymbolPair,
|
pair: SymbolPair,
|
||||||
positions_history: HashMap<u64, Position>,
|
positions_history: HashMap<u64, Position>,
|
||||||
active_position: Option<Position>,
|
active_position: Option<Position>,
|
||||||
@ -158,6 +142,7 @@ pub struct PositionManager {
|
|||||||
impl PositionManager {
|
impl PositionManager {
|
||||||
pub fn new(pair: SymbolPair, client: Client) -> Self {
|
pub fn new(pair: SymbolPair, client: Client) -> Self {
|
||||||
PositionManager {
|
PositionManager {
|
||||||
|
current_tick: 0,
|
||||||
pair,
|
pair,
|
||||||
positions_history: HashMap::new(),
|
positions_history: HashMap::new(),
|
||||||
active_position: None,
|
active_position: None,
|
||||||
@ -171,10 +156,16 @@ impl PositionManager {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_tick(&self) -> u64 {
|
||||||
|
self.current_tick
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn update(&mut self, tick: u64) -> Result<Option<Vec<Event>>, BoxError> {
|
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 opt_active_positions = self.client.active_positions(&self.pair).await?;
|
||||||
let mut events = vec![];
|
let mut events = vec![];
|
||||||
|
|
||||||
|
self.current_tick = tick;
|
||||||
|
|
||||||
if opt_active_positions.is_none() {
|
if opt_active_positions.is_none() {
|
||||||
return Ok(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);
|
self.active_position = Some(active_position);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@ -214,6 +206,24 @@ impl PositionManager {
|
|||||||
Ok(Some(events))
|
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 {
|
pub struct OrderManager {
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
|
|
||||||
use crate::events::{Event, SignalKind};
|
use crate::events::{Event, EventKind, EventMetadata, SignalKind};
|
||||||
use crate::managers::PositionManager;
|
use crate::managers::PositionManager;
|
||||||
use crate::models::Position;
|
use crate::models::{Position, PositionProfitState};
|
||||||
|
|
||||||
pub trait PositionStrategy: DynClone {
|
pub trait PositionStrategy: DynClone {
|
||||||
fn on_new_tick(
|
fn on_new_tick(
|
||||||
@ -20,110 +21,110 @@ impl Debug for dyn PositionStrategy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[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,
|
manager: &PositionManager,
|
||||||
// ) -> (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 = manager.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(),
|
manager.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(),
|
manager.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(),
|
manager.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(),
|
manager.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(),
|
manager.current_tick(),
|
||||||
// Some(event_metadata),
|
Some(event_metadata),
|
||||||
// ));
|
));
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// events
|
events
|
||||||
// };
|
};
|
||||||
//
|
|
||||||
// return (new_position, events, signals);
|
return (new_position, events, signals);
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user