203 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			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!()
 | 
						|
    }
 | 
						|
}
 |