use itertools::Itertools;
use std::{
collections::HashMap,
error::Error,
fmt::{Display, Formatter},
};
use log::info;
use rotala::clock::DateTime;
use rotala::exchange::uist::{
UistOrder, UistOrderType, UistQuote, UistTrade, UistTradeType, UistV1,
};
use crate::types::{
CashValue, PortfolioAllocation, PortfolioHoldings, PortfolioQty, PortfolioValues, Price,
};
use super::{
BrokerCost, BrokerEvent, BrokerOperations, BrokerState, BrokerStates, CashOperations,
Portfolio, Quote, SendOrder,
};
type UistBrokerEvent = BrokerEvent<UistOrder>;
#[derive(Debug)]
pub struct UistBroker {
cash: CashValue,
exchange: UistV1,
holdings: PortfolioHoldings,
pending_orders: PortfolioHoldings,
last_seen_trade: usize,
latest_quotes: HashMap<String, UistQuote>,
log: UistBrokerLog,
trade_costs: Vec<BrokerCost>,
broker_state: BrokerState,
}
impl Quote<UistQuote> for UistBroker {
fn get_quote(&self, symbol: &str) -> Option<UistQuote> {
self.latest_quotes.get(symbol).cloned()
}
fn get_quotes(&self) -> Option<Vec<UistQuote>> {
if self.latest_quotes.is_empty() {
return None;
}
let mut tmp = Vec::new();
for quote in self.latest_quotes.values() {
tmp.push(quote.clone());
}
Some(tmp)
}
}
impl Portfolio<UistQuote> for UistBroker {
fn get_trade_costs(&self) -> Vec<BrokerCost> {
self.trade_costs.clone()
}
fn get_holdings(&self) -> PortfolioHoldings {
self.holdings.clone()
}
fn get_cash_balance(&self) -> CashValue {
self.cash.clone()
}
fn update_cash_balance(&mut self, cash: CashValue) {
self.cash = cash;
}
fn get_position_cost(&self, symbol: &str) -> Option<Price> {
self.log.cost_basis(symbol)
}
fn update_holdings(&mut self, symbol: &str, change: PortfolioQty) {
let symbol_own = symbol.to_string();
info!(
"BROKER: Incrementing holdings in {:?} by {:?}",
symbol_own, change
);
if (*change).eq(&0.0) {
self.holdings.remove(symbol.as_ref());
} else {
self.holdings.insert(symbol.as_ref(), &change);
}
}
fn get_pending_orders(&self) -> PortfolioHoldings {
self.pending_orders.clone()
}
}
impl BrokerStates for UistBroker {
fn get_broker_state(&self) -> BrokerState {
self.broker_state.clone()
}
fn update_broker_state(&mut self, state: BrokerState) {
self.broker_state = state;
}
}
impl CashOperations<UistQuote> for UistBroker {}
impl BrokerOperations<UistOrder, UistQuote> for UistBroker {}
impl SendOrder<UistOrder> for UistBroker {
fn send_order(&mut self, order: UistOrder) -> UistBrokerEvent {
match self.get_broker_state() {
BrokerState::Failed => {
info!(
"BROKER: Unable to send {:?} order for {:?} shares of {:?} to exchange as broker in Failed state",
order.get_order_type(),
order.get_shares(),
order.get_symbol()
);
UistBrokerEvent::OrderInvalid(order.clone())
}
BrokerState::Ready => {
info!(
"BROKER: Attempting to send {:?} order for {:?} shares of {:?} to the exchange",
order.get_order_type(),
order.get_shares(),
order.get_symbol()
);
let quote = self.get_quote(order.get_symbol()).unwrap();
let price = match order.get_order_type() {
UistOrderType::MarketBuy | UistOrderType::LimitBuy | UistOrderType::StopBuy => {
quote.get_ask()
}
UistOrderType::MarketSell
| UistOrderType::LimitSell
| UistOrderType::StopSell => quote.get_bid(),
};
if let Err(_err) =
self.client_has_sufficient_cash::<UistOrderType>(&order, &Price::from(price))
{
info!(
"BROKER: Unable to send {:?} order for {:?} shares of {:?} to exchange",
order.get_order_type(),
order.get_shares(),
order.get_symbol()
);
return UistBrokerEvent::OrderInvalid(order.clone());
}
if let Err(_err) =
self.client_has_sufficient_holdings_for_sale::<UistOrderType>(&order)
{
info!(
"BROKER: Unable to send {:?} order for {:?} shares of {:?} to exchange",
order.get_order_type(),
order.get_shares(),
order.get_symbol()
);
return UistBrokerEvent::OrderInvalid(order.clone());
}
if let Err(_err) = self.client_is_issuing_nonsense_order(&order) {
info!(
"BROKER: Unable to send {:?} order for {:?} shares of {:?} to exchange",
order.get_order_type(),
order.get_shares(),
order.get_symbol()
);
return UistBrokerEvent::OrderInvalid(order.clone());
}
self.exchange.insert_order(order.clone());
let order_effect = match order.get_order_type() {
UistOrderType::MarketBuy | UistOrderType::LimitBuy | UistOrderType::StopBuy => {
order.get_shares()
}
UistOrderType::MarketSell
| UistOrderType::LimitSell
| UistOrderType::StopSell => -order.get_shares(),
};
if let Some(position) = self.pending_orders.get(order.get_symbol()) {
let existing = *position + order_effect;
self.pending_orders
.insert(order.get_symbol(), &PortfolioQty::from(existing));
} else {
self.pending_orders
.insert(order.get_symbol(), &PortfolioQty::from(order_effect));
}
info!(
"BROKER: Successfully sent {:?} order for {:?} shares of {:?} to exchange",
order.get_order_type(),
order.get_shares(),
order.get_symbol()
);
UistBrokerEvent::OrderSentToExchange(order)
}
}
}
fn send_orders(&mut self, orders: &[UistOrder]) -> Vec<UistBrokerEvent> {
let mut res = Vec::new();
for o in orders {
let trade = self.send_order(o.clone());
res.push(trade);
}
res
}
}
impl UistBroker {
pub fn check(&mut self) {
let (has_next, completed_trades, inserted_orders) = self.exchange.tick();
for quote in &self.exchange.fetch_quotes() {
self.latest_quotes
.insert(quote.get_symbol().to_string(), quote.clone());
}
for trade in completed_trades {
match trade.typ {
UistTradeType::Buy => self.debit_force(&trade.value),
UistTradeType::Sell => self.credit(&trade.value),
};
self.log.record::<UistTrade>(trade.clone());
let curr_position = self.get_position_qty(&trade.symbol).unwrap_or_default();
let updated = match trade.typ {
UistTradeType::Buy => *curr_position + trade.quantity,
UistTradeType::Sell => *curr_position - trade.quantity,
};
self.update_holdings(&trade.symbol, PortfolioQty::from(updated));
let pending = self.pending_orders.get(&trade.symbol).unwrap_or_default();
let updated_pending = match trade.typ {
UistTradeType::Buy => *pending - trade.quantity,
UistTradeType::Sell => *pending + trade.quantity,
};
if updated_pending == 0.0 {
self.pending_orders.remove(&trade.symbol);
} else {
self.pending_orders
.insert(&trade.symbol, &PortfolioQty::from(updated_pending));
}
self.last_seen_trade += 1;
}
self.rebalance_cash();
}
pub fn cost_basis(&self, symbol: &str) -> Option<Price> {
self.log.cost_basis(symbol)
}
pub fn trades_between(&self, start: &i64, stop: &i64) -> Vec<UistTrade> {
self.log.trades_between(start, stop)
}
}
pub struct UistBrokerBuilder {
trade_costs: Vec<BrokerCost>,
exchange: Option<UistV1>,
}
impl UistBrokerBuilder {
pub fn build(&mut self) -> UistBroker {
if self.exchange.is_none() {
panic!("Cannot build broker without exchange");
}
let mut first_quotes = HashMap::new();
let quotes = self.exchange.as_ref().unwrap().fetch_quotes();
for quote in "es {
first_quotes.insert(quote.get_symbol().to_string(), quote.clone());
}
let holdings = PortfolioHoldings::new();
let pending_orders = PortfolioHoldings::new();
let log = UistBrokerLog::new();
let exchange = std::mem::take(&mut self.exchange).unwrap();
UistBroker {
holdings,
pending_orders,
cash: CashValue::from(0.0),
log,
last_seen_trade: 0,
exchange,
trade_costs: self.trade_costs.clone(),
latest_quotes: first_quotes,
broker_state: BrokerState::Ready,
}
}
pub fn with_exchange(&mut self, exchange: UistV1) -> &mut Self {
self.exchange = Some(exchange);
self
}
pub fn with_trade_costs(&mut self, trade_costs: Vec<BrokerCost>) -> &mut Self {
self.trade_costs = trade_costs;
self
}
pub fn new() -> Self {
UistBrokerBuilder {
trade_costs: Vec::new(),
exchange: None,
}
}
}
impl Default for UistBrokerBuilder {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub enum UistRecordedEvent {
TradeCompleted(UistTrade),
}
impl From<UistTrade> for UistRecordedEvent {
fn from(value: UistTrade) -> Self {
UistRecordedEvent::TradeCompleted(value)
}
}
#[doc(hidden)]
#[derive(Clone, Debug)]
pub struct UistBrokerLog {
log: Vec<UistRecordedEvent>,
}
impl UistBrokerLog {
pub fn record<E: Into<UistRecordedEvent>>(&mut self, event: E) {
let brokerevent: UistRecordedEvent = event.into();
self.log.push(brokerevent);
}
pub fn trades(&self) -> Vec<UistTrade> {
let mut trades = Vec::new();
for event in &self.log {
let UistRecordedEvent::TradeCompleted(trade) = event;
trades.push(trade.clone());
}
trades
}
pub fn trades_between(&self, start: &i64, stop: &i64) -> Vec<UistTrade> {
let trades = self.trades();
trades
.iter()
.filter(|v| v.date >= *DateTime::from(*start) && v.date <= *DateTime::from(*stop))
.cloned()
.collect_vec()
}
pub fn cost_basis(&self, symbol: &str) -> Option<Price> {
let mut cum_qty = PortfolioQty::default();
let mut cum_val = CashValue::default();
for event in &self.log {
let UistRecordedEvent::TradeCompleted(trade) = event;
if trade.symbol.eq(symbol) {
match trade.typ {
UistTradeType::Buy => {
cum_qty = PortfolioQty::from(*cum_qty + trade.quantity);
cum_val = CashValue::from(*cum_val + trade.value);
}
UistTradeType::Sell => {
cum_qty = PortfolioQty::from(*cum_qty - trade.quantity);
cum_val = CashValue::from(*cum_val - trade.value);
}
}
if (*cum_qty).eq(&0.0) {
cum_val = CashValue::default();
}
}
}
if (*cum_qty).eq(&0.0) {
return None;
}
Some(Price::from(*cum_val / *cum_qty))
}
}
impl UistBrokerLog {
pub fn new() -> Self {
UistBrokerLog { log: Vec::new() }
}
}
impl Default for UistBrokerLog {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use crate::broker::{
BrokerCashEvent, BrokerCost, BrokerOperations, CashOperations, Portfolio, SendOrder,
};
use crate::types::{CashValue, PortfolioAllocation, PortfolioQty};
use rotala::clock::{ClockBuilder, Frequency};
use rotala::exchange::uist::{
random_uist_generator, UistOrder, UistOrderType, UistTrade, UistTradeType, UistV1,
};
use rotala::input::penelope::PenelopeBuilder;
use super::{UistBroker, UistBrokerBuilder, UistBrokerEvent, UistBrokerLog};
fn setup() -> UistBroker {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 101.00, 100, "ABC");
source_builder.add_quote(10.00, 11.00, 100, "BCD");
source_builder.add_quote(104.00, 105.00, 101, "ABC");
source_builder.add_quote(14.00, 15.00, 101, "BCD");
source_builder.add_quote(95.00, 96.00, 102, "ABC");
source_builder.add_quote(10.00, 11.00, 102, "BCD");
source_builder.add_quote(95.00, 96.00, 103, "ABC");
source_builder.add_quote(10.00, 11.00, 103, "BCD");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let brkr = UistBrokerBuilder::new()
.with_exchange(uist)
.with_trade_costs(vec![BrokerCost::PctOfValue(0.01)])
.build();
brkr
}
#[test]
fn test_cash_deposit_withdraw() {
let mut brkr = setup();
brkr.deposit_cash(&100.0);
brkr.check();
assert!(matches!(
brkr.withdraw_cash(&50.0),
BrokerCashEvent::WithdrawSuccess(..)
));
assert!(matches!(
brkr.withdraw_cash(&51.0),
BrokerCashEvent::WithdrawFailure(..)
));
assert!(matches!(
brkr.deposit_cash(&50.0),
BrokerCashEvent::DepositSuccess(..)
));
assert!(matches!(
brkr.debit(&50.0),
BrokerCashEvent::WithdrawSuccess(..)
));
assert!(matches!(
brkr.debit(&51.0),
BrokerCashEvent::WithdrawFailure(..)
));
assert!(matches!(
brkr.credit(&50.0),
BrokerCashEvent::DepositSuccess(..)
));
}
#[test]
fn test_that_buy_order_reduces_cash_and_increases_holdings() {
let mut brkr = setup();
brkr.deposit_cash(&100_000.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 495.0));
println!("{:?}", res);
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
brkr.check();
let cash = brkr.get_cash_balance();
assert!(*cash < 100_000.0);
let qty = brkr
.get_position_qty("ABC")
.unwrap_or(PortfolioQty::from(0.0));
assert_eq!(*qty.clone(), 495.00);
}
#[test]
fn test_that_buy_order_larger_than_cash_fails_with_error_returned_without_panic() {
let mut brkr = setup();
brkr.deposit_cash(&100.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 495.0));
assert!(matches!(res, UistBrokerEvent::OrderInvalid(..)));
brkr.check();
let cash = brkr.get_cash_balance();
assert!(*cash == 100.0);
}
#[test]
fn test_that_sell_order_larger_than_holding_fails_with_error_returned_without_panic() {
let mut brkr = setup();
brkr.deposit_cash(&100_000.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 100.0));
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
brkr.check();
brkr.check();
let res = brkr.send_order(UistOrder::market_sell("ABC", 105.0));
assert!(matches!(res, UistBrokerEvent::OrderInvalid(..)));
let qty = brkr.get_position_qty("ABC").unwrap_or_default();
println!("{:?}", qty);
assert!((*qty.clone()).eq(&100.0));
}
#[test]
fn test_that_market_sell_increases_cash_and_decreases_holdings() {
let mut brkr = setup();
brkr.deposit_cash(&100_000.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 495.0));
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
brkr.check();
let cash = brkr.get_cash_balance();
brkr.check();
let res = brkr.send_order(UistOrder::market_sell("ABC", 295.0));
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
brkr.check();
let cash0 = brkr.get_cash_balance();
let qty = brkr.get_position_qty("ABC").unwrap_or_default();
assert_eq!(*qty, 200.0);
assert!(*cash0 > *cash);
}
#[test]
fn test_that_valuation_updates_in_next_period() {
let mut brkr = setup();
brkr.deposit_cash(&100_000.0);
brkr.send_order(UistOrder::market_buy("ABC", 495.0));
brkr.check();
let val = brkr.get_position_value("ABC");
brkr.check();
let val1 = brkr.get_position_value("ABC");
assert_ne!(val, val1);
}
#[test]
fn test_that_profit_calculation_is_accurate() {
let mut brkr = setup();
brkr.deposit_cash(&100_000.0);
brkr.send_order(UistOrder::market_buy("ABC", 495.0));
brkr.check();
brkr.check();
let profit = brkr.get_position_profit("ABC").unwrap();
assert_eq!(*profit, -4950.00);
}
#[test]
fn test_that_broker_build_passes_without_trade_costs() {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 101.00, 100, "ABC");
source_builder.add_quote(104.00, 105.00, 101, "ABC");
source_builder.add_quote(95.00, 96.00, 102, "ABC");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let _brkr = UistBrokerBuilder::new()
.with_exchange(uist)
.with_trade_costs(vec![BrokerCost::PctOfValue(0.01)])
.build();
}
#[test]
fn test_that_broker_uses_last_value_if_it_fails_to_find_quote() {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 101.00, 100, "ABC");
source_builder.add_quote(10.00, 11.00, 100, "BCD");
source_builder.add_quote(100.00, 101.00, 101, "ABC");
source_builder.add_quote(10.00, 11.00, 101, "BCD");
source_builder.add_quote(104.00, 105.00, 102, "ABC");
source_builder.add_quote(104.00, 105.00, 103, "ABC");
source_builder.add_quote(12.00, 13.00, 103, "BCD");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let mut brkr = UistBrokerBuilder::new()
.with_exchange(uist)
.with_trade_costs(vec![BrokerCost::PctOfValue(0.01)])
.build();
brkr.deposit_cash(&100_000.0);
brkr.send_order(UistOrder::market_buy("ABC", 100.0));
brkr.send_order(UistOrder::market_buy("BCD", 100.0));
brkr.check();
brkr.check();
let value = brkr
.get_position_value("BCD")
.unwrap_or(CashValue::from(0.0));
println!("{:?}", value);
assert!(*value == 10.0 * 100.0);
brkr.check();
let value1 = brkr
.get_position_value("BCD")
.unwrap_or(CashValue::from(0.0));
println!("{:?}", value1);
assert!(*value1 == 12.0 * 100.0);
}
#[test]
fn test_that_broker_handles_negative_cash_balance_due_to_volatility() {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 101.00, 100, "ABC");
source_builder.add_quote(150.00, 151.00, 101, "ABC");
source_builder.add_quote(150.00, 151.00, 102, "ABC");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let mut brkr = UistBrokerBuilder::new()
.with_exchange(uist)
.with_trade_costs(vec![BrokerCost::PctOfValue(0.01)])
.build();
brkr.deposit_cash(&100_000.0);
brkr.send_order(UistOrder::market_buy("ABC", 700.0));
brkr.check();
let cash = brkr.get_cash_balance();
assert!(*cash < 0.0);
brkr.check();
let cash1 = brkr.get_cash_balance();
assert!(*cash1 > 0.0);
}
#[test]
fn test_that_broker_stops_when_liquidation_fails() {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 101.00, 100, "ABC");
source_builder.add_quote(200.00, 201.00, 101, "ABC");
source_builder.add_quote(200.00, 201.00, 101, "ABC");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let mut brkr = UistBrokerBuilder::new()
.with_exchange(uist)
.with_trade_costs(vec![BrokerCost::PctOfValue(0.01)])
.build();
brkr.deposit_cash(&100_000.0);
brkr.send_order(UistOrder::market_buy("ABC", 990.0));
brkr.check();
brkr.check();
let cash = brkr.get_cash_balance();
assert!(*cash < 0.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 100.0));
assert!(matches!(res, UistBrokerEvent::OrderInvalid { .. }));
assert!(matches!(
brkr.deposit_cash(&100_000.0),
BrokerCashEvent::OperationFailure { .. }
));
assert!(matches!(
brkr.withdraw_cash(&100_000.0),
BrokerCashEvent::OperationFailure { .. }
));
}
#[test]
fn test_that_holdings_updates_correctly() {
let mut brkr = setup();
brkr.deposit_cash(&100_000.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 50.0));
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
assert_eq!(
*brkr
.get_holdings_with_pending()
.get("ABC")
.unwrap_or_default(),
50.0
);
brkr.check();
assert_eq!(*brkr.get_holdings().get("ABC").unwrap_or_default(), 50.0);
let res = brkr.send_order(UistOrder::market_sell("ABC", 10.0));
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
assert_eq!(
*brkr
.get_holdings_with_pending()
.get("ABC")
.unwrap_or_default(),
40.0
);
brkr.check();
assert_eq!(*brkr.get_holdings().get("ABC").unwrap_or_default(), 40.0);
let res = brkr.send_order(UistOrder::market_buy("ABC", 50.0));
assert!(matches!(res, UistBrokerEvent::OrderSentToExchange(..)));
assert_eq!(
*brkr
.get_holdings_with_pending()
.get("ABC")
.unwrap_or_default(),
90.0
);
brkr.check();
assert_eq!(*brkr.get_holdings().get("ABC").unwrap_or_default(), 90.0)
}
fn setup_log() -> UistBrokerLog {
let mut rec = UistBrokerLog::new();
let t1 = UistTrade::new("ABC", 100.0, 10.00, 100, UistTradeType::Buy);
let t2 = UistTrade::new("ABC", 500.0, 90.00, 101, UistTradeType::Buy);
let t3 = UistTrade::new("BCD", 100.0, 100.0, 102, UistTradeType::Buy);
let t4 = UistTrade::new("BCD", 500.0, 100.00, 103, UistTradeType::Sell);
let t5 = UistTrade::new("BCD", 50.0, 50.00, 104, UistTradeType::Buy);
rec.record(t1);
rec.record(t2);
rec.record(t3);
rec.record(t4);
rec.record(t5);
rec
}
#[test]
fn test_that_log_filters_trades_between_dates() {
let log = setup_log();
let between = log.trades_between(&102.into(), &104.into());
assert!(between.len() == 3);
}
#[test]
fn test_that_log_calculates_the_cost_basis() {
let log = setup_log();
let abc_cost = log.cost_basis("ABC").unwrap();
let bcd_cost = log.cost_basis("BCD").unwrap();
assert_eq!(*abc_cost, 6.0);
assert_eq!(*bcd_cost, 1.0);
}
#[test]
fn diff_direction_correct_if_need_to_buy() {
let (uist, clock) = random_uist_generator(100);
let mut brkr = UistBrokerBuilder::new()
.with_trade_costs(vec![BrokerCost::flat(1.0)])
.with_exchange(uist)
.build();
let mut weights = PortfolioAllocation::new();
weights.insert("ABC", 1.0);
brkr.deposit_cash(&100_000.0);
brkr.check();
let orders = brkr.diff_brkr_against_target_weights(&weights);
println!("{:?}", orders);
let first = orders.first().unwrap();
assert!(matches!(
first.get_order_type(),
UistOrderType::MarketBuy { .. }
));
}
#[test]
fn diff_direction_correct_if_need_to_sell() {
let (uist, clock) = random_uist_generator(100);
let mut brkr = UistBrokerBuilder::new()
.with_trade_costs(vec![BrokerCost::flat(1.0)])
.with_exchange(uist)
.build();
let mut weights = PortfolioAllocation::new();
weights.insert("ABC", 1.0);
brkr.deposit_cash(&100_000.0);
let orders = brkr.diff_brkr_against_target_weights(&weights);
brkr.send_orders(&orders);
brkr.check();
brkr.check();
let mut weights1 = PortfolioAllocation::new();
weights1.insert("ABC", 0.01);
let orders1 = brkr.diff_brkr_against_target_weights(&weights1);
println!("{:?}", orders1);
let first = orders1.first().unwrap();
assert!(matches!(
first.get_order_type(),
UistOrderType::MarketSell { .. }
));
}
#[test]
fn diff_continues_if_security_missing() {
let (uist, clock) = random_uist_generator(100);
let mut brkr = UistBrokerBuilder::new()
.with_trade_costs(vec![BrokerCost::flat(1.0)])
.with_exchange(uist)
.build();
let mut weights = PortfolioAllocation::new();
weights.insert("ABC", 0.5);
weights.insert("XYZ", 0.5);
brkr.deposit_cash(&100_000.0);
brkr.check();
let orders = brkr.diff_brkr_against_target_weights(&weights);
assert!(orders.len() == 1);
}
#[test]
#[should_panic]
fn diff_panics_if_brkr_has_no_cash() {
let (uist, clock) = random_uist_generator(100);
let mut brkr = UistBrokerBuilder::new()
.with_trade_costs(vec![BrokerCost::flat(1.0)])
.with_exchange(uist)
.build();
let mut weights = PortfolioAllocation::new();
weights.insert("ABC", 1.0);
brkr.check();
brkr.diff_brkr_against_target_weights(&weights);
}
#[test]
fn can_estimate_trade_costs_of_proposed_trade() {
let pershare = BrokerCost::per_share(0.1);
let flat = BrokerCost::flat(10.0);
let pct = BrokerCost::pct_of_value(0.01);
let res = pershare.trade_impact(&1000.0, &1.0, true);
assert!((*res.1).eq(&1.1));
let res = pershare.trade_impact(&1000.0, &1.0, false);
assert!((*res.1).eq(&0.9));
let res = flat.trade_impact(&1000.0, &1.0, true);
assert!((*res.0).eq(&990.00));
let res = pct.trade_impact(&100.0, &1.0, true);
assert!((*res.0).eq(&99.0));
let costs = vec![pershare, flat];
let initial = BrokerCost::trade_impact_total(&costs, &1000.0, &1.0, true);
assert!((*initial.0).eq(&990.00));
assert!((*initial.1).eq(&1.1));
}
#[test]
fn diff_handles_sent_but_unexecuted_orders() {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 100.00, 100, "ABC");
source_builder.add_quote(100.00, 100.00, 101, "ABC");
source_builder.add_quote(100.00, 100.00, 103, "ABC");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let mut brkr = UistBrokerBuilder::new().with_exchange(uist).build();
brkr.deposit_cash(&100_000.0);
brkr.check();
brkr.check();
let mut target_weights = PortfolioAllocation::new();
target_weights.insert("ABC", 0.9);
let orders = brkr.diff_brkr_against_target_weights(&target_weights);
brkr.send_orders(&orders);
brkr.check();
let orders1 = brkr.diff_brkr_against_target_weights(&target_weights);
brkr.send_orders(&orders1);
brkr.check();
dbg!(brkr.get_position_qty("ABC"));
assert_eq!(*brkr.get_position_qty("ABC").unwrap(), 900.0);
}
#[tokio::test]
async fn diff_handles_case_when_existing_order_requires_sell_to_rebalance() {
let mut source_builder = PenelopeBuilder::new();
source_builder.add_quote(100.00, 100.00, 100, "ABC");
source_builder.add_quote(75.00, 75.00, 103, "ABC");
source_builder.add_quote(75.00, 75.00, 104, "ABC");
let (price_source, clock) =
source_builder.build_with_frequency(rotala::clock::Frequency::Second);
let uist = UistV1::new(clock, price_source, "FAKE");
let mut brkr = UistBrokerBuilder::new().with_exchange(uist).build();
brkr.deposit_cash(&100_000.0);
let mut target_weights = PortfolioAllocation::new();
target_weights.insert("ABC", 0.9);
let orders = brkr.diff_brkr_against_target_weights(&target_weights);
println!("{:?}", orders);
brkr.send_orders(&orders);
brkr.check();
brkr.check();
brkr.check();
let orders1 = brkr.diff_brkr_against_target_weights(&target_weights);
println!("{:?}", orders1);
brkr.send_orders(&orders1);
brkr.check();
println!("{:?}", brkr.get_holdings());
assert_eq!(*brkr.get_position_qty("ABC").unwrap(), 1200.0);
}
}