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::time::Duration;
|
||||
|
||||
use crate::BoxError;
|
||||
use crate::currency::{Symbol, SymbolPair};
|
||||
use crate::models::{
|
||||
ActiveOrder, OrderBook, OrderBookEntry, OrderDetails, OrderFee, OrderForm, OrderKind, Position,
|
||||
PositionState, PriceTicker, Trade, TradingFees, TradingPlatform, WalletKind,
|
||||
};
|
||||
use crate::BoxError;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum Exchange {
|
||||
@ -164,6 +164,8 @@ impl Client {
|
||||
) -> Result<Option<Vec<OrderDetails>>, BoxError> {
|
||||
self.inner.orders_history(pair).await
|
||||
}
|
||||
|
||||
pub async fn trading_fees(&self) -> Result<Vec<TradingFees>, BoxError> { self.inner.trading_fees().await }
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -237,7 +239,7 @@ impl BitfinexConnector {
|
||||
async fn retry_nonce<F, Fut, O>(mut func: F) -> Result<O, BoxError>
|
||||
where
|
||||
F: FnMut() -> Fut,
|
||||
Fut: Future<Output = Result<O, BoxError>>,
|
||||
Fut: Future<Output=Result<O, BoxError>>,
|
||||
{
|
||||
let response = {
|
||||
loop {
|
||||
@ -327,7 +329,7 @@ impl Connector for BitfinexConnector {
|
||||
OrderKind::Stop { price } => {
|
||||
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())
|
||||
.with_price_aux_limit(limit_price)?
|
||||
}
|
||||
@ -603,7 +605,7 @@ impl From<&bitfinex::responses::OrderResponse> for OrderKind {
|
||||
}
|
||||
bitfinex::orders::OrderKind::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!"),
|
||||
},
|
||||
bitfinex::orders::OrderKind::TrailingStop
|
||||
@ -642,7 +644,7 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderKind {
|
||||
}
|
||||
bitfinex::orders::OrderKind::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!"),
|
||||
},
|
||||
bitfinex::orders::OrderKind::TrailingStop
|
||||
|
@ -5,11 +5,12 @@ use futures_util::stream::FuturesUnordered;
|
||||
use futures_util::StreamExt;
|
||||
use log::{debug, error, info, trace};
|
||||
use merge::Merge;
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tokio::sync::mpsc::{Receiver, Sender};
|
||||
use tokio::sync::mpsc::channel;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::time::Duration;
|
||||
|
||||
use crate::BoxError;
|
||||
use crate::connectors::{Client, ExchangeDetails};
|
||||
use crate::currency::SymbolPair;
|
||||
use crate::events::{ActionMessage, ActorMessage, Event};
|
||||
@ -17,7 +18,6 @@ use crate::models::{
|
||||
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker,
|
||||
};
|
||||
use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy};
|
||||
use crate::BoxError;
|
||||
|
||||
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> {
|
||||
trace!("\t[PositionManager] Updating {}", self.pair);
|
||||
|
||||
let opt_active_positions = self.client.active_positions(&self.pair).await?;
|
||||
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
|
||||
match opt_active_positions {
|
||||
@ -258,11 +260,11 @@ impl PositionManager {
|
||||
|
||||
let (pos_on_tick, events_on_tick, messages_on_tick) = self
|
||||
.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
|
||||
.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_post_tick);
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::ops::Neg;
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use log::info;
|
||||
@ -8,7 +9,7 @@ use crate::BoxError;
|
||||
use crate::connectors::Connector;
|
||||
use crate::events::{ActionMessage, Event, EventKind, EventMetadata};
|
||||
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
|
||||
@ -21,12 +22,14 @@ pub trait PositionStrategy: DynClone + Send + Sync {
|
||||
position: Position,
|
||||
current_tick: u64,
|
||||
positions_history: &HashMap<u64, Position>,
|
||||
fees: &Vec<TradingFees>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||
fn post_tick(
|
||||
&mut self,
|
||||
position: Position,
|
||||
current_tick: u64,
|
||||
positions_history: &HashMap<u64, Position>,
|
||||
fees: &Vec<TradingFees>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||
}
|
||||
|
||||
@ -68,7 +71,10 @@ impl Debug for dyn OrderStrategy {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HiddenTrailingStop {
|
||||
// position id: stop_percentage
|
||||
stop_percentages: HashMap<u64, f64>,
|
||||
// position_id: bool
|
||||
stop_loss_flags: HashMap<u64, bool>,
|
||||
capital_max_loss: f64,
|
||||
capital_min_profit: f64,
|
||||
capital_good_profit: f64,
|
||||
@ -80,6 +86,7 @@ pub struct HiddenTrailingStop {
|
||||
max_loss_percentage: f64,
|
||||
}
|
||||
|
||||
|
||||
impl HiddenTrailingStop {
|
||||
fn update_stop_percentage(&mut self, position: &Position) {
|
||||
if let Some(profit_state) = position.profit_state() {
|
||||
@ -144,6 +151,7 @@ impl Default for HiddenTrailingStop {
|
||||
|
||||
HiddenTrailingStop {
|
||||
stop_percentages: Default::default(),
|
||||
stop_loss_flags: Default::default(),
|
||||
capital_max_loss,
|
||||
capital_min_profit,
|
||||
capital_good_profit,
|
||||
@ -168,9 +176,11 @@ impl PositionStrategy for HiddenTrailingStop {
|
||||
position: Position,
|
||||
current_tick: u64,
|
||||
positions_history: &HashMap<u64, Position>,
|
||||
_: &Vec<TradingFees>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||
let pl_perc = position.pl_perc();
|
||||
|
||||
// setting the state of the position based on its profit/loss percentage
|
||||
let state = {
|
||||
if pl_perc > self.good_profit_percentage {
|
||||
PositionProfitState::Profit
|
||||
@ -189,6 +199,8 @@ impl PositionStrategy for HiddenTrailingStop {
|
||||
let event_metadata = EventMetadata::new(Some(position.id()), None);
|
||||
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 {
|
||||
Some(prev) => {
|
||||
if prev.profit_state() == Some(state) {
|
||||
@ -239,28 +251,75 @@ impl PositionStrategy for HiddenTrailingStop {
|
||||
position: Position,
|
||||
_: u64,
|
||||
_: &HashMap<u64, Position>,
|
||||
fees: &Vec<TradingFees>,
|
||||
) -> (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(),
|
||||
};
|
||||
|
||||
// if critical, early return with close position
|
||||
if let Some(PositionProfitState::Critical) = position.profit_state() {
|
||||
info!("Maximum loss reached. Closing position.");
|
||||
return (position, None, Some(vec![close_message]));
|
||||
let close_position_msg = ActionMessage::ClosePosition {
|
||||
position_id: position.id(),
|
||||
};
|
||||
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
|
||||
if let Some(existing_stop_percentage) = self.stop_percentages.get(&position.id()) {
|
||||
if &position.pl_perc() <= existing_stop_percentage {
|
||||
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);
|
||||
|
||||
(position, None, None)
|
||||
(position, None, Some(messages))
|
||||
}
|
||||
}
|
||||
|
||||
@ -531,7 +590,7 @@ pub struct MarketEnforce {
|
||||
impl Default for MarketEnforce {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
threshold: 1.0 / 15.0,
|
||||
threshold: 100.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user