merge rust
This commit is contained in:
commit
b8f239ec31
@ -17,7 +17,7 @@ use tokio::time::Duration;
|
|||||||
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, TradingPlatform, WalletKind,
|
PositionState, PriceTicker, Trade, TradingFees, TradingPlatform, WalletKind,
|
||||||
};
|
};
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
|
|
||||||
@ -57,21 +57,56 @@ impl Client {
|
|||||||
pair: &SymbolPair,
|
pair: &SymbolPair,
|
||||||
) -> Result<Option<Vec<Position>>, BoxError> {
|
) -> Result<Option<Vec<Position>>, BoxError> {
|
||||||
// retrieving open positions and order book to calculate effective profit/loss
|
// retrieving open positions and order book to calculate effective profit/loss
|
||||||
let (positions, order_book) = tokio::join!(
|
let (positions, order_book, fees) = tokio::join!(
|
||||||
self.inner.active_positions(pair),
|
self.inner.active_positions(pair),
|
||||||
self.inner.order_book(pair)
|
self.inner.order_book(pair),
|
||||||
|
self.inner.trading_fees()
|
||||||
);
|
);
|
||||||
|
|
||||||
let (mut positions, order_book) = (positions?, order_book?);
|
let (mut positions, order_book, fees) = (positions?, order_book?, fees?);
|
||||||
let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid());
|
let (best_ask, best_bid) = (order_book.lowest_ask(), order_book.highest_bid());
|
||||||
|
|
||||||
|
if positions.is_none() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let derivative_taker = fees
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| match x {
|
||||||
|
TradingFees::Taker {
|
||||||
|
platform,
|
||||||
|
percentage,
|
||||||
|
} if platform == &TradingPlatform::Derivative => Some(percentage),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.ok_or("Could not retrieve derivative taker fee!")?;
|
||||||
|
let margin_taker = fees
|
||||||
|
.iter()
|
||||||
|
.filter_map(|x| match x {
|
||||||
|
TradingFees::Taker {
|
||||||
|
platform,
|
||||||
|
percentage,
|
||||||
|
} if platform == &TradingPlatform::Margin => Some(percentage),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.ok_or("Could not retrieve margin taker fee!")?;
|
||||||
|
|
||||||
// updating positions with effective profit/loss
|
// updating positions with effective profit/loss
|
||||||
// TODO: change fee with account's taker fee
|
|
||||||
positions.iter_mut().flatten().for_each(|x| {
|
positions.iter_mut().flatten().for_each(|x| {
|
||||||
|
let fee = match x.platform() {
|
||||||
|
TradingPlatform::Funding | TradingPlatform::Exchange => {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
TradingPlatform::Margin => margin_taker,
|
||||||
|
TradingPlatform::Derivative => derivative_taker,
|
||||||
|
};
|
||||||
|
|
||||||
if x.is_short() {
|
if x.is_short() {
|
||||||
x.update_profit_loss(best_ask, 0.2);
|
x.update_profit_loss(best_ask, *fee);
|
||||||
} else {
|
} else {
|
||||||
x.update_profit_loss(best_bid, 0.2);
|
x.update_profit_loss(best_bid, *fee);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,7 +123,7 @@ impl Client {
|
|||||||
.active_orders(pair)
|
.active_orders(pair)
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| &x.symbol == pair)
|
.filter(|x| &x.pair() == &pair)
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,6 +188,7 @@ pub trait Connector: Send + Sync {
|
|||||||
&self,
|
&self,
|
||||||
pair: &SymbolPair,
|
pair: &SymbolPair,
|
||||||
) -> Result<Option<Vec<OrderDetails>>, BoxError>;
|
) -> Result<Option<Vec<OrderDetails>>, BoxError>;
|
||||||
|
async fn trading_fees(&self) -> Result<Vec<TradingFees>, BoxError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn Connector {
|
impl Debug for dyn Connector {
|
||||||
@ -176,7 +212,7 @@ impl BitfinexConnector {
|
|||||||
if e.to_string().contains("nonce: small") {
|
if e.to_string().contains("nonce: small") {
|
||||||
return RetryPolicy::WaitRetry(Duration::from_millis(1));
|
return RetryPolicy::WaitRetry(Duration::from_millis(1));
|
||||||
}
|
}
|
||||||
return RetryPolicy::ForwardError(e);
|
RetryPolicy::ForwardError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(api_key: &str, api_secret: &str) -> Self {
|
pub fn new(api_key: &str, api_secret: &str) -> Self {
|
||||||
@ -186,7 +222,9 @@ impl BitfinexConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn format_trading_pair(pair: &SymbolPair) -> String {
|
fn format_trading_pair(pair: &SymbolPair) -> String {
|
||||||
if pair.to_string().to_lowercase().contains("test") {
|
if pair.to_string().to_lowercase().contains("test")
|
||||||
|
|| pair.to_string().to_lowercase().contains("f0")
|
||||||
|
{
|
||||||
format!("{}:{}", pair.base(), pair.quote())
|
format!("{}:{}", pair.base(), pair.quote())
|
||||||
} else {
|
} else {
|
||||||
format!("{}{}", pair.base(), pair.quote())
|
format!("{}{}", pair.base(), pair.quote())
|
||||||
@ -275,37 +313,48 @@ impl Connector for BitfinexConnector {
|
|||||||
|
|
||||||
async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
|
async fn submit_order(&self, order: &OrderForm) -> Result<ActiveOrder, BoxError> {
|
||||||
let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair()));
|
let symbol_name = format!("t{}", BitfinexConnector::format_trading_pair(order.pair()));
|
||||||
|
let amount = order.amount();
|
||||||
|
|
||||||
let order_form = match order.kind() {
|
let order_form = {
|
||||||
OrderKind::Limit { price, amount } => {
|
let pre_leverage = {
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
match order.kind() {
|
||||||
|
OrderKind::Limit { price } => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
|
}
|
||||||
|
OrderKind::Market => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
||||||
|
}
|
||||||
|
OrderKind::Stop { price } => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
|
}
|
||||||
|
OrderKind::StopLimit { price, limit_price } => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
|
.with_price_aux_limit(limit_price)?
|
||||||
|
}
|
||||||
|
OrderKind::TrailingStop { distance } => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
||||||
|
.with_price_trailing(distance)?
|
||||||
|
}
|
||||||
|
OrderKind::FillOrKill { price } => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
|
}
|
||||||
|
OrderKind::ImmediateOrCancel { price } => {
|
||||||
|
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.with_meta(OrderMeta::new(
|
||||||
|
BitfinexConnector::AFFILIATE_CODE.to_string(),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
// adding leverage, if any
|
||||||
|
match order.leverage() {
|
||||||
|
// TODO: CHANGEME!!!!
|
||||||
|
Some(_leverage) => pre_leverage.with_leverage(15),
|
||||||
|
// Some(leverage) => pre_leverage.with_leverage(leverage.round() as u32),
|
||||||
|
None => pre_leverage,
|
||||||
}
|
}
|
||||||
OrderKind::Market { amount } => {
|
};
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
|
||||||
}
|
|
||||||
OrderKind::Stop { price, amount } => {
|
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
||||||
}
|
|
||||||
OrderKind::StopLimit {
|
|
||||||
price,
|
|
||||||
amount,
|
|
||||||
limit_price,
|
|
||||||
} => bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
||||||
.with_price_aux_limit(limit_price)?,
|
|
||||||
OrderKind::TrailingStop { distance, amount } => {
|
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, 0.0, amount, order.into())
|
|
||||||
.with_price_trailing(distance)?
|
|
||||||
}
|
|
||||||
OrderKind::FillOrKill { price, amount } => {
|
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
||||||
}
|
|
||||||
OrderKind::ImmediateOrCancel { price, amount } => {
|
|
||||||
bitfinex::orders::OrderForm::new(symbol_name, price, amount, order.into())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.with_meta(OrderMeta::new(
|
|
||||||
BitfinexConnector::AFFILIATE_CODE.to_string(),
|
|
||||||
));
|
|
||||||
|
|
||||||
let response =
|
let response =
|
||||||
BitfinexConnector::retry_nonce(|| self.bfx.orders.submit_order(&order_form)).await?;
|
BitfinexConnector::retry_nonce(|| self.bfx.orders.submit_order(&order_form)).await?;
|
||||||
@ -371,11 +420,55 @@ impl Connector for BitfinexConnector {
|
|||||||
|
|
||||||
Ok((!mapped_vec.is_empty()).then_some(mapped_vec))
|
Ok((!mapped_vec.is_empty()).then_some(mapped_vec))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn trading_fees(&self) -> Result<Vec<TradingFees>, BoxError> {
|
||||||
|
let mut fees = vec![];
|
||||||
|
let accountfees =
|
||||||
|
BitfinexConnector::retry_nonce(|| self.bfx.account.account_summary()).await?;
|
||||||
|
|
||||||
|
// Derivatives
|
||||||
|
let derivative_taker = TradingFees::Taker {
|
||||||
|
platform: TradingPlatform::Derivative,
|
||||||
|
percentage: accountfees.derivative_taker() * 100.0,
|
||||||
|
};
|
||||||
|
let derivative_maker = TradingFees::Maker {
|
||||||
|
platform: TradingPlatform::Derivative,
|
||||||
|
percentage: accountfees.derivative_rebate() * 100.0,
|
||||||
|
};
|
||||||
|
fees.push(derivative_taker);
|
||||||
|
fees.push(derivative_maker);
|
||||||
|
|
||||||
|
// Exchange
|
||||||
|
let exchange_taker = TradingFees::Taker {
|
||||||
|
platform: TradingPlatform::Exchange,
|
||||||
|
percentage: accountfees.taker_to_fiat() * 100.0,
|
||||||
|
};
|
||||||
|
let exchange_maker = TradingFees::Maker {
|
||||||
|
platform: TradingPlatform::Exchange,
|
||||||
|
percentage: accountfees.maker_fee() * 100.0,
|
||||||
|
};
|
||||||
|
fees.push(exchange_taker);
|
||||||
|
fees.push(exchange_maker);
|
||||||
|
|
||||||
|
// Margin
|
||||||
|
let margin_taker = TradingFees::Taker {
|
||||||
|
platform: TradingPlatform::Margin,
|
||||||
|
percentage: accountfees.taker_to_fiat() * 100.0,
|
||||||
|
};
|
||||||
|
let margin_maker = TradingFees::Maker {
|
||||||
|
platform: TradingPlatform::Margin,
|
||||||
|
percentage: accountfees.maker_fee() * 100.0,
|
||||||
|
};
|
||||||
|
fees.push(margin_taker);
|
||||||
|
fees.push(margin_maker);
|
||||||
|
|
||||||
|
Ok(fees)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&ActiveOrder> for CancelOrderForm {
|
impl From<&ActiveOrder> for CancelOrderForm {
|
||||||
fn from(o: &ActiveOrder) -> Self {
|
fn from(o: &ActiveOrder) -> Self {
|
||||||
Self::from_id(o.id)
|
Self::from_id(o.id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -383,20 +476,18 @@ impl TryFrom<&bitfinex::responses::OrderResponse> for ActiveOrder {
|
|||||||
type Error = BoxError;
|
type Error = BoxError;
|
||||||
|
|
||||||
fn try_from(response: &OrderResponse) -> Result<Self, Self::Error> {
|
fn try_from(response: &OrderResponse) -> Result<Self, Self::Error> {
|
||||||
Ok(Self {
|
let pair = SymbolPair::from_str(response.symbol())?;
|
||||||
exchange: Exchange::Bitfinex,
|
|
||||||
id: response.id(),
|
Ok(ActiveOrder::new(
|
||||||
group_id: response.gid(),
|
Exchange::Bitfinex,
|
||||||
client_id: Some(response.cid()),
|
response.id(),
|
||||||
symbol: SymbolPair::from_str(response.symbol())?,
|
pair.clone(),
|
||||||
details: OrderForm::new(
|
OrderForm::new(pair, response.into(), response.into(), response.amount()),
|
||||||
SymbolPair::from_str(response.symbol())?,
|
response.mts_create(),
|
||||||
response.into(),
|
response.mts_update(),
|
||||||
response.into(),
|
)
|
||||||
),
|
.with_group_id(response.gid())
|
||||||
creation_timestamp: 0,
|
.with_client_id(Some(response.cid())))
|
||||||
update_timestamp: 0,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -412,6 +503,15 @@ impl TryInto<Position> for bitfinex::positions::Position {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let platform = {
|
||||||
|
if self.symbol().to_ascii_lowercase().contains("f0") {
|
||||||
|
TradingPlatform::Derivative
|
||||||
|
} else {
|
||||||
|
TradingPlatform::Margin
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("leverage: {}", self.leverage());
|
||||||
Ok(Position::new(
|
Ok(Position::new(
|
||||||
SymbolPair::from_str(self.symbol())?,
|
SymbolPair::from_str(self.symbol())?,
|
||||||
state,
|
state,
|
||||||
@ -421,6 +521,8 @@ impl TryInto<Position> for bitfinex::positions::Position {
|
|||||||
self.pl_perc(),
|
self.pl_perc(),
|
||||||
self.price_liq(),
|
self.price_liq(),
|
||||||
self.position_id(),
|
self.position_id(),
|
||||||
|
platform,
|
||||||
|
self.leverage(),
|
||||||
)
|
)
|
||||||
.with_creation_date(self.mts_create())
|
.with_creation_date(self.mts_create())
|
||||||
.with_creation_update(self.mts_update()))
|
.with_creation_update(self.mts_update()))
|
||||||
@ -439,7 +541,7 @@ impl From<&OrderForm> for bitfinex::orders::OrderKind {
|
|||||||
OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok,
|
OrderKind::FillOrKill { .. } => bitfinex::orders::OrderKind::ExchangeFok,
|
||||||
OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc,
|
OrderKind::ImmediateOrCancel { .. } => bitfinex::orders::OrderKind::ExchangeIoc,
|
||||||
},
|
},
|
||||||
TradingPlatform::Margin => match o.kind() {
|
TradingPlatform::Margin | TradingPlatform::Derivative => match o.kind() {
|
||||||
OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit,
|
OrderKind::Limit { .. } => bitfinex::orders::OrderKind::Limit,
|
||||||
OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market,
|
OrderKind::Market { .. } => bitfinex::orders::OrderKind::Market,
|
||||||
OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop,
|
OrderKind::Stop { .. } => bitfinex::orders::OrderKind::Stop,
|
||||||
@ -489,41 +591,33 @@ impl From<&bitfinex::responses::OrderResponse> for OrderKind {
|
|||||||
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
|
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
|
||||||
Self::Limit {
|
Self::Limit {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
|
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
|
||||||
Self::Market {
|
Self::Market
|
||||||
amount: response.amount(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
|
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
|
||||||
Self::Stop {
|
Self::Stop {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::StopLimit
|
bitfinex::orders::OrderKind::StopLimit
|
||||||
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
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
|
||||||
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
|
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
|
||||||
distance: response.price_trailing().expect("Distance not found!"),
|
distance: response.price_trailing().expect("Distance not found!"),
|
||||||
amount: response.amount(),
|
|
||||||
},
|
},
|
||||||
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
|
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
|
||||||
Self::FillOrKill {
|
Self::FillOrKill {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
|
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
|
||||||
Self::ImmediateOrCancel {
|
Self::ImmediateOrCancel {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -536,41 +630,33 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderKind {
|
|||||||
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
|
bitfinex::orders::OrderKind::Limit | bitfinex::orders::OrderKind::ExchangeLimit => {
|
||||||
Self::Limit {
|
Self::Limit {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
|
bitfinex::orders::OrderKind::Market | bitfinex::orders::OrderKind::ExchangeMarket => {
|
||||||
Self::Market {
|
Self::Market {}
|
||||||
amount: response.amount(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
|
bitfinex::orders::OrderKind::Stop | bitfinex::orders::OrderKind::ExchangeStop => {
|
||||||
Self::Stop {
|
Self::Stop {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::StopLimit
|
bitfinex::orders::OrderKind::StopLimit
|
||||||
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
| bitfinex::orders::OrderKind::ExchangeStopLimit => Self::StopLimit {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
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
|
||||||
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
|
| bitfinex::orders::OrderKind::ExchangeTrailingStop => Self::TrailingStop {
|
||||||
distance: response.price_trailing().expect("Distance not found!"),
|
distance: response.price_trailing().expect("Distance not found!"),
|
||||||
amount: response.amount(),
|
|
||||||
},
|
},
|
||||||
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
|
bitfinex::orders::OrderKind::Fok | bitfinex::orders::OrderKind::ExchangeFok => {
|
||||||
Self::FillOrKill {
|
Self::FillOrKill {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
|
bitfinex::orders::OrderKind::Ioc | bitfinex::orders::OrderKind::ExchangeIoc => {
|
||||||
Self::ImmediateOrCancel {
|
Self::ImmediateOrCancel {
|
||||||
price: response.price(),
|
price: response.price(),
|
||||||
amount: response.amount(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -581,16 +667,16 @@ impl From<&bitfinex::orders::ActiveOrder> for ActiveOrder {
|
|||||||
fn from(order: &bitfinex::orders::ActiveOrder) -> Self {
|
fn from(order: &bitfinex::orders::ActiveOrder) -> Self {
|
||||||
let pair = SymbolPair::from_str(&order.symbol()).expect("Invalid symbol!");
|
let pair = SymbolPair::from_str(&order.symbol()).expect("Invalid symbol!");
|
||||||
|
|
||||||
Self {
|
ActiveOrder::new(
|
||||||
exchange: Exchange::Bitfinex,
|
Exchange::Bitfinex,
|
||||||
id: order.id(),
|
order.id(),
|
||||||
group_id: order.group_id().map(|x| x as u64),
|
pair.clone(),
|
||||||
client_id: Some(order.client_id()),
|
OrderForm::new(pair, order.into(), order.into(), order.amount()),
|
||||||
symbol: pair.clone(),
|
order.creation_timestamp(),
|
||||||
details: OrderForm::new(pair, order.into(), order.into()),
|
order.update_timestamp(),
|
||||||
creation_timestamp: order.creation_timestamp(),
|
)
|
||||||
update_timestamp: order.update_timestamp(),
|
.with_client_id(Some(order.client_id()))
|
||||||
}
|
.with_group_id(order.group_id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,23 +723,23 @@ impl From<&bitfinex::orders::ActiveOrder> for OrderDetails {
|
|||||||
// TODO: fields are hardcoded, to fix
|
// TODO: fields are hardcoded, to fix
|
||||||
impl From<&bitfinex::responses::TradeResponse> for Trade {
|
impl From<&bitfinex::responses::TradeResponse> for Trade {
|
||||||
fn from(response: &TradeResponse) -> Self {
|
fn from(response: &TradeResponse) -> Self {
|
||||||
let pair = SymbolPair::from_str(&response.symbol).unwrap();
|
let pair = SymbolPair::from_str(&response.symbol()).unwrap();
|
||||||
let fee = {
|
let fee = {
|
||||||
if response.is_maker {
|
if response.is_maker() {
|
||||||
OrderFee::Maker(response.fee)
|
OrderFee::Maker(response.fee())
|
||||||
} else {
|
} else {
|
||||||
OrderFee::Taker(response.fee)
|
OrderFee::Taker(response.fee())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
trade_id: response.trade_id,
|
trade_id: response.trade_id(),
|
||||||
pair,
|
pair,
|
||||||
execution_timestamp: response.execution_timestamp,
|
execution_timestamp: response.execution_timestamp(),
|
||||||
price: response.execution_price,
|
price: response.execution_price(),
|
||||||
amount: response.execution_amount,
|
amount: response.execution_amount(),
|
||||||
fee,
|
fee,
|
||||||
fee_currency: Symbol::new(response.symbol.clone()),
|
fee_currency: Symbol::new(response.symbol().to_owned()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,10 +28,17 @@ impl Symbol {
|
|||||||
pub const LTC: Symbol = Symbol::new_static("LTC");
|
pub const LTC: Symbol = Symbol::new_static("LTC");
|
||||||
pub const DOT: Symbol = Symbol::new_static("DOT");
|
pub const DOT: Symbol = Symbol::new_static("DOT");
|
||||||
|
|
||||||
|
pub const DERIV_BTC: Symbol = Symbol::new_static("BTCF0");
|
||||||
|
pub const DERIV_ETH: Symbol = Symbol::new_static("ETHF0");
|
||||||
|
pub const DERIV_USDT: Symbol = Symbol::new_static("USTF0");
|
||||||
|
|
||||||
// Paper trading
|
// Paper trading
|
||||||
pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC");
|
pub const TESTBTC: Symbol = Symbol::new_static("TESTBTC");
|
||||||
pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD");
|
pub const TESTUSD: Symbol = Symbol::new_static("TESTUSD");
|
||||||
|
|
||||||
|
pub const DERIV_TESTBTC: Symbol = Symbol::new_static("TESTBTCF0");
|
||||||
|
pub const DERIV_TESTUSDT: Symbol = Symbol::new_static("TESTUSDTF0");
|
||||||
|
|
||||||
// Fiat coins
|
// Fiat coins
|
||||||
pub const USD: Symbol = Symbol::new_static("USD");
|
pub const USD: Symbol = Symbol::new_static("USD");
|
||||||
pub const GBP: Symbol = Symbol::new_static("GBP");
|
pub const GBP: Symbol = Symbol::new_static("GBP");
|
||||||
|
@ -5,15 +5,16 @@ use crate::models::OrderForm;
|
|||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ActorMessage {
|
pub struct ActorMessage {
|
||||||
pub(crate) message: Message,
|
pub(crate) message: ActionMessage,
|
||||||
pub(crate) respond_to: oneshot::Sender<OptionUpdate>,
|
pub(crate) respond_to: oneshot::Sender<OptionUpdate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Message {
|
pub enum ActionMessage {
|
||||||
Update { tick: u64 },
|
Update { tick: u64 },
|
||||||
ClosePosition { position_id: u64 },
|
ClosePosition { position_id: u64 },
|
||||||
SubmitOrder { order: OrderForm },
|
SubmitOrder { order: OrderForm },
|
||||||
|
ClosePositionOrders { position_id: u64 },
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
#![feature(drain_filter)]
|
#![feature(drain_filter)]
|
||||||
#![feature(bool_to_option)]
|
#![feature(bool_to_option)]
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
use fern::colors::{Color, ColoredLevelConfig};
|
use fern::colors::{Color, ColoredLevelConfig};
|
||||||
use log::LevelFilter::{Debug, Trace};
|
use log::LevelFilter::{Debug, Trace};
|
||||||
use tokio::time::Duration;
|
use tokio::time::Duration;
|
||||||
@ -23,33 +25,27 @@ pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), BoxError> {
|
async fn main() -> Result<(), BoxError> {
|
||||||
setup_logger()?;
|
setup_logger()?;
|
||||||
|
dotenv::dotenv()?;
|
||||||
|
|
||||||
// TEST
|
let api_key = env::vars()
|
||||||
let test_api_key = "P1EVE68DJByDAkGQvpIkTwfrbYXd2Vo2ZaIhTYb9vx2";
|
.find(|(k, v)| k == "API_KEY")
|
||||||
let test_api_secret = "1nicg8z0zKVEt5Rb7ZDpIYjVYVTgvCaCPMZqB0niFli";
|
.map(|(k, v)| v)
|
||||||
|
.ok_or("API_KEY not set!")?;
|
||||||
// REAL
|
let api_secret = env::vars()
|
||||||
// let orders_api_key = "hc5nDvYbFYJZMKdnzYq8P4AzCSwjxfQHnMyrg69Sf4c";
|
.find(|(k, v)| k == "API_SECRET")
|
||||||
// let orders_api_secret = "53x9goIOpbOtBoPi7dmigK5Cq5e0282EUO2qRIMEXlh";
|
.map(|(k, v)| v)
|
||||||
// let prices_api_key = "gTfFZUCwRBE0Z9FZjyk9HNe4lZ7XuiZY9rrW71SyUr9";
|
.ok_or("API_SECRET not set!")?;
|
||||||
// let prices_api_secret = "zWbxvoFZad3BPIiXK4DKfEvC0YsAuaApbeAyI8OBXgN";
|
|
||||||
// let positions_api_key = "PfR7BadPZPNdVZnkHFBfAjsg7gjt8pAecMj5B8eRPFi";
|
|
||||||
// let positions_api_secret = "izzvxtE3XsBBRpVCHGJ8f60UA56SmPNbBvJGVd67aqD";
|
|
||||||
|
|
||||||
let bitfinex = ExchangeDetails::Bitfinex {
|
let bitfinex = ExchangeDetails::Bitfinex {
|
||||||
prices_api_key: test_api_key.into(),
|
api_key: api_key.into(),
|
||||||
prices_api_secret: test_api_secret.into(),
|
api_secret: api_secret.into(),
|
||||||
orders_api_key: test_api_key.into(),
|
|
||||||
orders_api_secret: test_api_secret.into(),
|
|
||||||
positions_api_key: test_api_key.into(),
|
|
||||||
positions_api_secret: test_api_secret.into(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bot = BfxBot::new(
|
let mut bot = BfxBot::new(
|
||||||
vec![bitfinex],
|
vec![bitfinex],
|
||||||
vec![Symbol::BTC, Symbol::ETH, Symbol::XMR],
|
vec![Symbol::DERIV_ETH, Symbol::DERIV_BTC],
|
||||||
Symbol::USD,
|
Symbol::DERIV_USDT,
|
||||||
Duration::new(1, 0),
|
Duration::new(10, 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(bot.start_loop().await?)
|
Ok(bot.start_loop().await?)
|
||||||
|
@ -12,14 +12,14 @@ use tokio::time::Duration;
|
|||||||
|
|
||||||
use crate::connectors::{Client, ExchangeDetails};
|
use crate::connectors::{Client, ExchangeDetails};
|
||||||
use crate::currency::SymbolPair;
|
use crate::currency::SymbolPair;
|
||||||
use crate::events::{ActorMessage, Event, Message};
|
use crate::events::{ActionMessage, ActorMessage, Event};
|
||||||
use crate::models::{
|
use crate::models::{
|
||||||
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker, TradingPlatform,
|
ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PriceTicker,
|
||||||
};
|
};
|
||||||
use crate::strategy::{FastOrderStrategy, OrderStrategy, PositionStrategy, TrailingStop};
|
use crate::strategy::{HiddenTrailingStop, MarketEnforce, OrderStrategy, PositionStrategy};
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
|
|
||||||
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<Message>>);
|
pub type OptionUpdate = (Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||||
|
|
||||||
/******************
|
/******************
|
||||||
* PRICES
|
* PRICES
|
||||||
@ -33,6 +33,48 @@ pub struct PriceManager {
|
|||||||
client: Client,
|
client: Client,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PriceManager {
|
||||||
|
pub fn new(receiver: Receiver<ActorMessage>, pair: SymbolPair, client: Client) -> Self {
|
||||||
|
PriceManager {
|
||||||
|
receiver,
|
||||||
|
pair,
|
||||||
|
prices: Vec::new(),
|
||||||
|
client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> {
|
||||||
|
if let ActionMessage::Update { tick } = message.message {
|
||||||
|
let a = self.update(tick).await?;
|
||||||
|
self.add_entry(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(message
|
||||||
|
.respond_to
|
||||||
|
.send((None, None))
|
||||||
|
.map_err(|_| BoxError::from("Could not send message."))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_entry(&mut self, entry: PriceEntry) {
|
||||||
|
self.prices.push(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update(&mut self, tick: u64) -> Result<PriceEntry, BoxError> {
|
||||||
|
let current_prices = self.client.current_prices(&self.pair).await?.into();
|
||||||
|
|
||||||
|
Ok(PriceEntry::new(
|
||||||
|
tick,
|
||||||
|
current_prices,
|
||||||
|
self.pair.clone(),
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pair(&self) -> &SymbolPair {
|
||||||
|
&self.pair
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct PriceManagerHandle {
|
pub struct PriceManagerHandle {
|
||||||
sender: Sender<ActorMessage>,
|
sender: Sender<ActorMessage>,
|
||||||
}
|
}
|
||||||
@ -58,7 +100,7 @@ impl PriceManagerHandle {
|
|||||||
|
|
||||||
self.sender
|
self.sender
|
||||||
.send(ActorMessage {
|
.send(ActorMessage {
|
||||||
message: Message::Update { tick },
|
message: ActionMessage::Update { tick },
|
||||||
respond_to: send,
|
respond_to: send,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -67,51 +109,6 @@ impl PriceManagerHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PriceManager {
|
|
||||||
pub fn new(receiver: Receiver<ActorMessage>, pair: SymbolPair, client: Client) -> Self {
|
|
||||||
PriceManager {
|
|
||||||
receiver,
|
|
||||||
pair,
|
|
||||||
prices: Vec::new(),
|
|
||||||
client,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn handle_message(&mut self, message: ActorMessage) -> Result<(), BoxError> {
|
|
||||||
match message.message {
|
|
||||||
Message::Update { tick } => {
|
|
||||||
let a = self.update(tick).await?;
|
|
||||||
self.add_entry(a);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(message
|
|
||||||
.respond_to
|
|
||||||
.send((None, None))
|
|
||||||
.map_err(|_| BoxError::from("Could not send message."))?)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_entry(&mut self, entry: PriceEntry) {
|
|
||||||
self.prices.push(entry);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update(&mut self, tick: u64) -> Result<PriceEntry, BoxError> {
|
|
||||||
let current_prices = self.client.current_prices(&self.pair).await?.into();
|
|
||||||
|
|
||||||
Ok(PriceEntry::new(
|
|
||||||
tick,
|
|
||||||
current_prices,
|
|
||||||
self.pair.clone(),
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pair(&self) -> &SymbolPair {
|
|
||||||
&self.pair
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct PriceEntry {
|
pub struct PriceEntry {
|
||||||
tick: u64,
|
tick: u64,
|
||||||
@ -179,7 +176,7 @@ impl PositionManagerHandle {
|
|||||||
|
|
||||||
self.sender
|
self.sender
|
||||||
.send(ActorMessage {
|
.send(ActorMessage {
|
||||||
message: Message::Update { tick },
|
message: ActionMessage::Update { tick },
|
||||||
respond_to: send,
|
respond_to: send,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -224,7 +221,7 @@ impl PositionManager {
|
|||||||
|
|
||||||
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
|
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
|
||||||
let (events, messages) = match msg.message {
|
let (events, messages) = match msg.message {
|
||||||
Message::Update { tick } => self.update(tick).await?,
|
ActionMessage::Update { tick } => self.update(tick).await?,
|
||||||
_ => (None, None),
|
_ => (None, None),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -247,11 +244,7 @@ impl PositionManager {
|
|||||||
|
|
||||||
Some(positions) => {
|
Some(positions) => {
|
||||||
// checking if there are positions open for our pair
|
// checking if there are positions open for our pair
|
||||||
match positions
|
match positions.into_iter().find(|x| x.pair() == &self.pair) {
|
||||||
.into_iter()
|
|
||||||
.filter(|x| x.pair() == &self.pair)
|
|
||||||
.next()
|
|
||||||
{
|
|
||||||
// no open positions for our pair, setting active position to none
|
// no open positions for our pair, setting active position to none
|
||||||
None => {
|
None => {
|
||||||
self.active_position = None;
|
self.active_position = None;
|
||||||
@ -300,10 +293,7 @@ impl PositionManager {
|
|||||||
None => self.current_tick() - 1,
|
None => self.current_tick() - 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
self.positions_history
|
self.positions_history.get(&tick).filter(|x| x.id() == id)
|
||||||
.get(&tick)
|
|
||||||
.filter(|x| x.id() == id)
|
|
||||||
.and_then(|x| Some(x))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +302,7 @@ impl PositionManager {
|
|||||||
******************/
|
******************/
|
||||||
|
|
||||||
// Position ID: Order ID
|
// Position ID: Order ID
|
||||||
pub type TrackedPositionsMap = HashMap<u64, u64>;
|
pub type TrackedPositionsMap = HashMap<u64, Vec<u64>>;
|
||||||
|
|
||||||
pub struct OrderManagerHandle {
|
pub struct OrderManagerHandle {
|
||||||
sender: Sender<ActorMessage>,
|
sender: Sender<ActorMessage>,
|
||||||
@ -354,7 +344,36 @@ impl OrderManagerHandle {
|
|||||||
|
|
||||||
self.sender
|
self.sender
|
||||||
.send(ActorMessage {
|
.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,
|
respond_to: send,
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
@ -391,9 +410,14 @@ impl OrderManager {
|
|||||||
|
|
||||||
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
|
pub async fn handle_message(&mut self, msg: ActorMessage) -> Result<(), BoxError> {
|
||||||
let (events, messages) = match msg.message {
|
let (events, messages) = match msg.message {
|
||||||
Message::Update { .. } => self.update().await?,
|
ActionMessage::Update { .. } => self.update().await?,
|
||||||
Message::ClosePosition { position_id } => self.close_position(position_id).await?,
|
ActionMessage::ClosePosition { position_id } => {
|
||||||
_ => (None, None),
|
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
|
Ok(msg
|
||||||
@ -402,6 +426,53 @@ impl OrderManager {
|
|||||||
.map_err(|_| BoxError::from("Could not send message."))?)
|
.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> {
|
pub async fn close_position(&mut self, position_id: u64) -> Result<OptionUpdate, BoxError> {
|
||||||
info!("Closing position #{}", position_id);
|
info!("Closing position #{}", position_id);
|
||||||
|
|
||||||
@ -421,25 +492,28 @@ impl OrderManager {
|
|||||||
if let Some(position) = open_positions.into_iter().find(|x| x.id() == position_id) {
|
if let Some(position) = open_positions.into_iter().find(|x| x.id() == position_id) {
|
||||||
let opt_position_order = open_orders
|
let opt_position_order = open_orders
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.details.amount().neg() == position.amount());
|
// avoid using direct equality, using error margin instead
|
||||||
|
.find(|x| {
|
||||||
|
(x.order_form().amount().neg() - position.amount()).abs() < 0.0000001
|
||||||
|
});
|
||||||
|
|
||||||
// checking if the position has an open order.
|
// checking if the position has an open order.
|
||||||
// If so, don't do anything since the order is taken care of
|
// If so, don't do anything since the order is taken care of
|
||||||
// in the update phase.
|
// in the update phase.
|
||||||
// If no order is open, send an undercut limit order at the best current price.
|
// If no order is open, send an undercut limit order at the best current price.
|
||||||
if let None = opt_position_order {
|
if opt_position_order.is_none() {
|
||||||
// No open order, undercutting best price with limit order
|
// No open order, undercutting best price with limit order
|
||||||
let closing_price = self.best_closing_price(&position, &order_book);
|
let closing_price = self.best_closing_price(&position, &order_book);
|
||||||
|
|
||||||
// TODO: hardcoded platform to Margin!
|
|
||||||
let order_form = OrderForm::new(
|
let order_form = OrderForm::new(
|
||||||
self.pair.clone(),
|
self.pair.clone(),
|
||||||
OrderKind::Limit {
|
OrderKind::Limit {
|
||||||
price: closing_price,
|
price: closing_price,
|
||||||
amount: position.amount().neg(),
|
|
||||||
},
|
},
|
||||||
TradingPlatform::Margin,
|
position.platform(),
|
||||||
);
|
position.amount().neg(),
|
||||||
|
)
|
||||||
|
.with_leverage(Some(position.leverage()));
|
||||||
|
|
||||||
info!("Submitting {} order", order_form.kind());
|
info!("Submitting {} order", order_form.kind());
|
||||||
if let Err(e) = self.client.submit_order(&order_form).await {
|
if let Err(e) = self.client.submit_order(&order_form).await {
|
||||||
@ -458,8 +532,8 @@ impl OrderManager {
|
|||||||
Ok((None, None))
|
Ok((None, None))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update(&self) -> Result<OptionUpdate, BoxError> {
|
pub async fn update(&mut self) -> Result<OptionUpdate, BoxError> {
|
||||||
trace!("\t[OrderManager] Updating {}", self.pair);
|
debug!("\t[OrderManager] Updating {}", self.pair);
|
||||||
|
|
||||||
let (res_open_orders, res_order_book) = tokio::join!(
|
let (res_open_orders, res_order_book) = tokio::join!(
|
||||||
self.client.active_orders(&self.pair),
|
self.client.active_orders(&self.pair),
|
||||||
@ -468,8 +542,49 @@ impl OrderManager {
|
|||||||
|
|
||||||
let (open_orders, order_book) = (res_open_orders?, res_order_book?);
|
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.order_form().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 {
|
for active_order in open_orders {
|
||||||
debug!(
|
trace!(
|
||||||
"Found open order, calling \"{}\" strategy.",
|
"Found open order, calling \"{}\" strategy.",
|
||||||
self.strategy.name()
|
self.strategy.name()
|
||||||
);
|
);
|
||||||
@ -479,9 +594,9 @@ impl OrderManager {
|
|||||||
if let Some(messages) = strat_messages {
|
if let Some(messages) = strat_messages {
|
||||||
for m in messages {
|
for m in messages {
|
||||||
match m {
|
match m {
|
||||||
Message::SubmitOrder { order: order_form } => {
|
ActionMessage::SubmitOrder { order: order_form } => {
|
||||||
info!("Closing open order...");
|
info!("Closing open order...");
|
||||||
info!("\tCancelling open order #{}", &active_order.id);
|
info!("\tCancelling open order #{}", &active_order.id());
|
||||||
self.client.cancel_order(&active_order).await?;
|
self.client.cancel_order(&active_order).await?;
|
||||||
|
|
||||||
info!("\tSubmitting {}...", order_form.kind());
|
info!("\tSubmitting {}...", order_form.kind());
|
||||||
@ -508,26 +623,22 @@ impl OrderManager {
|
|||||||
let delta = (ask - bid) / 10.0;
|
let delta = (ask - bid) / 10.0;
|
||||||
|
|
||||||
let closing_price = {
|
let closing_price = {
|
||||||
let closing_price = {
|
if position.is_short() {
|
||||||
if position.is_short() {
|
bid - delta
|
||||||
bid - delta
|
|
||||||
} else {
|
|
||||||
ask + delta
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if avg > 9999.0 {
|
|
||||||
if position.is_short() {
|
|
||||||
closing_price.ceil()
|
|
||||||
} else {
|
|
||||||
closing_price.floor()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
closing_price
|
ask + delta
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
closing_price
|
if avg > 9999.0 {
|
||||||
|
if position.is_short() {
|
||||||
|
closing_price.ceil()
|
||||||
|
} else {
|
||||||
|
closing_price.floor()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
closing_price
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -546,12 +657,12 @@ impl PairManager {
|
|||||||
order_manager: OrderManagerHandle::new(
|
order_manager: OrderManagerHandle::new(
|
||||||
pair.clone(),
|
pair.clone(),
|
||||||
client.clone(),
|
client.clone(),
|
||||||
Box::new(FastOrderStrategy::default()),
|
Box::new(MarketEnforce::default()),
|
||||||
),
|
),
|
||||||
position_manager: PositionManagerHandle::new(
|
position_manager: PositionManagerHandle::new(
|
||||||
pair.clone(),
|
pair,
|
||||||
client.clone(),
|
client,
|
||||||
Box::new(TrailingStop::new()),
|
Box::new(HiddenTrailingStop::default()),
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -578,10 +689,18 @@ impl PairManager {
|
|||||||
if let Some(messages) = messages {
|
if let Some(messages) = messages {
|
||||||
for m in messages {
|
for m in messages {
|
||||||
match m {
|
match m {
|
||||||
Message::ClosePosition { position_id } => {
|
ActionMessage::Update { .. } => {}
|
||||||
|
ActionMessage::ClosePosition { position_id } => {
|
||||||
self.order_manager.close_position(position_id).await?;
|
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?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -593,21 +712,19 @@ impl PairManager {
|
|||||||
pub struct ExchangeManager {
|
pub struct ExchangeManager {
|
||||||
kind: ExchangeDetails,
|
kind: ExchangeDetails,
|
||||||
pair_managers: Vec<PairManager>,
|
pair_managers: Vec<PairManager>,
|
||||||
client: Client,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExchangeManager {
|
impl ExchangeManager {
|
||||||
pub fn new(kind: &ExchangeDetails, pairs: &Vec<SymbolPair>) -> Self {
|
pub fn new(kind: &ExchangeDetails, pairs: &[SymbolPair]) -> Self {
|
||||||
let client = Client::new(kind);
|
let client = Client::new(kind);
|
||||||
let pair_managers = pairs
|
let pair_managers = pairs
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|x| PairManager::new(x.clone(), client.clone()))
|
.map(|x| PairManager::new(x.clone(), client.clone()))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
kind: kind.clone(),
|
kind: kind.clone(),
|
||||||
pair_managers,
|
pair_managers,
|
||||||
client,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -619,7 +736,7 @@ impl ExchangeManager {
|
|||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// execute the futures
|
// execute the futures
|
||||||
while let Some(_) = futures.next().await {}
|
while futures.next().await.is_some() {}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -139,18 +139,6 @@ impl OrderDetails {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exchange(&self) -> Exchange {
|
|
||||||
self.exchange
|
|
||||||
}
|
|
||||||
pub fn platform(&self) -> TradingPlatform {
|
|
||||||
self.platform
|
|
||||||
}
|
|
||||||
pub fn kind(&self) -> OrderKind {
|
|
||||||
self.kind
|
|
||||||
}
|
|
||||||
pub fn execution_timestamp(&self) -> u64 {
|
|
||||||
self.execution_timestamp
|
|
||||||
}
|
|
||||||
pub fn id(&self) -> u64 {
|
pub fn id(&self) -> u64 {
|
||||||
self.id
|
self.id
|
||||||
}
|
}
|
||||||
@ -162,14 +150,71 @@ impl OrderDetails {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ActiveOrder {
|
pub struct ActiveOrder {
|
||||||
pub(crate) exchange: Exchange,
|
exchange: Exchange,
|
||||||
pub(crate) id: u64,
|
id: u64,
|
||||||
pub(crate) group_id: Option<u64>,
|
group_id: Option<u64>,
|
||||||
pub(crate) client_id: Option<u64>,
|
client_id: Option<u64>,
|
||||||
pub(crate) symbol: SymbolPair,
|
pair: SymbolPair,
|
||||||
pub(crate) details: OrderForm,
|
order_form: OrderForm,
|
||||||
pub(crate) creation_timestamp: u64,
|
creation_timestamp: u64,
|
||||||
pub(crate) update_timestamp: u64,
|
update_timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ActiveOrder {
|
||||||
|
pub fn new(
|
||||||
|
exchange: Exchange,
|
||||||
|
id: u64,
|
||||||
|
pair: SymbolPair,
|
||||||
|
order_form: OrderForm,
|
||||||
|
creation_timestamp: u64,
|
||||||
|
update_timestamp: u64,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
exchange,
|
||||||
|
id,
|
||||||
|
group_id: None,
|
||||||
|
client_id: None,
|
||||||
|
pair,
|
||||||
|
order_form,
|
||||||
|
creation_timestamp,
|
||||||
|
update_timestamp,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_group_id(mut self, group_id: Option<u64>) -> Self {
|
||||||
|
self.group_id = group_id;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_client_id(mut self, client_id: Option<u64>) -> Self {
|
||||||
|
self.client_id = client_id;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exchange(&self) -> Exchange {
|
||||||
|
self.exchange
|
||||||
|
}
|
||||||
|
pub fn id(&self) -> u64 {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
pub fn group_id(&self) -> Option<u64> {
|
||||||
|
self.group_id
|
||||||
|
}
|
||||||
|
pub fn client_id(&self) -> Option<u64> {
|
||||||
|
self.client_id
|
||||||
|
}
|
||||||
|
pub fn pair(&self) -> &SymbolPair {
|
||||||
|
&self.pair
|
||||||
|
}
|
||||||
|
pub fn order_form(&self) -> &OrderForm {
|
||||||
|
&self.order_form
|
||||||
|
}
|
||||||
|
pub fn creation_timestamp(&self) -> u64 {
|
||||||
|
self.creation_timestamp
|
||||||
|
}
|
||||||
|
pub fn update_timestamp(&self) -> u64 {
|
||||||
|
self.update_timestamp
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for ActiveOrder {
|
impl Hash for ActiveOrder {
|
||||||
@ -186,7 +231,7 @@ impl PartialEq for ActiveOrder {
|
|||||||
|
|
||||||
impl Eq for ActiveOrder {}
|
impl Eq for ActiveOrder {}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||||
pub enum TradingPlatform {
|
pub enum TradingPlatform {
|
||||||
Exchange,
|
Exchange,
|
||||||
Derivative,
|
Derivative,
|
||||||
@ -213,34 +258,13 @@ impl Display for TradingPlatform {
|
|||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum OrderKind {
|
pub enum OrderKind {
|
||||||
Limit {
|
Limit { price: f64 },
|
||||||
price: f64,
|
Market,
|
||||||
amount: f64,
|
Stop { price: f64 },
|
||||||
},
|
StopLimit { price: f64, limit_price: f64 },
|
||||||
Market {
|
TrailingStop { distance: f64 },
|
||||||
amount: f64,
|
FillOrKill { price: f64 },
|
||||||
},
|
ImmediateOrCancel { price: f64 },
|
||||||
Stop {
|
|
||||||
price: f64,
|
|
||||||
amount: f64,
|
|
||||||
},
|
|
||||||
StopLimit {
|
|
||||||
price: f64,
|
|
||||||
amount: f64,
|
|
||||||
limit_price: f64,
|
|
||||||
},
|
|
||||||
TrailingStop {
|
|
||||||
distance: f64,
|
|
||||||
amount: f64,
|
|
||||||
},
|
|
||||||
FillOrKill {
|
|
||||||
price: f64,
|
|
||||||
amount: f64,
|
|
||||||
},
|
|
||||||
ImmediateOrCancel {
|
|
||||||
price: f64,
|
|
||||||
amount: f64,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrderKind {
|
impl OrderKind {
|
||||||
@ -260,67 +284,32 @@ impl OrderKind {
|
|||||||
impl Display for OrderKind {
|
impl Display for OrderKind {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
OrderKind::Limit { price, amount } => {
|
OrderKind::Limit { price } => {
|
||||||
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,)
|
||||||
|
}
|
||||||
|
OrderKind::Market => {
|
||||||
|
write!(f, "[{}]", self.as_str())
|
||||||
|
}
|
||||||
|
OrderKind::Stop { price } => {
|
||||||
|
write!(f, "[{} | Price: {:0.5}", self.as_str(), price,)
|
||||||
|
}
|
||||||
|
OrderKind::StopLimit { price, limit_price } => {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
"[{} | Price: {:0.5}, Limit Price: {:0.5}]",
|
||||||
self.as_str(),
|
self.as_str(),
|
||||||
price,
|
price,
|
||||||
amount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
OrderKind::Market { amount } => {
|
|
||||||
write!(f, "[{} | Amount: {:0.5}]", self.as_str(), amount)
|
|
||||||
}
|
|
||||||
OrderKind::Stop { price, amount } => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
||||||
self.as_str(),
|
|
||||||
price,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
OrderKind::StopLimit {
|
|
||||||
price,
|
|
||||||
amount,
|
|
||||||
limit_price,
|
|
||||||
} => {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"[{} | Price: {:0.5}, Amount: {:0.5}, Limit Price: {:0.5}]",
|
|
||||||
self.as_str(),
|
|
||||||
price,
|
|
||||||
amount,
|
|
||||||
limit_price
|
limit_price
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
OrderKind::TrailingStop { distance, amount } => {
|
OrderKind::TrailingStop { distance } => {
|
||||||
write!(
|
write!(f, "[{} | Distance: {:0.5}]", self.as_str(), distance,)
|
||||||
f,
|
|
||||||
"[{} | Distance: {:0.5}, Amount: {:0.5}]",
|
|
||||||
self.as_str(),
|
|
||||||
distance,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
OrderKind::FillOrKill { price, amount } => {
|
OrderKind::FillOrKill { price } => {
|
||||||
write!(
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,)
|
||||||
f,
|
|
||||||
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
||||||
self.as_str(),
|
|
||||||
price,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
OrderKind::ImmediateOrCancel { price, amount } => {
|
OrderKind::ImmediateOrCancel { price } => {
|
||||||
write!(
|
write!(f, "[{} | Price: {:0.5}]", self.as_str(), price,)
|
||||||
f,
|
|
||||||
"[{} | Price: {:0.5}, Amount: {:0.5}]",
|
|
||||||
self.as_str(),
|
|
||||||
price,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -331,17 +320,38 @@ pub struct OrderForm {
|
|||||||
pair: SymbolPair,
|
pair: SymbolPair,
|
||||||
kind: OrderKind,
|
kind: OrderKind,
|
||||||
platform: TradingPlatform,
|
platform: TradingPlatform,
|
||||||
|
amount: f64,
|
||||||
|
leverage: Option<f64>,
|
||||||
|
metadata: Option<OrderMetadata>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OrderForm {
|
impl OrderForm {
|
||||||
pub fn new(pair: SymbolPair, order_kind: OrderKind, platform: TradingPlatform) -> Self {
|
pub fn new(
|
||||||
|
pair: SymbolPair,
|
||||||
|
order_kind: OrderKind,
|
||||||
|
platform: TradingPlatform,
|
||||||
|
amount: f64,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
pair,
|
pair,
|
||||||
kind: order_kind,
|
kind: order_kind,
|
||||||
platform,
|
platform,
|
||||||
|
amount,
|
||||||
|
leverage: None,
|
||||||
|
metadata: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn with_leverage(mut self, leverage: Option<f64>) -> Self {
|
||||||
|
self.leverage = leverage;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_metadata(mut self, metadata: Option<OrderMetadata>) -> Self {
|
||||||
|
self.metadata = metadata;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pair(&self) -> &SymbolPair {
|
pub fn pair(&self) -> &SymbolPair {
|
||||||
&self.pair
|
&self.pair
|
||||||
}
|
}
|
||||||
@ -355,15 +365,7 @@ impl OrderForm {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn amount(&self) -> f64 {
|
pub fn amount(&self) -> f64 {
|
||||||
match self.kind {
|
self.amount
|
||||||
OrderKind::Limit { amount, .. } => amount,
|
|
||||||
OrderKind::Market { amount } => amount,
|
|
||||||
OrderKind::Stop { amount, .. } => amount,
|
|
||||||
OrderKind::StopLimit { amount, .. } => amount,
|
|
||||||
OrderKind::TrailingStop { amount, .. } => amount,
|
|
||||||
OrderKind::FillOrKill { amount, .. } => amount,
|
|
||||||
OrderKind::ImmediateOrCancel { amount, .. } => amount,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn price(&self) -> Option<f64> {
|
pub fn price(&self) -> Option<f64> {
|
||||||
@ -377,6 +379,31 @@ impl OrderForm {
|
|||||||
OrderKind::ImmediateOrCancel { price, .. } => Some(price),
|
OrderKind::ImmediateOrCancel { price, .. } => Some(price),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
@ -396,6 +423,8 @@ pub struct Position {
|
|||||||
position_id: u64,
|
position_id: u64,
|
||||||
creation_date: Option<u64>,
|
creation_date: Option<u64>,
|
||||||
creation_update: Option<u64>,
|
creation_update: Option<u64>,
|
||||||
|
platform: TradingPlatform,
|
||||||
|
leverage: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Position {
|
impl Position {
|
||||||
@ -408,6 +437,8 @@ impl Position {
|
|||||||
pl_perc: f64,
|
pl_perc: f64,
|
||||||
price_liq: f64,
|
price_liq: f64,
|
||||||
position_id: u64,
|
position_id: u64,
|
||||||
|
platform: TradingPlatform,
|
||||||
|
leverage: f64,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Position {
|
Position {
|
||||||
pair,
|
pair,
|
||||||
@ -421,6 +452,8 @@ impl Position {
|
|||||||
creation_date: None,
|
creation_date: None,
|
||||||
creation_update: None,
|
creation_update: None,
|
||||||
profit_state: None,
|
profit_state: None,
|
||||||
|
platform,
|
||||||
|
leverage,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -505,6 +538,12 @@ impl Position {
|
|||||||
pub fn is_long(&self) -> bool {
|
pub fn is_long(&self) -> bool {
|
||||||
self.amount.is_sign_positive()
|
self.amount.is_sign_positive()
|
||||||
}
|
}
|
||||||
|
pub fn platform(&self) -> TradingPlatform {
|
||||||
|
self.platform
|
||||||
|
}
|
||||||
|
pub fn leverage(&self) -> f64 {
|
||||||
|
self.leverage
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for Position {
|
impl Hash for Position {
|
||||||
@ -530,17 +569,6 @@ pub enum PositionProfitState {
|
|||||||
Profit,
|
Profit,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PositionProfitState {
|
|
||||||
fn color(self) -> String {
|
|
||||||
match self {
|
|
||||||
PositionProfitState::Critical | PositionProfitState::Loss => "red",
|
|
||||||
PositionProfitState::BreakEven => "yellow",
|
|
||||||
PositionProfitState::MinimumProfit | PositionProfitState::Profit => "green",
|
|
||||||
}
|
|
||||||
.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||||
pub enum PositionState {
|
pub enum PositionState {
|
||||||
Closed,
|
Closed,
|
||||||
@ -563,3 +591,15 @@ pub struct Trade {
|
|||||||
pub fee: OrderFee,
|
pub fee: OrderFee,
|
||||||
pub fee_currency: Symbol,
|
pub fee_currency: Symbol,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum TradingFees {
|
||||||
|
Maker {
|
||||||
|
platform: TradingPlatform,
|
||||||
|
percentage: f64,
|
||||||
|
},
|
||||||
|
Taker {
|
||||||
|
platform: TradingPlatform,
|
||||||
|
percentage: f64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
@ -4,12 +4,10 @@ use std::fmt::{Debug, Formatter};
|
|||||||
use dyn_clone::DynClone;
|
use dyn_clone::DynClone;
|
||||||
use log::info;
|
use log::info;
|
||||||
|
|
||||||
use crate::events::{Event, EventKind, EventMetadata, Message};
|
use crate::connectors::Connector;
|
||||||
|
use crate::events::{ActionMessage, Event, EventKind, EventMetadata};
|
||||||
use crate::managers::OptionUpdate;
|
use crate::managers::OptionUpdate;
|
||||||
use crate::models::{
|
use crate::models::{ActiveOrder, OrderBook, OrderForm, OrderKind, Position, PositionProfitState};
|
||||||
ActiveOrder, OrderBook, OrderBookEntry, OrderForm, OrderKind, Position, PositionProfitState,
|
|
||||||
PositionState, TradingPlatform,
|
|
||||||
};
|
|
||||||
use crate::BoxError;
|
use crate::BoxError;
|
||||||
|
|
||||||
/***************
|
/***************
|
||||||
@ -23,13 +21,13 @@ 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>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
|
) -> (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>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>);
|
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for dyn PositionStrategy {
|
impl Debug for dyn PositionStrategy {
|
||||||
@ -69,54 +67,54 @@ impl Debug for dyn OrderStrategy {
|
|||||||
***************/
|
***************/
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TrailingStop {
|
pub struct HiddenTrailingStop {
|
||||||
stop_percentages: HashMap<u64, f64>,
|
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 {
|
impl HiddenTrailingStop {
|
||||||
const BREAK_EVEN_PERC: f64 = 0.1;
|
|
||||||
const MIN_PROFIT_PERC: f64 = 0.5;
|
|
||||||
const GOOD_PROFIT_PERC: f64 = TrailingStop::MIN_PROFIT_PERC * 1.75;
|
|
||||||
const MAX_LOSS_PERC: f64 = -4.0;
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
TrailingStop {
|
|
||||||
stop_percentages: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
||||||
let profit_state_delta = match profit_state {
|
let profit_state_delta = match profit_state {
|
||||||
PositionProfitState::MinimumProfit => Some(0.2),
|
PositionProfitState::MinimumProfit => Some(self.min_profit_trailing_delta),
|
||||||
PositionProfitState::Profit => Some(0.1),
|
PositionProfitState::Profit => Some(self.good_profit_trailing_delta),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(profit_state_delta) = profit_state_delta {
|
if let Some(profit_state_delta) = profit_state_delta {
|
||||||
let current_stop_percentage = position.pl_perc() - profit_state_delta;
|
let current_stop_percentage = position.pl_perc() - profit_state_delta;
|
||||||
|
|
||||||
match profit_state {
|
if let PositionProfitState::MinimumProfit | PositionProfitState::Profit =
|
||||||
PositionProfitState::MinimumProfit | PositionProfitState::Profit => {
|
profit_state
|
||||||
match self.stop_percentages.get(&position.id()) {
|
{
|
||||||
None => {
|
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
|
self.stop_percentages
|
||||||
.insert(position.id(), current_stop_percentage);
|
.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!(
|
info!(
|
||||||
"\tState: {:?} | PL%: {:0.2} | Stop: {:0.2}",
|
"\tState: {:?} | PL: {:0.2}{} ({:0.2}%) | Stop: {:0.2}",
|
||||||
position.profit_state().unwrap(),
|
position.profit_state().unwrap(),
|
||||||
|
position.pl(),
|
||||||
|
position.pair().quote(),
|
||||||
position.pl_perc(),
|
position.pl_perc(),
|
||||||
self.stop_percentages.get(&position.id()).unwrap_or(&0.0)
|
self.stop_percentages.get(&position.id()).unwrap_or(&0.0)
|
||||||
);
|
);
|
||||||
@ -124,9 +122,43 @@ impl TrailingStop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PositionStrategy for TrailingStop {
|
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 {
|
fn name(&self) -> String {
|
||||||
"Trailing stop".into()
|
"Hidden Trailing Stop".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the profit state of an open position
|
/// Sets the profit state of an open position
|
||||||
@ -135,19 +167,17 @@ impl PositionStrategy for TrailingStop {
|
|||||||
position: Position,
|
position: Position,
|
||||||
current_tick: u64,
|
current_tick: u64,
|
||||||
positions_history: &HashMap<u64, Position>,
|
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 pl_perc = position.pl_perc();
|
||||||
|
|
||||||
let state = {
|
let state = {
|
||||||
if pl_perc > TrailingStop::GOOD_PROFIT_PERC {
|
if pl_perc > self.good_profit_percentage {
|
||||||
PositionProfitState::Profit
|
PositionProfitState::Profit
|
||||||
} else if TrailingStop::MIN_PROFIT_PERC <= pl_perc
|
} else if (self.min_profit_percentage..self.good_profit_percentage).contains(&pl_perc) {
|
||||||
&& pl_perc < TrailingStop::GOOD_PROFIT_PERC
|
|
||||||
{
|
|
||||||
PositionProfitState::MinimumProfit
|
PositionProfitState::MinimumProfit
|
||||||
} else if 0.0 <= pl_perc && pl_perc < TrailingStop::MIN_PROFIT_PERC {
|
} else if (0.0..self.min_profit_percentage).contains(&pl_perc) {
|
||||||
PositionProfitState::BreakEven
|
PositionProfitState::BreakEven
|
||||||
} else if TrailingStop::MAX_LOSS_PERC < pl_perc && pl_perc < 0.0 {
|
} else if (self.max_loss_percentage..0.0).contains(&pl_perc) {
|
||||||
PositionProfitState::Loss
|
PositionProfitState::Loss
|
||||||
} else {
|
} else {
|
||||||
PositionProfitState::Critical
|
PositionProfitState::Critical
|
||||||
@ -156,7 +186,7 @@ impl PositionStrategy for TrailingStop {
|
|||||||
|
|
||||||
let opt_prev_position = positions_history.get(&(current_tick - 1));
|
let opt_prev_position = positions_history.get(&(current_tick - 1));
|
||||||
let event_metadata = EventMetadata::new(Some(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.with_profit_state(Some(state));
|
||||||
|
|
||||||
match opt_prev_position {
|
match opt_prev_position {
|
||||||
Some(prev) => {
|
Some(prev) => {
|
||||||
@ -205,7 +235,7 @@ impl PositionStrategy for TrailingStop {
|
|||||||
events
|
events
|
||||||
};
|
};
|
||||||
|
|
||||||
return (new_position, Some(events), None);
|
(new_position, Some(events), None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn post_tick(
|
fn post_tick(
|
||||||
@ -213,20 +243,15 @@ impl PositionStrategy for TrailingStop {
|
|||||||
position: Position,
|
position: Position,
|
||||||
_: u64,
|
_: u64,
|
||||||
_: &HashMap<u64, Position>,
|
_: &HashMap<u64, Position>,
|
||||||
) -> (Position, Option<Vec<Event>>, Option<Vec<Message>>) {
|
) -> (Position, Option<Vec<Event>>, Option<Vec<ActionMessage>>) {
|
||||||
let close_message = Message::ClosePosition {
|
let close_message = ActionMessage::ClosePosition {
|
||||||
position_id: position.id(),
|
position_id: position.id(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// if critical, early return with close position
|
// if critical, early return with close position
|
||||||
if let Some(profit_state) = position.profit_state() {
|
if let Some(PositionProfitState::Critical) = position.profit_state() {
|
||||||
match profit_state {
|
info!("Maximum loss reached. Closing position.");
|
||||||
PositionProfitState::Critical => {
|
return (position, None, Some(vec![close_message]));
|
||||||
info!("Maximum loss reached. Closing position.");
|
|
||||||
return (position, None, Some(vec![close_message]));
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// let's check if we surpassed an existing stop percentage
|
// let's check if we surpassed an existing stop percentage
|
||||||
@ -243,28 +268,281 @@ impl PositionStrategy for TrailingStop {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #[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)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct FastOrderStrategy {
|
pub struct MarketEnforce {
|
||||||
// threshold (%) for which we trigger a market order
|
// threshold (%) for which we trigger a market order
|
||||||
// to close an open position
|
// to close an open position
|
||||||
threshold: f64,
|
threshold: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for FastOrderStrategy {
|
impl Default for MarketEnforce {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { threshold: 0.2 }
|
Self {
|
||||||
|
threshold: 1.0 / 15.0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FastOrderStrategy {
|
impl OrderStrategy for MarketEnforce {
|
||||||
pub fn new(threshold: f64) -> Self {
|
|
||||||
Self { threshold }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl OrderStrategy for FastOrderStrategy {
|
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"Fast order strategy".into()
|
"Market Enforce".into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_open_order(
|
fn on_open_order(
|
||||||
@ -276,7 +554,7 @@ impl OrderStrategy for FastOrderStrategy {
|
|||||||
|
|
||||||
// long
|
// long
|
||||||
let offer_comparison = {
|
let offer_comparison = {
|
||||||
if order.details.amount() > 0.0 {
|
if order.order_form().amount() > 0.0 {
|
||||||
order_book.highest_bid()
|
order_book.highest_bid()
|
||||||
} else {
|
} else {
|
||||||
order_book.lowest_ask()
|
order_book.lowest_ask()
|
||||||
@ -286,63 +564,24 @@ impl OrderStrategy for FastOrderStrategy {
|
|||||||
// if the best offer is higher than our threshold,
|
// if the best offer is higher than our threshold,
|
||||||
// ask the manager to close the position with a market order
|
// ask the manager to close the position with a market order
|
||||||
let order_price = order
|
let order_price = order
|
||||||
.details
|
.order_form()
|
||||||
.price()
|
.price()
|
||||||
.ok_or("The active order does not have a price!")?;
|
.ok_or("The active order does not have a price!")?;
|
||||||
let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0;
|
let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0;
|
||||||
|
|
||||||
if delta > self.threshold {
|
if delta > self.threshold {
|
||||||
messages.push(Message::SubmitOrder {
|
messages.push(ActionMessage::SubmitOrder {
|
||||||
order: OrderForm::new(
|
order: OrderForm::new(
|
||||||
order.symbol.clone(),
|
order.pair().clone(),
|
||||||
OrderKind::Market {
|
OrderKind::Market,
|
||||||
amount: order.details.amount(),
|
*order.order_form().platform(),
|
||||||
},
|
order.order_form().amount(),
|
||||||
order.details.platform().clone(),
|
)
|
||||||
),
|
.with_leverage(order.order_form().leverage())
|
||||||
|
.with_metadata(order.order_form().metadata().clone()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((None, (!messages.is_empty()).then_some(messages)))
|
Ok((None, (!messages.is_empty()).then_some(messages)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn on_position_order(
|
|
||||||
// &self,
|
|
||||||
// order: &ActiveOrder,
|
|
||||||
// _: &Position,
|
|
||||||
// order_book: &OrderBook,
|
|
||||||
// ) -> Result<OptionUpdate, BoxError> {
|
|
||||||
// let mut messages = vec![];
|
|
||||||
//
|
|
||||||
// // long
|
|
||||||
// let offer_comparison = {
|
|
||||||
// if order.current_form.amount() > 0.0 {
|
|
||||||
// order_book.highest_bid()
|
|
||||||
// } else {
|
|
||||||
// order_book.lowest_ask()
|
|
||||||
// }
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// // if the best offer is higher than our threshold,
|
|
||||||
// // ask the manager to close the position with a market order
|
|
||||||
// let order_price = order
|
|
||||||
// .current_form
|
|
||||||
// .price()
|
|
||||||
// .ok_or("The active order does not have a price!")?;
|
|
||||||
// let delta = (1.0 - (offer_comparison / order_price)).abs() * 100.0;
|
|
||||||
//
|
|
||||||
// if delta > self.threshold {
|
|
||||||
// messages.push(Message::SubmitOrder {
|
|
||||||
// order: OrderForm::new(
|
|
||||||
// order.symbol.clone(),
|
|
||||||
// OrderKind::Market {
|
|
||||||
// amount: order.current_form.amount(),
|
|
||||||
// },
|
|
||||||
// order.current_form.platform().clone(),
|
|
||||||
// ),
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Ok((None, (!messages.is_empty()).then_some(messages)))
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user