added trailingstop strategy (useless) but modified hiddentrailing stop: implemented default for both strategies.
additionally, the order manager now tries to pair active orders with active positions. order managers now have more APIs such as close orders associated with positions and submit order
This commit is contained in:
parent
762485db3a
commit
53fb8781b3
@ -12,14 +12,14 @@ use tokio::time::Duration;
|
||||
|
||||
use crate::connectors::{Client, ExchangeDetails};
|
||||
use crate::currency::SymbolPair;
|
||||
use crate::events::{ActorMessage, Event, Message};
|
||||
use crate::events::{ActionMessage, ActorMessage, Event};
|
||||
use crate::models::{
|
||||
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform,
|
||||
};
|
||||
use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy};
|
||||
use crate::BoxError;
|
||||
|
||||
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<Message>>);
|
||||
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||
|
||||
/******************
|
||||
* PRICES
|
||||
@ -44,7 +44,7 @@ impl PriceManager {
|
||||
}
|
||||
|
||||
pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> {
|
||||
if let Message::Update { tick } = message.message {
|
||||
if let ActionMessage::Update { tick } = message.message {
|
||||
let a = self.update(tick).await?;
|
||||
self.add_entry(a);
|
||||
}
|
||||
@ -100,7 +100,7 @@ impl PriceManagerHandle {
|
||||
|
||||
self.sender
|
||||
.send(ActorMessage {
|
||||
message: Message::Update { tick },
|
||||
message: ActionMessage::Update { tick },
|
||||
respond_to: send,
|
||||
})
|
||||
.await?;
|
||||
@ -176,7 +176,7 @@ impl PositionManagerHandle {
|
||||
|
||||
self.sender
|
||||
.send(ActorMessage {
|
||||
message: Message::Update { tick },
|
||||
message: ActionMessage::Update { tick },
|
||||
respond_to: send,
|
||||
})
|
||||
.await?;
|
||||
@ -221,7 +221,7 @@ impl PositionManager {
|
||||
|
||||
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
|
||||
let (events, messages) = match msg.message {
|
||||
Message::Update { tick } => self.update(tick).await?,
|
||||
ActionMessage::Update { tick } => self.update(tick).await?,
|
||||
_ => (None, None),
|
||||
};
|
||||
|
||||
@ -302,7 +302,7 @@ impl PositionManager {
|
||||
******************/
|
||||
|
||||
// Position ID: Order ID
|
||||
pub type TrackedPositionsMap = HashMap<u64, u64>;
|
||||
pub type TrackedPositionsMap = HashMap<u64, Vec<u64>>;
|
||||
|
||||
pub struct OrderManagerHandle {
|
||||
sender: Sender<ActorMessage>,
|
||||
@ -344,7 +344,36 @@ impl OrderManagerHandle {
|
||||
|
||||
self.sender
|
||||
.send(ActorMessage {
|
||||
message: Message::ClosePosition { position_id },
|
||||
message: ActionMessage::ClosePosition { position_id },
|
||||
respond_to: send,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(recv.await?)
|
||||
}
|
||||
|
||||
pub async fn close_position_orders(
|
||||
&mut self,
|
||||
position_id: u64,
|
||||
) -> Result<OptionUpdate, BoxError> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
|
||||
self.sender
|
||||
.send(ActorMessage {
|
||||
message: ActionMessage::ClosePositionOrders { position_id },
|
||||
respond_to: send,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(recv.await?)
|
||||
}
|
||||
|
||||
pub async fn submit_order(&mut self, order_form: OrderForm) -> Result<OptionUpdate, BoxError> {
|
||||
let (send, recv) = oneshot::channel();
|
||||
|
||||
self.sender
|
||||
.send(ActorMessage {
|
||||
message: ActionMessage::SubmitOrder { order: order_form },
|
||||
respond_to: send,
|
||||
})
|
||||
.await?;
|
||||
@ -381,9 +410,14 @@ impl OrderManager {
|
||||
|
||||
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
|
||||
let (events, messages) = match msg.message {
|
||||
Message::Update { .. } => self.update().await?,
|
||||
Message::ClosePosition { position_id } => self.close_position(position_id).await?,
|
||||
_ => (None, None),
|
||||
ActionMessage::Update { .. } => self.update().await?,
|
||||
ActionMessage::ClosePosition { position_id } => {
|
||||
self.close_position(position_id).await?
|
||||
}
|
||||
ActionMessage::ClosePositionOrders { position_id } => {
|
||||
self.close_position_orders(position_id).await?
|
||||
}
|
||||
ActionMessage::SubmitOrder { order } => self.submit_order(&order).await?,
|
||||
};
|
||||
|
||||
Ok(msg
|
||||
@ -392,6 +426,53 @@ impl OrderManager {
|
||||
.map_err(|_| BoxError::from("Could not send message."))?)
|
||||
}
|
||||
|
||||
pub async fn close_position_orders(&self, position_id: u64) -> Result<OptionUpdate, BoxError> {
|
||||
info!("Closing outstanding orders for position #{}", position_id);
|
||||
|
||||
if let Some(position_orders) = self.tracked_positions.get(&position_id) {
|
||||
// retrieving open orders
|
||||
let open_orders = self.client.active_orders(&self.pair).await?;
|
||||
let position_orders: Vec<_> = position_orders
|
||||
.iter()
|
||||
.filter_map(|&x| open_orders.iter().find(|y| y.id == x))
|
||||
.collect();
|
||||
|
||||
for order in position_orders {
|
||||
match self.client.cancel_order(order).await {
|
||||
Ok(_) => info!("Order #{} closed successfully.", order.id),
|
||||
Err(e) => error!("Could not close order #{}: {}", order.id, e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: return valid messages and events!
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
pub async fn submit_order(&mut self, order_form: &OrderForm) -> Result<OptionUpdate, BoxError> {
|
||||
info!("Submiting {}", order_form.kind());
|
||||
|
||||
let active_order = self.client.submit_order(order_form).await?;
|
||||
|
||||
debug!("Adding order to tracked orders.");
|
||||
if let Some(metadata) = order_form.metadata() {
|
||||
if let Some(position_id) = metadata.position_id() {
|
||||
match self.tracked_positions.get_mut(&position_id) {
|
||||
None => {
|
||||
self.tracked_positions
|
||||
.insert(position_id, vec![active_order.id]);
|
||||
}
|
||||
Some(position_orders) => {
|
||||
position_orders.push(active_order.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: return valid messages and events!111!!!1!
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
pub async fn close_position(&mut self, position_id: u64) -> Result<OptionUpdate, BoxError> {
|
||||
info!("Closing position #{}", position_id);
|
||||
|
||||
@ -450,8 +531,8 @@ impl OrderManager {
|
||||
Ok((None, None))
|
||||
}
|
||||
|
||||
pub async fn update(&self) -> Result<OptionUpdate, BoxError> {
|
||||
trace!("\t[OrderManager] Updating {}", self.pair);
|
||||
pub async fn update(&mut self) -> Result<OptionUpdate, BoxError> {
|
||||
debug!("\t[OrderManager] Updating {}", self.pair);
|
||||
|
||||
let (res_open_orders, res_order_book) = tokio::join!(
|
||||
self.client.active_orders(&self.pair),
|
||||
@ -460,8 +541,49 @@ impl OrderManager {
|
||||
|
||||
let (open_orders, order_book) = (res_open_orders?, res_order_book?);
|
||||
|
||||
// retrieving open positions to check whether the positions have open orders.
|
||||
// we need to update our internal mapping in that case.
|
||||
if !open_orders.is_empty() {
|
||||
let open_positions = self.client.active_positions(&self.pair).await?;
|
||||
|
||||
if let Some(positions) = open_positions {
|
||||
// currently, we are only trying to match orders with an amount equal to
|
||||
// a position amount.
|
||||
for position in positions {
|
||||
let matching_order = open_orders
|
||||
.iter()
|
||||
.find(|x| x.details.amount().abs() == position.amount().abs());
|
||||
|
||||
// if an order is found, we insert the order to our internal mapping, if not already present
|
||||
if let Some(matching_order) = matching_order {
|
||||
match self.tracked_positions.get_mut(&position.id()) {
|
||||
Some(position_orders) => {
|
||||
if !position_orders.contains(&matching_order.id) {
|
||||
trace!(
|
||||
"Mapped order #{} to position #{}",
|
||||
position.id(),
|
||||
matching_order.id
|
||||
);
|
||||
position_orders.push(matching_order.id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
trace!(
|
||||
"Mapped order #{} to position #{}",
|
||||
position.id(),
|
||||
matching_order.id
|
||||
);
|
||||
self.tracked_positions
|
||||
.insert(position.id(), vec![matching_order.id]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for active_order in open_orders {
|
||||
debug!(
|
||||
trace!(
|
||||
"Found open order, calling \"{}\" strategy.",
|
||||
self.strategy.name()
|
||||
);
|
||||
@ -471,7 +593,7 @@ impl OrderManager {
|
||||
if let Some(messages) = strat_messages {
|
||||
for m in messages {
|
||||
match m {
|
||||
Message::SubmitOrder { order: order_form } => {
|
||||
ActionMessage::SubmitOrder { order: order_form } => {
|
||||
info!("Closing open order...");
|
||||
info!("\tCancelling open order #{}", &active_order.id);
|
||||
self.client.cancel_order(&active_order).await?;
|
||||
@ -539,7 +661,7 @@ impl PairManager {
|
||||
position_manager: PositionManagerHandle::new(
|
||||
pair,
|
||||
client,
|
||||
Box::new(HiddenTrailingStop::new()),
|
||||
Box::new(HiddenTrailingStop::default()),
|
||||
),
|
||||
}
|
||||
}
|
||||
@ -565,9 +687,20 @@ impl PairManager {
|
||||
// TODO: to move into Handler?
|
||||
if let Some(messages) = messages {
|
||||
for m in messages {
|
||||
if let Message::ClosePosition { position_id } = m {
|
||||
match m {
|
||||
ActionMessage::Update { .. } => {}
|
||||
ActionMessage::ClosePosition { position_id } => {
|
||||
self.order_manager.close_position(position_id).await?;
|
||||
}
|
||||
ActionMessage::SubmitOrder { order } => {
|
||||
self.order_manager.submit_order(order).await?;
|
||||
}
|
||||
ActionMessage::ClosePositionOrders { position_id } => {
|
||||
self.order_manager
|
||||
.close_position_orders(position_id)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -265,6 +265,7 @@ pub struct OrderForm {
|
||||
platform: TradingPlatform,
|
||||
amount: f64,
|
||||
leverage: Option<f64>,
|
||||
metadata: Option<OrderMetadata>,
|
||||
}
|
||||
|
||||
impl OrderForm {
|
||||
@ -280,6 +281,7 @@ impl OrderForm {
|
||||
platform,
|
||||
amount,
|
||||
leverage: None,
|
||||
metadata: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -288,6 +290,11 @@ impl OrderForm {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_metadata(mut self, metadata: OrderMetadata) -> Self {
|
||||
self.metadata = Some(metadata);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn pair(&self) -> &SymbolPair {
|
||||
&self.pair
|
||||
}
|
||||
@ -319,6 +326,27 @@ impl OrderForm {
|
||||
pub fn leverage(&self) -> Option<f64> {
|
||||
self.leverage
|
||||
}
|
||||
|
||||
pub fn metadata(&self) -> &Option<OrderMetadata> {
|
||||
&self.metadata
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OrderMetadata {
|
||||
position_id: Option<u64>,
|
||||
}
|
||||
|
||||
impl OrderMetadata {
|
||||
pub fn with_position_id(position_id: u64) -> Self {
|
||||
OrderMetadata {
|
||||
position_id: Some(position_id),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position_id(&self) -> Option<u64> {
|
||||
self.position_id
|
||||
}
|
||||
}
|
||||
|
||||
/***************
|
||||
|
@ -1,13 +1,16 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{Debug, Formatter};
|
||||
|
||||
|
||||
use dyn_clone::DynClone;
|
||||
use log::info;
|
||||
use log::{info};
|
||||
|
||||
use crate::connectors::Connector;
|
||||
use crate::events::{Event, EventKind, EventMetadata, Message};
|
||||
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, Position, PositionProfitState,
|
||||
};
|
||||
use crate::BoxError;
|
||||
|
||||
/***************
|
||||
@ -21,13 +24,13 @@ pub trait PositionStrategy: DynClone + Send + Sync {
|
||||
position: Position,
|
||||
current_tick: u64,
|
||||
positions_history: &HashMap<u64, Position>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||
fn post_tick(
|
||||
&mut self,
|
||||
position: Position,
|
||||
current_tick: u64,
|
||||
positions_history: &HashMap<u64, Position>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||
}
|
||||
|
||||
impl Debug for dyn PositionStrategy {
|
||||
@ -69,50 +72,32 @@ impl Debug for dyn OrderStrategy {
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HiddenTrailingStop {
|
||||
stop_percentages: HashMap<u64, f64>,
|
||||
capital_max_loss: f64,
|
||||
capital_min_profit: f64,
|
||||
capital_good_profit: f64,
|
||||
min_profit_trailing_delta: f64,
|
||||
good_profit_trailing_delta: f64,
|
||||
leverage: f64,
|
||||
min_profit_percentage: f64,
|
||||
good_profit_percentage: f64,
|
||||
max_loss_percentage: f64,
|
||||
}
|
||||
|
||||
impl HiddenTrailingStop {
|
||||
// in percentage
|
||||
const CAPITAL_MAX_LOSS: f64 = 15.0;
|
||||
const CAPITAL_MIN_PROFIT: f64 = 9.0;
|
||||
const CAPITAL_GOOD_PROFIT: f64 = HiddenTrailingStop::CAPITAL_MIN_PROFIT * 2.0;
|
||||
|
||||
// in percentage
|
||||
const MIN_PROFIT_TRAILING_DELTA: f64 = 0.2;
|
||||
const GOOD_PROFIT_TRAILING_DELTA: f64 = 0.1;
|
||||
|
||||
const LEVERAGE: f64 = 15.0;
|
||||
|
||||
const MIN_PROFIT_PERC: f64 = (HiddenTrailingStop::CAPITAL_MIN_PROFIT
|
||||
/ HiddenTrailingStop::LEVERAGE)
|
||||
+ HiddenTrailingStop::MIN_PROFIT_TRAILING_DELTA;
|
||||
const GOOD_PROFIT_PERC: f64 = (HiddenTrailingStop::CAPITAL_GOOD_PROFIT
|
||||
/ HiddenTrailingStop::LEVERAGE)
|
||||
+ HiddenTrailingStop::GOOD_PROFIT_TRAILING_DELTA;
|
||||
const MAX_LOSS_PERC: f64 =
|
||||
-(HiddenTrailingStop::CAPITAL_MAX_LOSS / HiddenTrailingStop::LEVERAGE);
|
||||
|
||||
pub fn new() -> Self {
|
||||
HiddenTrailingStop {
|
||||
stop_percentages: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn update_stop_percentage(&mut self, position: &Position) {
|
||||
if let Some(profit_state) = position.profit_state() {
|
||||
let profit_state_delta = match profit_state {
|
||||
PositionProfitState::MinimumProfit => {
|
||||
Some(HiddenTrailingStop::MIN_PROFIT_TRAILING_DELTA)
|
||||
}
|
||||
PositionProfitState::Profit => Some(HiddenTrailingStop::GOOD_PROFIT_TRAILING_DELTA),
|
||||
PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta),
|
||||
PositionProfitState::Profit => Some(self.good_profit_trailing_delta),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some(profit_state_delta) = profit_state_delta {
|
||||
let current_stop_percentage = position.pl_perc() - profit_state_delta;
|
||||
|
||||
match profit_state {
|
||||
PositionProfitState::MinimumProfit | PositionProfitState::Profit => {
|
||||
if let PositionProfitState::MinimumProfit | PositionProfitState::Profit =
|
||||
profit_state
|
||||
{
|
||||
match self.stop_percentages.get(&position.id()) {
|
||||
None => {
|
||||
self.stop_percentages
|
||||
@ -126,9 +111,8 @@ impl HiddenTrailingStop {
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
info!(
|
||||
"\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}",
|
||||
position.profit_state().unwrap(),
|
||||
@ -141,6 +125,40 @@ impl HiddenTrailingStop {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for HiddenTrailingStop {
|
||||
fn default() -> Self {
|
||||
let leverage = 5.0;
|
||||
|
||||
// in percentage
|
||||
let capital_max_loss = 15.0;
|
||||
let capital_min_profit = 9.0;
|
||||
let capital_good_profit = capital_min_profit * 2.0;
|
||||
|
||||
let weighted_min_profit = capital_min_profit / leverage;
|
||||
let weighted_good_profit = capital_good_profit / leverage;
|
||||
let weighted_max_loss = capital_max_loss / leverage;
|
||||
|
||||
let min_profit_trailing_delta = weighted_min_profit * 0.17;
|
||||
let good_profit_trailing_delta = weighted_good_profit * 0.08;
|
||||
|
||||
let min_profit_percentage = weighted_min_profit + min_profit_trailing_delta;
|
||||
let good_profit_percentage = weighted_good_profit + good_profit_trailing_delta;
|
||||
let max_loss_percentage = -weighted_max_loss;
|
||||
|
||||
HiddenTrailingStop {
|
||||
stop_percentages: Default::default(),
|
||||
capital_max_loss,
|
||||
capital_min_profit,
|
||||
capital_good_profit,
|
||||
min_profit_trailing_delta,
|
||||
good_profit_trailing_delta,
|
||||
leverage,
|
||||
min_profit_percentage,
|
||||
good_profit_percentage,
|
||||
max_loss_percentage,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl PositionStrategy for HiddenTrailingStop {
|
||||
fn name(&self) -> String {
|
||||
"Hidden Trailing Stop".into()
|
||||
@ -152,19 +170,17 @@ impl PositionStrategy for HiddenTrailingStop {
|
||||
position: Position,
|
||||
current_tick: u64,
|
||||
positions_history: &HashMap<u64, Position>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>) {
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||
let pl_perc = position.pl_perc();
|
||||
|
||||
let state = {
|
||||
if pl_perc > HiddenTrailingStop::GOOD_PROFIT_PERC {
|
||||
if pl_perc > self.good_profit_percentage {
|
||||
PositionProfitState::Profit
|
||||
} else if HiddenTrailingStop::MIN_PROFIT_PERC <= pl_perc
|
||||
&& pl_perc < HiddenTrailingStop::GOOD_PROFIT_PERC
|
||||
{
|
||||
} else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) {
|
||||
PositionProfitState::MinimumProfit
|
||||
} else if (0.0..HiddenTrailingStop::MIN_PROFIT_PERC).contains(&pl_perc) {
|
||||
} else if (0.0..self.min_profit_percentage).contains(&pl_perc) {
|
||||
PositionProfitState::BreakEven
|
||||
} else if (HiddenTrailingStop::MAX_LOSS_PERC..0.0).contains(&pl_perc) {
|
||||
} else if (self.max_loss_percentage..0.0).contains(&pl_perc) {
|
||||
PositionProfitState::Loss
|
||||
} else {
|
||||
PositionProfitState::Critical
|
||||
@ -230,8 +246,8 @@ impl PositionStrategy for HiddenTrailingStop {
|
||||
position: Position,
|
||||
_: u64,
|
||||
_: &HashMap<u64, Position>,
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>) {
|
||||
let close_message = Message::ClosePosition {
|
||||
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||
let close_message = ActionMessage::ClosePosition {
|
||||
position_id: position.id(),
|
||||
};
|
||||
|
||||
@ -255,6 +271,263 @@ impl PositionStrategy for HiddenTrailingStop {
|
||||
}
|
||||
}
|
||||
|
||||
// #[derive(Clone, Debug)]
|
||||
// pub struct TrailingStop {
|
||||
// stop_percentages: HashMap<u64, f64>,
|
||||
// capital_max_loss: f64,
|
||||
// capital_min_profit: f64,
|
||||
// capital_good_profit: f64,
|
||||
// min_profit_trailing_delta: f64,
|
||||
// good_profit_trailing_delta: f64,
|
||||
// leverage: f64,
|
||||
// min_profit_percentage: f64,
|
||||
// good_profit_percentage: f64,
|
||||
// max_loss_percentage: f64,
|
||||
// }
|
||||
//
|
||||
// impl TrailingStop {
|
||||
// fn update_stop_percentage(&mut self, position: &Position) -> Option<OrderForm> {
|
||||
// let mut order_form = None;
|
||||
//
|
||||
// if let Some(profit_state) = position.profit_state() {
|
||||
// let profit_state_delta = match profit_state {
|
||||
// PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta),
|
||||
// PositionProfitState::Profit => Some(self.good_profit_trailing_delta),
|
||||
// _ => None,
|
||||
// };
|
||||
//
|
||||
// if let Some(profit_state_delta) = profit_state_delta {
|
||||
// let current_stop_percentage = position.pl_perc() - profit_state_delta;
|
||||
// let price_percentage_delta = {
|
||||
// if position.is_short() {
|
||||
// // 1.0 is the base price
|
||||
// 1.0 - current_stop_percentage / 100.0
|
||||
// } else {
|
||||
// 1.0 + current_stop_percentage / 100.0
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// println!("Delta: {}", price_percentage_delta);
|
||||
//
|
||||
// if let PositionProfitState::MinimumProfit | PositionProfitState::Profit =
|
||||
// profit_state
|
||||
// {
|
||||
// match self.stop_percentages.get(&position.id()) {
|
||||
// None => {
|
||||
// self.stop_percentages
|
||||
// .insert(position.id(), current_stop_percentage);
|
||||
//
|
||||
// trace!("Setting trailing stop, asking order manager to cancel previous orders.");
|
||||
// order_form = Some(
|
||||
// OrderForm::new(
|
||||
// position.pair().clone(),
|
||||
// OrderKind::Limit {
|
||||
// price: position.base_price() * price_percentage_delta,
|
||||
// },
|
||||
// position.platform(),
|
||||
// position.amount().neg(),
|
||||
// )
|
||||
// .with_metadata(OrderMetadata::with_position_id(position.id())),
|
||||
// );
|
||||
// }
|
||||
// Some(existing_threshold) => {
|
||||
// // follow and update trailing stop
|
||||
// if existing_threshold < ¤t_stop_percentage {
|
||||
// self.stop_percentages
|
||||
// .insert(position.id(), current_stop_percentage);
|
||||
//
|
||||
// trace!("Updating threshold, asking order manager to cancel previous orders.");
|
||||
// order_form = Some(
|
||||
// OrderForm::new(
|
||||
// position.pair().clone(),
|
||||
// OrderKind::Limit {
|
||||
// price: position.base_price() * price_percentage_delta,
|
||||
// },
|
||||
// position.platform(),
|
||||
// position.amount().neg(),
|
||||
// )
|
||||
// .with_metadata(OrderMetadata::with_position_id(position.id())),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// info!(
|
||||
// "\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}",
|
||||
// position.profit_state().unwrap(),
|
||||
// position.pl(),
|
||||
// position.pair().quote(),
|
||||
// position.pl_perc(),
|
||||
// self.stop_percentages.get(&position.id()).unwrap_or(&0.0)
|
||||
// );
|
||||
// }
|
||||
//
|
||||
// order_form
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl Default for TrailingStop {
|
||||
// fn default() -> Self {
|
||||
// let leverage = 5.0;
|
||||
//
|
||||
// // in percentage
|
||||
// let capital_max_loss = 15.0;
|
||||
// let capital_min_profit = 1.0;
|
||||
// let capital_good_profit = 6.0;
|
||||
//
|
||||
// let weighted_min_profit = capital_min_profit / leverage;
|
||||
// let weighted_good_profit = capital_good_profit / leverage;
|
||||
// let weighted_max_loss = capital_max_loss / leverage;
|
||||
//
|
||||
// let min_profit_trailing_delta = weighted_min_profit * 0.2;
|
||||
// let good_profit_trailing_delta = weighted_good_profit * 0.08;
|
||||
//
|
||||
// let min_profit_percentage = weighted_min_profit + min_profit_trailing_delta;
|
||||
// let good_profit_percentage = weighted_good_profit + good_profit_trailing_delta;
|
||||
// let max_loss_percentage = -weighted_max_loss;
|
||||
//
|
||||
// TrailingStop {
|
||||
// stop_percentages: Default::default(),
|
||||
// capital_max_loss,
|
||||
// capital_min_profit,
|
||||
// capital_good_profit,
|
||||
// min_profit_trailing_delta,
|
||||
// good_profit_trailing_delta,
|
||||
// leverage,
|
||||
// min_profit_percentage,
|
||||
// good_profit_percentage,
|
||||
// max_loss_percentage,
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// impl PositionStrategy for TrailingStop {
|
||||
// fn name(&self) -> String {
|
||||
// "Trailing Stop".into()
|
||||
// }
|
||||
//
|
||||
// /// Sets the profit state of an open position
|
||||
// fn on_tick(
|
||||
// &mut self,
|
||||
// position: Position,
|
||||
// current_tick: u64,
|
||||
// positions_history: &HashMap<u64, Position>,
|
||||
// ) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||
// let pl_perc = position.pl_perc();
|
||||
//
|
||||
// let state = {
|
||||
// if pl_perc > self.good_profit_percentage {
|
||||
// PositionProfitState::Profit
|
||||
// } else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) {
|
||||
// PositionProfitState::MinimumProfit
|
||||
// } else if (0.0..self.min_profit_percentage).contains(&pl_perc) {
|
||||
// PositionProfitState::BreakEven
|
||||
// } else if (self.max_loss_percentage..0.0).contains(&pl_perc) {
|
||||
// PositionProfitState::Loss
|
||||
// } else {
|
||||
// PositionProfitState::Critical
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// let opt_prev_position = positions_history.get(&(current_tick - 1));
|
||||
// let event_metadata = EventMetadata::new(Some(position.id()), None);
|
||||
// let new_position = position.with_profit_state(Some(state));
|
||||
//
|
||||
// match opt_prev_position {
|
||||
// Some(prev) => {
|
||||
// if prev.profit_state() == Some(state) {
|
||||
// return (new_position, None, None);
|
||||
// }
|
||||
// }
|
||||
// None => return (new_position, None, None),
|
||||
// };
|
||||
//
|
||||
// let events = {
|
||||
// let mut events = vec![];
|
||||
//
|
||||
// if state == PositionProfitState::Profit {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedGoodProfit,
|
||||
// current_tick,
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else if state == PositionProfitState::MinimumProfit {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedMinProfit,
|
||||
// current_tick,
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else if state == PositionProfitState::BreakEven {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedBreakEven,
|
||||
// current_tick,
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else if state == PositionProfitState::Loss {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedLoss,
|
||||
// current_tick,
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// } else {
|
||||
// events.push(Event::new(
|
||||
// EventKind::ReachedMaxLoss,
|
||||
// current_tick,
|
||||
// Some(event_metadata),
|
||||
// ));
|
||||
// }
|
||||
//
|
||||
// events
|
||||
// };
|
||||
//
|
||||
// (new_position, Some(events), None)
|
||||
// }
|
||||
//
|
||||
// fn post_tick(
|
||||
// &mut self,
|
||||
// position: Position,
|
||||
// _: u64,
|
||||
// _: &HashMap<u64, Position>,
|
||||
// ) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||
// let close_message = ActionMessage::ClosePosition {
|
||||
// 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'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]));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // updated or new trailing stop. should cancel orders and submit new one
|
||||
// if let Some(order_form) = self.update_stop_percentage(&position) {
|
||||
// let mut messages = vec![];
|
||||
//
|
||||
// messages.push(ActionMessage::ClosePositionOrders {
|
||||
// position_id: position.id(),
|
||||
// });
|
||||
// messages.push(ActionMessage::SubmitOrder { order: order_form });
|
||||
//
|
||||
// return (position, None, Some(messages));
|
||||
// }
|
||||
//
|
||||
// (position, None, None)
|
||||
// }
|
||||
// }
|
||||
|
||||
/*
|
||||
* ORDER STRATEGIES
|
||||
*/
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MarketEnforce {
|
||||
// threshold (%) for which we trigger a market order
|
||||
@ -264,7 +537,9 @@ pub struct MarketEnforce {
|
||||
|
||||
impl Default for MarketEnforce {
|
||||
fn default() -> Self {
|
||||
Self { threshold: 0.15 }
|
||||
Self {
|
||||
threshold: 1.0 / 15.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -298,7 +573,7 @@ impl OrderStrategy for MarketEnforce {
|
||||
let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0;
|
||||
|
||||
if delta > self.threshold {
|
||||
messages.push(Message::SubmitOrder {
|
||||
messages.push(ActionMessage::SubmitOrder {
|
||||
order: OrderForm::new(
|
||||
order.symbol.clone(),
|
||||
OrderKind::Market,
|
||||
|
Loading…
Reference in New Issue
Block a user