This commit is contained in:
Giulio De Pasquale 2021-01-16 11:43:16 +00:00
parent 0d48d3768a
commit dfd676612e
7 changed files with 271 additions and 102 deletions

1
rustybot/Cargo.lock generated
View File

@ -1128,6 +1128,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bitfinex", "bitfinex",
"byteorder",
"chrono", "chrono",
"dyn-clone", "dyn-clone",
"fern", "fern",

View File

@ -16,4 +16,5 @@ regex = "1"
dyn-clone = "1" dyn-clone = "1"
log = "0.4" log = "0.4"
fern = {version = "0.6", features = ["colored"]} fern = {version = "0.6", features = ["colored"]}
chrono = "0.4" chrono = "0.4"
byteorder = "1"

View File

@ -4,11 +4,11 @@ use std::sync::Arc;
use async_trait::async_trait; use async_trait::async_trait;
use bitfinex::api::Bitfinex; use bitfinex::api::Bitfinex;
use bitfinex::orders::{OrderForm, OrderMeta}; use bitfinex::orders::{ActiveOrder, OrderMeta};
use bitfinex::ticker::TradingPairTicker; use bitfinex::ticker::TradingPairTicker;
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::models::{Order, OrderKind, Position, PositionState, PriceTicker}; use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PositionState, PriceTicker};
use crate::BoxError; use crate::BoxError;
#[derive(Eq, PartialEq, Hash, Clone, Debug)] #[derive(Eq, PartialEq, Hash, Clone, Debug)]
@ -50,18 +50,12 @@ impl Client {
self.inner.current_prices(pair).await self.inner.current_prices(pair).await
} }
pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> { pub async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError> {
self.inner.active_orders(pair).await self.inner.active_orders(pair).await
} }
pub async fn submit_order( pub async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError> {
&self, self.inner.submit_order(order).await
pair: &SymbolPair,
amount: f64,
price: f64,
kind: &OrderKind,
) -> Result<(), BoxError> {
self.inner.submit_order(pair, amount, price, kind).await
} }
} }
@ -70,14 +64,8 @@ pub trait Connector: Send + Sync {
fn name(&self) -> String; fn name(&self) -> String;
async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError>; async fn active_positions(&self, pair: &SymbolPair) -> Result<Option<Vec<Position>>, BoxError>;
async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>; async fn current_prices(&self, pair: &SymbolPair) -> Result<TradingPairTicker, BoxError>;
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError>; async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError>;
async fn submit_order( async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError>;
&self,
pair: &SymbolPair,
amount: f64,
price: f64,
kind: &OrderKind,
) -> Result<(), BoxError>;
} }
impl Debug for dyn Connector { impl Debug for dyn Connector {
@ -148,24 +136,28 @@ impl Connector for BitfinexConnector {
Ok(ticker) Ok(ticker)
} }
async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<Order>, BoxError> { async fn active_orders(&self, pair: &SymbolPair) -> Result<Vec<ExecutedOrder>, BoxError> {
unimplemented!() unimplemented!()
} }
async fn submit_order( async fn submit_order(&self, order: OrderForm) -> Result<ExecutedOrder, BoxError> {
&self,
pair: &SymbolPair,
amount: f64,
price: f64,
kind: &OrderKind,
) -> Result<(), BoxError> {
let order_form = match &self.affiliate_code { let order_form = match &self.affiliate_code {
Some(affiliate_code) => OrderForm::new(pair.trading_repr(), price, amount, kind.into()) Some(affiliate_code) => bitfinex::orders::OrderForm::new(
.with_meta(OrderMeta::new(affiliate_code.clone())), order.pair().trading_repr(),
None => OrderForm::new(pair.trading_repr(), price, amount, kind.into()), *order.price(),
*order.amount(),
order.kind().into(),
)
.with_meta(OrderMeta::new(affiliate_code.clone())),
None => bitfinex::orders::OrderForm::new(
order.pair().trading_repr(),
*order.price(),
*order.amount(),
order.kind().into(),
),
}; };
Ok(self.bfx.orders.submit_order(&order_form).await?) Ok(self.bfx.orders.submit_order(&order_form).await?.into())
} }
} }
@ -216,6 +208,26 @@ impl From<&OrderKind> for bitfinex::orders::OrderKind {
} }
} }
impl From<OrderKind> for bitfinex::orders::OrderKind {
fn from(o: OrderKind) -> Self {
match o {
OrderKind::Limit => Self::Limit,
OrderKind::ExchangeLimit => Self::ExchangeLimit,
OrderKind::Market => Self::Market,
OrderKind::ExchangeMarket => Self::ExchangeMarket,
OrderKind::Stop => Self::Stop,
OrderKind::ExchangeStop => Self::ExchangeStop,
OrderKind::StopLimit => Self::StopLimit,
OrderKind::ExchangeStopLimit => Self::ExchangeStopLimit,
OrderKind::TrailingStop => Self::TrailingStop,
OrderKind::Fok => Self::Fok,
OrderKind::ExchangeFok => Self::ExchangeFok,
OrderKind::Ioc => Self::Ioc,
OrderKind::ExchangeIoc => Self::ExchangeIoc,
}
}
}
impl From<TradingPairTicker> for PriceTicker { impl From<TradingPairTicker> for PriceTicker {
fn from(t: TradingPairTicker) -> Self { fn from(t: TradingPairTicker) -> Self {
Self { Self {
@ -232,3 +244,29 @@ impl From<TradingPairTicker> for PriceTicker {
} }
} }
} }
impl From<bitfinex::orders::ActiveOrder> for ExecutedOrder {
fn from(o: ActiveOrder) -> Self {
Self {
id: o.id,
group_id: o.group_id,
client_id: o.client_id,
symbol: o.symbol,
creation_timestamp: o.creation_timestamp,
update_timestamp: o.update_timestamp,
amount: o.amount,
amount_original: o.amount_original,
order_type: o.order_type,
previous_order_type: o.previous_order_type,
flags: o.flags,
order_status: o.order_status,
price: o.price,
price_avg: o.price_avg,
price_trailing: o.price_trailing,
price_aux_limit: o.price_aux_limit,
notify: o.notify,
hidden: o.hidden,
placed_id: o.placed_id,
}
}
}

View File

@ -3,12 +3,12 @@ use std::future::Future;
use tokio::task::JoinHandle; use tokio::task::JoinHandle;
use crate::managers::PriceManager; use crate::managers::{OrderManager, PositionManager, PriceManager};
use crate::models::{Position, PositionProfitState}; use crate::models::{Position, PositionProfitState};
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum SignalKind { pub enum SignalKind {
ClosePosition { position_id: u64 }, ClosePosition(Position),
OpenPosition, OpenPosition,
} }
@ -74,18 +74,19 @@ impl Event {
} }
} }
pub struct EventDispatcher { pub struct Dispatcher {
event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>, event_handlers: HashMap<EventKind, Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>>,
profit_state_handlers: profit_state_handlers:
HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>>, HashMap<PositionProfitState, Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>>,
signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>, signal_handlers: HashMap<SignalKind, Vec<Box<dyn Fn(&SignalKind) -> JoinHandle<()>>>>,
on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>, on_any_event_handlers: Vec<Box<dyn Fn(&Event, &PriceManager) -> JoinHandle<()>>>,
on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>, on_any_profit_state_handlers: Vec<Box<dyn Fn(&Position, &PriceManager) -> JoinHandle<()>>>,
} }
impl EventDispatcher { impl Dispatcher {
pub fn new() -> Self { pub fn new() -> Self {
EventDispatcher { Dispatcher {
event_handlers: HashMap::new(), event_handlers: HashMap::new(),
profit_state_handlers: HashMap::new(), profit_state_handlers: HashMap::new(),
signal_handlers: HashMap::new(), signal_handlers: HashMap::new(),

View File

@ -1,10 +1,14 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::Neg;
use bitfinex::ticker::TradingPairTicker;
use log::error;
use crate::connectors::{Client, ExchangeKind}; use crate::connectors::{Client, ExchangeKind};
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::events::{Event, SignalKind}; use crate::events::{Dispatcher, Event, SignalKind};
use crate::models::{Order, OrderForm, Position, PriceTicker}; use crate::models::{ExecutedOrder, OrderForm, OrderKind, Position, PriceTicker};
use crate::strategy::PositionStrategy; use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy};
use crate::BoxError; use crate::BoxError;
pub struct EventManager { pub struct EventManager {
@ -43,34 +47,6 @@ impl PriceManager {
)) ))
} }
// pub fn add_position(&mut self, position: Position) {
// let (new_position, events, signals) = {
// match &self.strategy {
// Some(strategy) => strategy.on_new_tick(&position, &self),
// None => (position, vec![], vec![]),
// }
// };
//
// self.positions
// .entry(self.current_tick)
// .or_default()
// .push(new_position.clone());
//
// // calling position state callbacks
// self.dispatcher
// .call_position_state_handlers(&new_position, &self);
//
// // adding events and calling callbacks
// for e in events {
// self.add_event(e);
// }
//
// // adding signals to current tick vector
// for s in signals {
// self.add_signal(s);
// }
// }
// fn add_event(&mut self, event: Event) { // fn add_event(&mut self, event: Event) {
// self.events.push(event); // self.events.push(event);
// //
@ -190,7 +166,7 @@ impl PositionManager {
match &self.strategy { match &self.strategy {
Some(strategy) => { Some(strategy) => {
let (pos, strategy_events, _) = let (pos, strategy_events, _) =
strategy.on_new_tick(&position, &self); strategy.on_new_tick(position, &self);
events.extend(strategy_events); events.extend(strategy_events);
@ -225,29 +201,81 @@ impl PositionManager {
self.positions_history self.positions_history
.get(&tick) .get(&tick)
.filter(|x| x.position_id() == id) .filter(|x| x.id() == id)
.and_then(|x| Some(x)) .and_then(|x| Some(x))
} }
} }
pub struct OrderManager { pub struct OrderManager {
tracked_positions: HashMap<u64, ExecutedOrder>,
pair: SymbolPair, pair: SymbolPair,
open_orders: Vec<Order>, open_orders: Vec<ExecutedOrder>,
client: Client, client: Client,
strategy: Box<dyn OrderStrategy>,
} }
impl OrderManager { impl OrderManager {
pub fn new(pair: SymbolPair, client: Client) -> Self { const UNDERCUT_PERC: f64 = 0.005;
pub fn new(pair: SymbolPair, client: Client, strategy: Box<dyn OrderStrategy>) -> Self {
OrderManager { OrderManager {
pair, pair,
open_orders: Vec::new(), open_orders: Vec::new(),
client, client,
strategy,
tracked_positions: HashMap::new(),
} }
} }
pub async fn close_position(&mut self, position: &Position) -> Result<(), BoxError> {
// checking if the position has an open order.
// If so, the strategy method is called, otherwise we open
// an undercut limit order at the best current price.
match self.tracked_positions.get(&position.id()) {
Some(open_order) => self.strategy.on_position_close(open_order, &self),
None => {
let current_prices = self.client.current_prices(&self.pair).await?;
let closing_price = self.best_closing_price(&position, &current_prices)?;
// submitting order
let order_form = OrderForm::new(
&self.pair,
closing_price,
position.amount().neg(),
OrderKind::Limit,
);
match self.client.submit_order(order_form).await {
Err(e) => error!("Could not submit order: {}", e),
Ok(o) => {
self.tracked_positions.insert(position.id(), o);
}
};
}
}
Ok(())
}
pub fn update(&self) -> Option<Vec<Event>> { pub fn update(&self) -> Option<Vec<Event>> {
unimplemented!() unimplemented!()
} }
pub fn best_closing_price(
&self,
position: &Position,
price_ticker: &TradingPairTicker,
) -> Result<f64, BoxError> {
let price = {
if position.is_short() {
price_ticker.ask
} else {
price_ticker.bid
}
};
Ok(price * (1.0 - OrderManager::UNDERCUT_PERC))
}
} }
pub struct ExchangeManager { pub struct ExchangeManager {
@ -255,6 +283,7 @@ pub struct ExchangeManager {
price_managers: Vec<PriceManager>, price_managers: Vec<PriceManager>,
order_managers: Vec<OrderManager>, order_managers: Vec<OrderManager>,
position_managers: Vec<PositionManager>, position_managers: Vec<PositionManager>,
dispatcher: Dispatcher,
client: Client, client: Client,
} }
@ -268,7 +297,11 @@ impl ExchangeManager {
for p in pairs { for p in pairs {
position_managers.push(PositionManager::new(p.clone(), client.clone())); position_managers.push(PositionManager::new(p.clone(), client.clone()));
order_managers.push(OrderManager::new(p.clone(), client.clone())); order_managers.push(OrderManager::new(
p.clone(),
client.clone(),
Box::new(FastOrderStrategy {}),
));
price_managers.push(PriceManager::new(p.clone(), client.clone())); price_managers.push(PriceManager::new(p.clone(), client.clone()));
} }
@ -278,6 +311,7 @@ impl ExchangeManager {
order_managers, order_managers,
price_managers, price_managers,
client, client,
dispatcher: Dispatcher::new(),
} }
} }

View File

@ -1,7 +1,10 @@
use std::fmt::Display;
use chrono::{DateTime, TimeZone};
use crate::currency::SymbolPair; use crate::currency::SymbolPair;
use crate::BoxError; use crate::BoxError;
use chrono::{DateTime, TimeZone}; use std::hash::{Hash, Hasher};
use std::fmt::Display;
/*************** /***************
* Prices * Prices
@ -26,7 +29,7 @@ pub struct PriceTicker {
***************/ ***************/
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Order { pub struct ExecutedOrder {
pub id: i64, pub id: i64,
pub group_id: Option<i32>, pub group_id: Option<i32>,
pub client_id: i64, pub client_id: i64,
@ -48,7 +51,13 @@ pub struct Order {
pub placed_id: Option<i32>, pub placed_id: Option<i32>,
} }
#[derive(Copy, Clone, Debug)] impl Hash for ExecutedOrder {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.id.to_le_bytes())
}
}
#[derive(Copy, Clone, Debug, Hash)]
pub enum OrderKind { pub enum OrderKind {
Limit, Limit,
ExchangeLimit, ExchangeLimit,
@ -73,11 +82,11 @@ pub struct OrderForm {
/// EXCHANGE FOK, IOC, EXCHANGE IOC /// EXCHANGE FOK, IOC, EXCHANGE IOC
kind: OrderKind, kind: OrderKind,
/// Symbol for desired pair /// Symbol for desired pair
symbol: SymbolPair, pair: SymbolPair,
/// Price of order /// Price of order
price: String, price: f64,
/// Amount of order (positive for buy, negative for sell) /// Amount of order (positive for buy, negative for sell)
amount: String, amount: f64,
/// Set the leverage for a derivative order, supported by derivative symbol orders only. /// Set the leverage for a derivative order, supported by derivative symbol orders only.
/// The value should be between 1 and 100 inclusive. /// The value should be between 1 and 100 inclusive.
/// The field is optional, if omitted the default leverage value of 10 will be used. /// The field is optional, if omitted the default leverage value of 10 will be used.
@ -91,12 +100,12 @@ pub struct OrderForm {
} }
impl OrderForm { impl OrderForm {
pub fn new(symbol: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self { pub fn new(pair: &SymbolPair, price: f64, amount: f64, kind: OrderKind) -> Self {
OrderForm { OrderForm {
kind, kind,
symbol: symbol.clone(), pair: pair.clone(),
price: price.to_string(), price,
amount: amount.to_string(), amount,
leverage: None, leverage: None,
price_trailing: None, price_trailing: None,
price_aux_limit: None, price_aux_limit: None,
@ -119,7 +128,7 @@ impl OrderForm {
} }
} }
pub fn price_aux_limit(mut self, limit: f64) -> Result<Self, BoxError> { pub fn with_price_aux_limit(mut self, limit: f64) -> Result<Self, BoxError> {
match self.kind { match self.kind {
OrderKind::StopLimit | OrderKind::ExchangeStopLimit => { OrderKind::StopLimit | OrderKind::ExchangeStopLimit => {
self.price_aux_limit = Some(limit.to_string()); self.price_aux_limit = Some(limit.to_string());
@ -136,13 +145,38 @@ impl OrderForm {
self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string()); self.tif = Some(tif.format("%Y-%m-%d %H:%M:%S").to_string());
self self
} }
pub fn kind(&self) -> OrderKind {
self.kind
}
pub fn pair(&self) -> &SymbolPair {
&self.pair
}
pub fn price(&self) -> &f64 {
&self.price
}
pub fn amount(&self) -> &f64 {
&self.amount
}
pub fn leverage(&self) -> Option<u32> {
self.leverage
}
pub fn price_trailing(&self) -> &Option<String> {
&self.price_trailing
}
pub fn price_aux_limit(&self) -> &Option<String> {
&self.price_aux_limit
}
pub fn tif(&self) -> &Option<String> {
&self.tif
}
} }
/*************** /***************
* Positions * Positions
***************/ ***************/
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug)]
pub struct Position { pub struct Position {
pair: SymbolPair, pair: SymbolPair,
state: PositionState, state: PositionState,
@ -219,7 +253,7 @@ impl Position {
pub fn price_liq(&self) -> f64 { pub fn price_liq(&self) -> f64 {
self.price_liq self.price_liq
} }
pub fn position_id(&self) -> u64 { pub fn id(&self) -> u64 {
self.position_id self.position_id
} }
pub fn profit_state(&self) -> Option<PositionProfitState> { pub fn profit_state(&self) -> Option<PositionProfitState> {
@ -231,8 +265,27 @@ impl Position {
pub fn creation_update(&self) -> Option<u64> { pub fn creation_update(&self) -> Option<u64> {
self.creation_update self.creation_update
} }
pub fn is_short(&self) -> bool {
self.amount.is_sign_negative()
}
pub fn is_long(&self) -> bool {
self.amount.is_sign_positive()
}
} }
impl Hash for Position {
fn hash<H: Hasher>(&self, state: &mut H) {
state.write(&self.id().to_le_bytes())
}
}
impl PartialEq for Position {
fn eq(&self, other: &Self) -> bool {
self.id() == other.id()
}
}
impl Eq for Position {}
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum PositionProfitState { pub enum PositionProfitState {
Critical, Critical,

View File

@ -4,16 +4,20 @@ use std::fmt::{Debug, Formatter};
use dyn_clone::DynClone; use dyn_clone::DynClone;
use crate::events::{Event, EventKind, EventMetadata, SignalKind}; use crate::events::{Event, EventKind, EventMetadata, SignalKind};
use crate::managers::PositionManager; use crate::managers::{OrderManager, PositionManager};
use crate::models::{Position, PositionProfitState}; use crate::models::{ExecutedOrder, OrderForm, Position, PositionProfitState};
/***************
* DEFINITIONS
***************/
pub trait PositionStrategy: DynClone { pub trait PositionStrategy: DynClone {
fn name(&self) -> String; fn name(&self) -> String;
fn on_new_tick( fn on_new_tick(
&self, &self,
position: &Position, position: Position,
manager: &PositionManager, manager: &PositionManager,
) -> (Position, Vec<Event>, Vec<SignalKind>); ) -> (Position, Vec<Event>, Option<OrderForm>);
} }
impl Debug for dyn PositionStrategy { impl Debug for dyn PositionStrategy {
@ -22,6 +26,27 @@ impl Debug for dyn PositionStrategy {
} }
} }
pub trait OrderStrategy: DynClone {
/// 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: &ExecutedOrder, manager: &mut OrderManager);
}
impl Debug for dyn OrderStrategy {
fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
write!(f, "{}", self.name())
}
}
/***************
* IMPLEMENTATIONS
***************/
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TrailingStop { pub struct TrailingStop {
stop_percentages: HashMap<u64, f64>, stop_percentages: HashMap<u64, f64>,
@ -53,12 +78,13 @@ impl PositionStrategy for TrailingStop {
fn on_new_tick( fn on_new_tick(
&self, &self,
position: &Position, position: Position,
manager: &PositionManager, manager: &PositionManager,
) -> (Position, Vec<Event>, Vec<SignalKind>) { ) -> (Position, Vec<Event>, Option<OrderForm>) {
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 mut order = None;
let state = { let state = {
if pl_perc > TrailingStop::GOOD_PROFIT_PERC { if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
@ -72,24 +98,22 @@ impl PositionStrategy for TrailingStop {
} 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.clone()));
position_id: position.position_id(),
});
PositionProfitState::Critical PositionProfitState::Critical
} }
}; };
let opt_pre_pw = manager.position_previous_tick(position.position_id(), None); let opt_pre_pw = manager.position_previous_tick(position.id(), None);
let event_metadata = EventMetadata::new(Some(position.position_id()), None); let event_metadata = EventMetadata::new(Some(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, order);
} }
} }
None => return (new_position, events, signals), None => return (new_position, events, order),
}; };
let events = { let events = {
@ -130,6 +154,23 @@ impl PositionStrategy for TrailingStop {
events events
}; };
return (new_position, events, signals); return (new_position, events, order);
}
}
#[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: &ExecutedOrder, manager: &mut OrderManager) {
unimplemented!()
} }
} }