core/rustybot/src/strategy.rs
2021-01-22 15:37:53 +00:00

203 lines
6.1 KiB
Rust

use dyn_clone::DynClone;
use log::debug;
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use crate::events::{Event, EventKind, EventMetadata, Message};
use crate::managers::{OrderManager, PositionManager, TrackedPositionsMap};
use crate::models::{ActiveOrder, OrderForm, Position, PositionProfitState};
use tokio::sync::oneshot;
/***************
* DEFINITIONS
***************/
pub trait PositionStrategy: DynClone + Send {
fn name(&self) -> String;
fn on_new_tick(
&self,
position: Position,
manager: &PositionManager,
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
}
impl Debug for dyn PositionStrategy {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
pub trait OrderStrategy: DynClone + Send {
/// The name of the strategy, used for debugging purposes
fn name(&self) -> String;
/// This method is called when the OrderManager checks the open orders on a new tick.
/// It should manage if some orders have to be closed or keep open.
fn on_update(&self);
/// This method is called when the OrderManager is requested to close
/// a position that has an open order associated to it.
fn on_position_close(
&self,
order: &ActiveOrder,
tracked_positions: &HashMap<u64, ActiveOrder>,
) -> TrackedPositionsMap;
}
impl Debug for dyn OrderStrategy {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
/***************
* IMPLEMENTATIONS
***************/
#[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 = -0.01;
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 name(&self) -> String {
"Trailing stop".into()
}
fn on_new_tick(
&self,
position: Position,
manager: &PositionManager,
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>) {
let mut messages = vec![];
let events = vec![];
let pl_perc = TrailingStop::net_pl_percentage(position.pl_perc(), TrailingStop::TAKER_FEE);
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 {
debug!("Inserting close position message...");
messages.push(Message::ClosePosition {
position: position.clone(),
});
PositionProfitState::Critical
}
};
let opt_pre_pw = manager.position_previous_tick(position.id(), None);
let event_metadata = EventMetadata::new(Some(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.is_empty()).then_some(events),
(!messages.is_empty()).then_some(messages),
);
}
}
None => {
return (
new_position,
(!events.is_empty()).then_some(events),
(!messages.is_empty()).then_some(messages),
)
}
};
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.is_empty()).then_some(events),
(!messages.is_empty()).then_some(messages),
);
}
}
#[derive(Clone, Debug)]
pub struct FastOrderStrategy {}
impl OrderStrategy for FastOrderStrategy {
fn name(&self) -> String {
"Fast order strategy".into()
}
fn on_update(&self) {
unimplemented!()
}
fn on_position_close(
&self,
order: &ActiveOrder,
tracked_positions: &HashMap<u64, ActiveOrder>,
) -> TrackedPositionsMap {
unimplemented!()
}
}