rust #10
@ -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,8 +687,19 @@ impl PairManager {
|
||||
// TODO: to move into Handler?
|
||||
if let Some(messages) = messages {
|
||||
for m in messages {
|
||||
if let Message::ClosePosition { position_id } = m {
|
||||
self.order_manager.close_position(position_id).await?;
|
||||
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,66 +72,47 @@ 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 => {
|
||||
match self.stop_percentages.get(&position.id()) {
|
||||
None => {
|
||||
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);
|
||||
}
|
||||
Some(existing_threshold) => {
|
||||
if existing_threshold < ¤t_stop_percentage {
|
||||
self.stop_percentages
|
||||
.insert(position.id(), current_stop_percentage);
|
||||
}
|
||||
Some(existing_threshold) => {
|
||||
if existing_threshold < ¤t_stop_percentage {
|
||||
self.stop_percentages
|
||||
.insert(position.id(), current_stop_percentage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
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