implemented stop order for trailing stop. position strategy now receives fees information. added fees API to client.
This commit is contained in:
parent
386137f16e
commit
2d81358fa0
@ -14,12 +14,12 @@ use log::trace;
|
|||||||
use tokio::macros::support::Future;
|
use tokio::macros::support::Future;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
|
|
||||||
|
use crate::BoxError;
|
||||||
use crate::currency::{Symbol, SymbolPair};
|
use crate::currency::{Symbol, SymbolPair};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position,
|
ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position,
|
||||||
PositionState, PriceTicker, Trade, TradingFees, TradingPlatform, WalletKind,
|
PositionState, PriceTicker, Trade, TradingFees, TradingPlatform, WalletKind,
|
||||||
};
|
};
|
||||||
use crate::BoxError;
|
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||||
pub enum Exchange {
|
pub enum Exchange {
|
||||||
@ -164,6 +164,8 @@ impl Client {
|
|||||||
) -> Result<Option<Vec<OrderDetails>>, BoxError> {
|
) -> Result<Option<Vec<OrderDetails>>, BoxError> {
|
||||||
self.inner.orders_history(pair).await
|
self.inner.orders_history(pair).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn trading_fees(&self) -> Result<Vec<TradingFees>, BoxError> { self.inner.trading_fees().await }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -237,7 +239,7 @@ impl BitfinexConnector {
|
|||||||
async fn retry_nonce<F, Fut, O>(mut func: F) -> Result<O, BoxError>
|
async fn retry_nonce<F, Fut, O>(mut func: F) -> Result<O, BoxError>
|
||||||
where
|
where
|
||||||
F: FnMut() -> Fut,
|
F: FnMut() -> Fut,
|
||||||
Fut: Future<Output = Result<O, BoxError>>,
|
Fut: Future<Output=Result<O, BoxError>>,
|
||||||
{
|
{
|
||||||
let response = {
|
let response = {
|
||||||
loop {
|
loop {
|
||||||
@ -327,7 +329,7 @@ impl Connector for BitfinexConnector {
|
|||||||
OrderKind::Stop { price } => {
|
OrderKind::Stop { price } => {
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
}
|
}
|
||||||
OrderKind::StopLimit { price, limit_price } => {
|
OrderKind::StopLimit { stop_price: price, limit_price } => {
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
.with_price_aux_limit(limit_price)?
|
.with_price_aux_limit(limit_price)?
|
||||||
}
|
}
|
||||||
@ -603,7 +605,7 @@ impl From<&bitfinex::responses::OrderResponse> for OrderKind {
|
|||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::StopLimit
|
bitfinex::orders::OrderKind::StopLimit
|
||||||
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
||||||
price: response.price(),
|
stop_price: response.price(),
|
||||||
limit_price: response.price_aux_limit().expect("Limit price not found!"),
|
limit_price: response.price_aux_limit().expect("Limit price not found!"),
|
||||||
},
|
},
|
||||||
bitfinex::orders::OrderKind::TrailingStop
|
bitfinex::orders::OrderKind::TrailingStop
|
||||||
@ -642,7 +644,7 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderKind {
|
|||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::StopLimit
|
bitfinex::orders::OrderKind::StopLimit
|
||||||
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
||||||
price: response.price(),
|
stop_price: response.price(),
|
||||||
limit_price: response.price_aux_limit().expect("Limit price not found!"),
|
limit_price: response.price_aux_limit().expect("Limit price not found!"),
|
||||||
},
|
},
|
||||||
bitfinex::orders::OrderKind::TrailingStop
|
bitfinex::orders::OrderKind::TrailingStop
|
||||||
|
@ -5,11 +5,12 @@ use futures_util::stream::FuturesUnordered;
|
|||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use log::{debug, error, info, trace};
|
use log::{debug, error, info, trace};
|
||||||
use merge::Merge;
|
use merge::Merge;
|
||||||
use tokio::sync::mpsc::channel;
|
|
||||||
use tokio::sync::mpsc::{Receiver, Sender};
|
use tokio::sync::mpsc::{Receiver, Sender};
|
||||||
|
use tokio::sync::mpsc::channel;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
|
|
||||||
|
use crate::BoxError;
|
||||||
use crate::connectors::{Client, ExchangeDetails};
|
use crate::connectors::{Client, ExchangeDetails};
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::events::{ActionMessage, ActorMessage, Event};
|
use crate::events::{ActionMessage, ActorMessage, Event};
|
||||||
@ -17,7 +18,6 @@ use crate::models::{
|
|||||||
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker,
|
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker,
|
||||||
};
|
};
|
||||||
use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy};
|
use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy};
|
||||||
use crate::BoxError;
|
|
||||||
|
|
||||||
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||||
|
|
||||||
@ -234,8 +234,10 @@ impl PositionManager {
|
|||||||
pub async fn update(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
pub async fn update(&mut self, tick: u64) -> Result<OptionUpdate, BoxError> {
|
||||||
trace!("\t[PositionManager] Updating {}", self.pair);
|
trace!("\t[PositionManager] Updating {}", self.pair);
|
||||||
|
|
||||||
let opt_active_positions = self.client.active_positions(&self.pair).await?;
|
|
||||||
self.current_tick = tick;
|
self.current_tick = tick;
|
||||||
|
let (fees, opt_active_positions) = tokio::join!(self.client.trading_fees(),self.client.active_positions(&self.pair));
|
||||||
|
let (fees, opt_active_positions) = (fees?, opt_active_positions?);
|
||||||
|
|
||||||
|
|
||||||
// we assume there is only ONE active position per pair
|
// we assume there is only ONE active position per pair
|
||||||
match opt_active_positions {
|
match opt_active_positions {
|
||||||
@ -258,11 +260,11 @@ impl PositionManager {
|
|||||||
|
|
||||||
let (pos_on_tick, events_on_tick, messages_on_tick) = self
|
let (pos_on_tick, events_on_tick, messages_on_tick) = self
|
||||||
.strategy
|
.strategy
|
||||||
.on_tick(position, self.current_tick(), &self.positions_history);
|
.on_tick(position, self.current_tick(), &self.positions_history, &fees);
|
||||||
|
|
||||||
let (pos_post_tick, events_post_tick, messages_post_tick) = self
|
let (pos_post_tick, events_post_tick, messages_post_tick) = self
|
||||||
.strategy
|
.strategy
|
||||||
.post_tick(pos_on_tick, self.current_tick(), &self.positions_history);
|
.post_tick(pos_on_tick, self.current_tick(), &self.positions_history, &fees);
|
||||||
|
|
||||||
events.merge(events_on_tick);
|
events.merge(events_on_tick);
|
||||||
events.merge(events_post_tick);
|
events.merge(events_post_tick);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
use std::ops::Neg;
|
||||||
|
|
||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
use log::info;
|
use log::info;
|
||||||
@ -8,7 +9,7 @@ use crate::BoxError;
|
|||||||
use crate::connectors::Connector;
|
use crate::connectors::Connector;
|
||||||
use crate::events::{ActionMessage, Event, EventKind, EventMetadata};
|
use crate::events::{ActionMessage, Event, EventKind, EventMetadata};
|
||||||
use crate::managers::OptionUpdate;
|
use crate::managers::OptionUpdate;
|
||||||
use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState};
|
use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, OrderMetadata, Position, PositionProfitState, TradingFees};
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
* DEFINITIONS
|
* DEFINITIONS
|
||||||
@ -21,12 +22,14 @@ pub trait PositionStrategy: DynClone + Send + Sync {
|
|||||||
position: Position,
|
position: Position,
|
||||||
current_tick: u64,
|
current_tick: u64,
|
||||||
positions_history: &HashMap<u64, Position>,
|
positions_history: &HashMap<u64, Position>,
|
||||||
|
fees: &Vec<TradingFees>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||||
fn post_tick(
|
fn post_tick(
|
||||||
&mut self,
|
&mut self,
|
||||||
position: Position,
|
position: Position,
|
||||||
current_tick: u64,
|
current_tick: u64,
|
||||||
positions_history: &HashMap<u64, Position>,
|
positions_history: &HashMap<u64, Position>,
|
||||||
|
fees: &Vec<TradingFees>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +71,10 @@ impl Debug for dyn OrderStrategy {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HiddenTrailingStop {
|
pub struct HiddenTrailingStop {
|
||||||
|
// position id: stop_percentage
|
||||||
stop_percentages: HashMap<u64, f64>,
|
stop_percentages: HashMap<u64, f64>,
|
||||||
|
// position_id: bool
|
||||||
|
stop_loss_flags: HashMap<u64, bool>,
|
||||||
capital_max_loss: f64,
|
capital_max_loss: f64,
|
||||||
capital_min_profit: f64,
|
capital_min_profit: f64,
|
||||||
capital_good_profit: f64,
|
capital_good_profit: f64,
|
||||||
@ -80,6 +86,7 @@ pub struct HiddenTrailingStop {
|
|||||||
max_loss_percentage: f64,
|
max_loss_percentage: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl HiddenTrailingStop {
|
impl HiddenTrailingStop {
|
||||||
fn update_stop_percentage(&mut self, position: &Position) {
|
fn update_stop_percentage(&mut self, position: &Position) {
|
||||||
if let Some(profit_state) = position.profit_state() {
|
if let Some(profit_state) = position.profit_state() {
|
||||||
@ -144,6 +151,7 @@ impl Default for HiddenTrailingStop {
|
|||||||
|
|
||||||
HiddenTrailingStop {
|
HiddenTrailingStop {
|
||||||
stop_percentages: Default::default(),
|
stop_percentages: Default::default(),
|
||||||
|
stop_loss_flags: Default::default(),
|
||||||
capital_max_loss,
|
capital_max_loss,
|
||||||
capital_min_profit,
|
capital_min_profit,
|
||||||
capital_good_profit,
|
capital_good_profit,
|
||||||
@ -168,9 +176,11 @@ impl PositionStrategy for HiddenTrailingStop {
|
|||||||
position: Position,
|
position: Position,
|
||||||
current_tick: u64,
|
current_tick: u64,
|
||||||
positions_history: &HashMap<u64, Position>,
|
positions_history: &HashMap<u64, Position>,
|
||||||
|
_: &Vec<TradingFees>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||||
let pl_perc = position.pl_perc();
|
let pl_perc = position.pl_perc();
|
||||||
|
|
||||||
|
// setting the state of the position based on its profit/loss percentage
|
||||||
let state = {
|
let state = {
|
||||||
if pl_perc > self.good_profit_percentage {
|
if pl_perc > self.good_profit_percentage {
|
||||||
PositionProfitState::Profit
|
PositionProfitState::Profit
|
||||||
@ -189,6 +199,8 @@ impl PositionStrategy for HiddenTrailingStop {
|
|||||||
let event_metadata = EventMetadata::new(Some(position.id()), None);
|
let event_metadata = EventMetadata::new(Some(position.id()), None);
|
||||||
let new_position = position.with_profit_state(Some(state));
|
let new_position = position.with_profit_state(Some(state));
|
||||||
|
|
||||||
|
// checking if there was a state change between the current position
|
||||||
|
// and its last state
|
||||||
match opt_prev_position {
|
match opt_prev_position {
|
||||||
Some(prev) => {
|
Some(prev) => {
|
||||||
if prev.profit_state() == Some(state) {
|
if prev.profit_state() == Some(state) {
|
||||||
@ -239,28 +251,75 @@ impl PositionStrategy for HiddenTrailingStop {
|
|||||||
position: Position,
|
position: Position,
|
||||||
_: u64,
|
_: u64,
|
||||||
_: &HashMap<u64, Position>,
|
_: &HashMap<u64, Position>,
|
||||||
|
fees: &Vec<TradingFees>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||||
let close_message = ActionMessage::ClosePosition {
|
let taker_fee = fees
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| match x {
|
||||||
|
TradingFees::Taker {
|
||||||
|
platform,
|
||||||
|
percentage,
|
||||||
|
} if platform == &position.platform() => Some(percentage),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next().map_or_else(|| 0.0, |&x| x);
|
||||||
|
|
||||||
|
let stop_loss_price = {
|
||||||
|
if position.is_short() {
|
||||||
|
position.base_price() * (1.0 - (self.max_loss_percentage - taker_fee) / 100.0)
|
||||||
|
} else {
|
||||||
|
position.base_price() * (1.0 + (self.max_loss_percentage - taker_fee) / 100.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let close_position_orders_msg = ActionMessage::ClosePositionOrders {
|
||||||
position_id: position.id(),
|
position_id: position.id(),
|
||||||
};
|
};
|
||||||
|
let close_position_msg = ActionMessage::ClosePosition {
|
||||||
// if critical, early return with close position
|
position_id: position.id(),
|
||||||
if let Some(PositionProfitState::Critical) = position.profit_state() {
|
|
||||||
info!("Maximum loss reached. Closing position.");
|
|
||||||
return (position, None, Some(vec![close_message]));
|
|
||||||
};
|
};
|
||||||
|
let set_stop_loss_msg = ActionMessage::SubmitOrder {
|
||||||
|
order: OrderForm::new(position.pair().clone(),
|
||||||
|
OrderKind::Stop { price: stop_loss_price },
|
||||||
|
position.platform(),
|
||||||
|
position.amount().neg())
|
||||||
|
.with_metadata(Some(OrderMetadata::with_position_id(position.id())))
|
||||||
|
};
|
||||||
|
let stop_loss_set = *self.stop_loss_flags.entry(position.id()).or_insert(false);
|
||||||
|
|
||||||
|
// if in loss, ask the order manager to set the stop limit order,
|
||||||
|
// if not already set
|
||||||
|
if let Some(PositionProfitState::Critical) | Some(PositionProfitState::Loss) = position.profit_state() {
|
||||||
|
if !stop_loss_set {
|
||||||
|
info!("In loss. Opening trailing stop order.");
|
||||||
|
|
||||||
|
self.stop_loss_flags.insert(position.id(), true);
|
||||||
|
return (position, None, Some(vec![set_stop_loss_msg]));
|
||||||
|
}
|
||||||
|
return (position, None, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut messages = vec![];
|
||||||
|
|
||||||
|
// if a stop loss order was previously set,
|
||||||
|
// ask the order manager to remove the order first
|
||||||
|
if stop_loss_set {
|
||||||
|
info!("Removing stop loss order.");
|
||||||
|
messages.push(close_position_orders_msg);
|
||||||
|
self.stop_loss_flags.insert(position.id(), false);
|
||||||
|
}
|
||||||
|
|
||||||
// let's check if we surpassed an existing stop percentage
|
// let's check if we surpassed an existing stop percentage
|
||||||
if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) {
|
if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) {
|
||||||
if &position.pl_perc() <= existing_stop_percentage {
|
if &position.pl_perc() <= existing_stop_percentage {
|
||||||
info!("Stop percentage surpassed. Closing position.");
|
info!("Stop percentage surpassed. Closing position.");
|
||||||
return (position, None, Some(vec![close_message]));
|
messages.push(close_position_msg);
|
||||||
|
return (position, None, Some(messages));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.update_stop_percentage(&position);
|
self.update_stop_percentage(&position);
|
||||||
|
|
||||||
(position, None, None)
|
(position, None, Some(messages))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -531,7 +590,7 @@ pub struct MarketEnforce {
|
|||||||
impl Default for MarketEnforce {
|
impl Default for MarketEnforce {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
threshold: 1.0 / 15.0,
|
threshold: 100.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user