use std::{
error::Error,
fmt::{Display, Formatter},
};
use log::info;
use rotala::exchange::uist::{UistOrder, UistOrderType, UistQuote, UistTrade};
use crate::types::{
CashValue, PortfolioAllocation, PortfolioHoldings, PortfolioQty, PortfolioValues, Price,
};
pub mod uist;
#[derive(Clone, Debug)]
pub enum BrokerState {
Ready,
Failed,
}
#[derive(Clone, Debug)]
pub enum BrokerOrderType {
MarketBuy,
MarketSell,
LimitBuy,
LimitSell,
StopBuy,
StopSell,
}
impl From<UistOrderType> for BrokerOrderType {
fn from(value: UistOrderType) -> Self {
match value {
UistOrderType::MarketBuy => BrokerOrderType::MarketBuy,
UistOrderType::MarketSell => BrokerOrderType::MarketSell,
UistOrderType::LimitBuy => BrokerOrderType::LimitBuy,
UistOrderType::LimitSell => BrokerOrderType::LimitSell,
UistOrderType::StopBuy => BrokerOrderType::StopBuy,
UistOrderType::StopSell => BrokerOrderType::StopSell,
}
}
}
pub trait BrokerTrade: Clone {
fn get_quantity(&self) -> f64;
fn get_value(&self) -> f64;
}
impl BrokerTrade for UistTrade {
fn get_quantity(&self) -> f64 {
self.quantity
}
fn get_value(&self) -> f64 {
self.value
}
}
pub trait BrokerQuote {
fn get_bid(&self) -> f64;
fn get_ask(&self) -> f64;
}
impl BrokerQuote for UistQuote {
fn get_bid(&self) -> f64 {
self.bid
}
fn get_ask(&self) -> f64 {
self.ask
}
}
pub trait BrokerOrder {
fn get_order_type<T: Into<BrokerOrderType>>(&self) -> BrokerOrderType;
fn get_shares(&self) -> f64;
fn get_symbol(&self) -> String;
fn market_buy(symbol: String, shares: f64) -> Self;
fn market_sell(symbol: String, shares: f64) -> Self;
}
impl BrokerOrder for UistOrder {
fn get_order_type<UistOrderType>(&self) -> BrokerOrderType {
self.order_type.into()
}
fn get_shares(&self) -> f64 {
self.shares
}
fn get_symbol(&self) -> String {
self.symbol.clone()
}
fn market_buy(symbol: String, shares: f64) -> Self {
UistOrder::market_buy(symbol, shares)
}
fn market_sell(symbol: String, shares: f64) -> Self {
UistOrder::market_sell(symbol, shares)
}
}
#[derive(Clone, Debug)]
pub enum BrokerEvent<O: BrokerOrder> {
OrderSentToExchange(O),
OrderInvalid(O),
OrderCreated(O),
OrderFailure(O),
}
#[derive(Clone, Debug)]
pub enum BrokerCashEvent {
WithdrawSuccess(CashValue),
WithdrawFailure(CashValue),
DepositSuccess(CashValue),
OperationFailure(CashValue),
}
#[derive(Clone, Debug)]
pub struct InsufficientCashError;
impl Error for InsufficientCashError {}
impl Display for InsufficientCashError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "Client has insufficient cash to execute order")
}
}
#[derive(Debug, Clone)]
pub struct UnexecutableOrderError;
impl Error for UnexecutableOrderError {}
impl Display for UnexecutableOrderError {
fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
write!(f, "Client has passed unexecutable order")
}
}
#[derive(Clone, Debug)]
pub enum BrokerCost {
PerShare(Price),
PctOfValue(f64),
Flat(CashValue),
}
impl BrokerCost {
pub fn per_share(val: f64) -> Self {
BrokerCost::PerShare(Price::from(val))
}
pub fn pct_of_value(val: f64) -> Self {
BrokerCost::PctOfValue(val)
}
pub fn flat(val: f64) -> Self {
BrokerCost::Flat(CashValue::from(val))
}
pub fn calc(&self, trade: impl BrokerTrade) -> CashValue {
match self {
BrokerCost::PerShare(cost) => CashValue::from(*cost.clone() * trade.get_quantity()),
BrokerCost::PctOfValue(pct) => CashValue::from(trade.get_value() * *pct),
BrokerCost::Flat(val) => val.clone(),
}
}
pub fn trade_impact(
&self,
gross_budget: &f64,
gross_price: &f64,
is_buy: bool,
) -> (CashValue, Price) {
let mut net_budget = *gross_budget;
let mut net_price = *gross_price;
match self {
BrokerCost::PerShare(val) => {
if is_buy {
net_price += *val.clone();
} else {
net_price -= *val.clone();
}
}
BrokerCost::PctOfValue(pct) => {
net_budget *= 1.0 - pct;
}
BrokerCost::Flat(val) => net_budget -= *val.clone(),
}
(CashValue::from(net_budget), Price::from(net_price))
}
pub fn trade_impact_total(
trade_costs: &[BrokerCost],
gross_budget: &f64,
gross_price: &f64,
is_buy: bool,
) -> (CashValue, Price) {
let mut res = (CashValue::from(*gross_budget), Price::from(*gross_price));
for cost in trade_costs {
res = cost.trade_impact(&res.0, &res.1, is_buy);
}
res
}
}
pub trait Quote<Q: BrokerQuote> {
fn get_quote(&self, symbol: &str) -> Option<Q>;
fn get_quotes(&self) -> Option<Vec<Q>>;
}
pub trait SendOrder<O: BrokerOrder> {
fn send_order(&mut self, order: O) -> BrokerEvent<O>;
fn send_orders(&mut self, orders: &[O]) -> Vec<BrokerEvent<O>>;
}
pub trait Portfolio<Q: BrokerQuote>: Quote<Q> {
fn get_position_profit(&self, symbol: &str) -> Option<CashValue> {
if let Some(cost) = self.get_position_cost(symbol) {
if let Some(qty) = self.get_position_qty(symbol) {
if let Some(position_value) = self.get_position_value(symbol) {
let price = *position_value / *qty.clone();
let value = CashValue::from(*qty.clone() * (price - *cost));
return Some(value);
}
}
}
None
}
fn get_position_liquidation_value(&self, symbol: &str) -> Option<CashValue> {
if let Some(position_value) = self.get_position_value(symbol) {
if let Some(qty) = self.get_position_qty(symbol) {
let price = Price::from(*position_value / *qty);
let (value_after_costs, _price_after_costs) =
self.calc_trade_impact(&position_value, &price, false);
return Some(value_after_costs);
}
}
None
}
fn get_total_value(&self) -> CashValue {
let assets = self.get_positions();
let mut value = self.get_cash_balance();
for a in assets {
if let Some(position_value) = self.get_position_value(&a) {
value = CashValue::from(*value + *position_value);
}
}
value
}
fn get_liquidation_value(&self) -> CashValue {
let mut value = self.get_cash_balance();
for asset in self.get_positions() {
if let Some(asset_value) = self.get_position_liquidation_value(&asset) {
value = CashValue::from(*value + *asset_value);
}
}
value
}
fn get_values(&self) -> PortfolioValues {
let mut holdings = PortfolioValues::new();
let assets = self.get_positions();
for a in assets {
if let Some(_qty) = self.get_position_qty(&a) {
if let Some(value) = self.get_position_value(&a) {
holdings.insert(&a, &value);
}
}
}
holdings
}
fn get_position_qty(&self, symbol: &str) -> Option<PortfolioQty> {
self.get_holdings().get(symbol).to_owned()
}
fn get_position_value(&self, symbol: &str) -> Option<CashValue> {
if let Some(quote) = self.get_quote(symbol) {
let price = quote.get_bid();
if let Some(qty) = self.get_position_qty(symbol) {
let val = price * *qty;
return Some(CashValue::from(val));
}
}
None
}
fn get_positions(&self) -> Vec<String> {
self.get_holdings().keys()
}
fn get_holdings_with_pending(&self) -> PortfolioHoldings {
let mut merged_holdings = PortfolioHoldings::new();
for (key, value) in self.get_holdings().0.iter() {
if merged_holdings.0.contains_key(key) {
if let Some(val) = merged_holdings.get(key) {
let new_val = PortfolioQty::from(*val + **value);
merged_holdings.insert(key, &new_val);
}
} else {
merged_holdings.insert(key, value);
}
}
for (key, value) in self.get_pending_orders().0.iter() {
if merged_holdings.0.contains_key(key) {
if let Some(val) = merged_holdings.get(key) {
let new_val = PortfolioQty::from(*val + **value);
merged_holdings.insert(key, &new_val);
}
} else {
merged_holdings.insert(key, value);
}
}
merged_holdings
}
fn calculate_trade_costs(&self, trade: impl BrokerTrade) -> CashValue {
let mut cost = CashValue::default();
for trade_cost in &self.get_trade_costs() {
cost = CashValue::from(*cost + *trade_cost.calc(trade.clone()));
}
cost
}
fn calc_trade_impact(&self, budget: &f64, price: &f64, is_buy: bool) -> (CashValue, Price) {
BrokerCost::trade_impact_total(&self.get_trade_costs(), budget, price, is_buy)
}
fn get_cash_balance(&self) -> CashValue;
fn update_cash_balance(&mut self, cash: CashValue);
fn get_holdings(&self) -> PortfolioHoldings;
fn update_holdings(&mut self, symbol: &str, change: PortfolioQty);
fn get_position_cost(&self, symbol: &str) -> Option<Price>;
fn get_pending_orders(&self) -> PortfolioHoldings;
fn get_trade_costs(&self) -> Vec<BrokerCost>;
}
pub trait BrokerStates {
fn get_broker_state(&self) -> BrokerState;
fn update_broker_state(&mut self, state: BrokerState);
}
pub trait CashOperations<Q: BrokerQuote>: Portfolio<Q> + BrokerStates {
fn withdraw_cash(&mut self, cash: &f64) -> BrokerCashEvent {
match self.get_broker_state() {
BrokerState::Failed => {
info!(
"BROKER: Attempted cash withdraw of {:?} but broker in Failed State",
cash,
);
BrokerCashEvent::OperationFailure(CashValue::from(*cash))
}
BrokerState::Ready => {
if cash > &self.get_cash_balance() {
info!(
"BROKER: Attempted cash withdraw of {:?} but only have {:?}",
cash,
self.get_cash_balance()
);
return BrokerCashEvent::WithdrawFailure(CashValue::from(*cash));
}
info!(
"BROKER: Successful cash withdraw of {:?}, {:?} left in cash",
cash,
self.get_cash_balance()
);
self.debit(cash);
BrokerCashEvent::WithdrawSuccess(CashValue::from(*cash))
}
}
}
fn deposit_cash(&mut self, cash: &f64) -> BrokerCashEvent {
match self.get_broker_state() {
BrokerState::Failed => {
info!(
"BROKER: Attempted cash deposit of {:?} but broker in Failed State",
cash,
);
BrokerCashEvent::OperationFailure(CashValue::from(*cash))
}
BrokerState::Ready => {
info!(
"BROKER: Deposited {:?} cash, current balance of {:?}",
cash,
self.get_cash_balance()
);
self.credit(cash);
BrokerCashEvent::DepositSuccess(CashValue::from(*cash))
}
}
}
fn credit(&mut self, value: &f64) -> BrokerCashEvent {
info!(
"BROKER: Credited {:?} cash, current balance of {:?}",
value,
self.get_cash_balance()
);
self.update_cash_balance(CashValue::from(*value + *self.get_cash_balance()));
BrokerCashEvent::DepositSuccess(CashValue::from(*value))
}
fn debit(&mut self, value: &f64) -> BrokerCashEvent {
if value > &self.get_cash_balance() {
info!(
"BROKER: Debit failed of {:?} cash, current balance of {:?}",
value,
self.get_cash_balance()
);
return BrokerCashEvent::WithdrawFailure(CashValue::from(*value));
}
info!(
"BROKER: Debited {:?} cash, current balance of {:?}",
value,
self.get_cash_balance()
);
self.update_cash_balance(CashValue::from(*self.get_cash_balance() - *value));
BrokerCashEvent::WithdrawSuccess(CashValue::from(*value))
}
fn debit_force(&mut self, value: &f64) -> BrokerCashEvent {
info!(
"BROKER: Force debt {:?} cash, current balance of {:?}",
value,
self.get_cash_balance()
);
self.update_cash_balance(CashValue::from(*self.get_cash_balance() - *value));
BrokerCashEvent::WithdrawSuccess(CashValue::from(*value))
}
}
pub trait BrokerOperations<O: BrokerOrder, Q: BrokerQuote>:
Portfolio<Q> + BrokerStates + SendOrder<O> + CashOperations<Q>
{
fn rebalance_cash(&mut self) {
if *self.get_cash_balance() < 0.0 {
let shortfall = *self.get_cash_balance() * -1.0;
let plus_buffer = shortfall + 1000.0;
let res = self.withdraw_cash_with_liquidation(&plus_buffer);
if let BrokerCashEvent::WithdrawFailure(_val) = res {
self.update_broker_state(BrokerState::Failed);
}
}
}
fn withdraw_cash_with_liquidation(&mut self, cash: &f64) -> BrokerCashEvent {
info!("BROKER: Withdrawing {:?} with liquidation", cash);
let value = self.get_liquidation_value();
if cash > &value {
self.debit(cash);
info!(
"BROKER: Failed to withdraw {:?} with liquidation. Deducting value from cash.",
cash
);
BrokerCashEvent::WithdrawFailure(CashValue::from(*cash))
} else {
let mut total_sold = *cash;
let positions = self.get_positions();
let mut sell_orders: Vec<O> = Vec::new();
for ticker in positions {
let position_value = self
.get_position_value(&ticker)
.unwrap_or(CashValue::from(0.0));
if *position_value <= total_sold {
if let Some(qty) = self.get_position_qty(&ticker) {
let order = O::market_sell(ticker, *qty);
info!("BROKER: Withdrawing {:?} with liquidation, queueing sale of {:?} shares of {:?}", cash, order.get_shares(), order.get_symbol());
sell_orders.push(order);
total_sold -= *position_value;
}
} else {
let quote = self.get_quote(&ticker).unwrap();
let price = quote.get_bid();
let shares_req = PortfolioQty::from((total_sold / price).ceil());
let order = O::market_sell(ticker, *shares_req);
info!("BROKER: Withdrawing {:?} with liquidation, queueing sale of {:?} shares of {:?}", cash, order.get_shares(), order.get_symbol());
sell_orders.push(order);
total_sold = 0.0;
break;
}
}
if (total_sold).eq(&0.0) {
self.send_orders(&sell_orders);
info!("BROKER: Succesfully withdrew {:?} with liquidation", cash);
BrokerCashEvent::WithdrawSuccess(CashValue::from(*cash))
} else {
self.debit(cash);
info!(
"BROKER: Failed to withdraw {:?} with liquidation. Deducting value from cash.",
cash
);
BrokerCashEvent::WithdrawFailure(CashValue::from(*cash))
}
}
}
fn client_has_sufficient_cash<T: Into<BrokerOrderType>>(
&self,
order: &O,
price: &Price,
) -> Result<(), InsufficientCashError> {
let shares = order.get_shares();
let value = CashValue::from(shares * **price);
match order.get_order_type::<T>() {
BrokerOrderType::MarketBuy => {
if self.get_cash_balance() > value {
return Ok(());
}
Err(InsufficientCashError)
}
BrokerOrderType::MarketSell => Ok(()),
_ => unreachable!("Shouldn't hit unless something has gone wrong"),
}
}
fn client_has_sufficient_holdings_for_sale<T: Into<BrokerOrderType>>(
&self,
order: &O,
) -> Result<(), UnexecutableOrderError> {
if let BrokerOrderType::MarketSell = order.get_order_type::<T>() {
if let Some(holding) = self.get_position_qty(&order.get_symbol()) {
if *holding >= order.get_shares() {
return Ok(());
} else {
return Err(UnexecutableOrderError);
}
}
}
Ok(())
}
fn client_is_issuing_nonsense_order(&self, order: &O) -> Result<(), UnexecutableOrderError> {
let shares = order.get_shares();
if shares == 0.0 {
return Err(UnexecutableOrderError);
}
Ok(())
}
fn diff_brkr_against_target_weights(&mut self, target_weights: &PortfolioAllocation) -> Vec<O> {
info!("STRATEGY: Calculating diff of current allocation vs. target");
let total_value = self.get_liquidation_value();
if (*total_value).eq(&0.0) {
panic!("Client is attempting to trade a portfolio with zero value");
}
let mut orders: Vec<O> = Vec::new();
let mut buy_orders: Vec<O> = Vec::new();
let mut sell_orders: Vec<O> = Vec::new();
let calc_required_shares_with_costs = |diff_val: &f64, quote: &Q, brkr: &Self| -> f64 {
if diff_val.lt(&0.0) {
let price = quote.get_bid();
let costs = brkr.calc_trade_impact(&diff_val.abs(), &price, false);
let total = (*costs.0 / *costs.1).floor();
-total
} else {
let price = quote.get_ask();
let costs = brkr.calc_trade_impact(&diff_val.abs(), &price, true);
(*costs.0 / *costs.1).floor()
}
};
for symbol in target_weights.keys() {
let curr_val = self
.get_position_value(&symbol)
.unwrap_or(CashValue::from(0.0));
let target_val = CashValue::from(*total_value * **target_weights.get(&symbol).unwrap());
let diff_val = CashValue::from(*target_val - *curr_val);
if (*diff_val).eq(&0.0) {
break;
}
if let Some(quote) = self.get_quote(&symbol) {
let required_shares = calc_required_shares_with_costs(&diff_val, "e, self);
if required_shares.ne(&0.0) {
if required_shares.gt(&0.0) {
buy_orders.push(O::market_buy(symbol.clone(), required_shares));
} else {
sell_orders.push(O::market_sell(
symbol.clone(),
required_shares.abs(),
));
}
}
}
}
orders.extend(sell_orders);
orders.extend(buy_orders);
orders
}
}