stock_trek/examples/
cost_averaging.rs1use crate::prelude::*;
2use std::cmp::Ordering;
3
4pub struct CostAveraging {
5 key_cex: SignalKey<CexId>,
6 key_market_exists: SignalKey<bool>,
7 key_satoshi_price: SignalKey<f64>,
8 key_satoshi_quantity: SignalKey<f64>,
9}
10
11impl Default for CostAveraging {
12 fn default() -> Self {
13 Self {
14 key_cex: SignalKey::new_required("CEX"),
15 key_market_exists: SignalKey::new_optional("MARKET_EXISTS", false),
16 key_satoshi_price: SignalKey::new_required("SATOSHI_PRICE"),
17 key_satoshi_quantity: SignalKey::new_required("SATOSHI_QUANTITY"),
18 }
19 }
20}
21
22#[register_algorithm(default)]
23impl Algorithm for CostAveraging {
24 fn preferences(&self) -> Preferences {
25 Preferences {
26 cex: CexPreferences {
27 max_network_delay_millis: 5000,
28 rounding: Rounding {
29 activation_price_triggered_above: RoundingStrategy::AwayFromZero,
30 activation_price_triggered_below: RoundingStrategy::ToZero,
31 price: RoundingStrategy::ToZero,
32 quantity: RoundingStrategy::ToZero,
33 callback_rate_bps: RoundingStrategy::ToZero,
34 },
35 },
36 }
37 }
38 fn signals(&self, c: &SignalContext) -> Signals {
39 let mut signals = Signals::new();
40 let one_millionth = 1.0 / 1_000_000.0;
41 signals.write(&self.key_satoshi_quantity, one_millionth);
42 let iter = c.cex_markets_for(AssetId::bitcoin(), AssetId::tether_usd());
43 let min_by_last_ask = iter.min_by(|(_a_exch, a_market), (_b_exch, b_market)| {
44 let a_last_ask = a_market.ticks.ticks[0].ask.price;
45 let b_last_ask = b_market.ticks.ticks[0].ask.price;
46 a_last_ask.partial_cmp(&b_last_ask).unwrap()
47 });
48 if let Some((cheapest_cex_name, market)) = min_by_last_ask {
49 signals.write(&self.key_cex, cheapest_cex_name);
50 signals.write(&self.key_market_exists, true);
51 let satoshi_price = market.ticks.ticks[0].ask.price / 1_000_000.0;
52 signals.write(&self.key_satoshi_price, satoshi_price);
53 }
54 signals
55 }
56 fn strategy(&self, c: &StrategyContext) -> Command {
57 let cex = c.signals.cex_id(&self.key_cex);
58 let btc = c.literals.asset_id(AssetId::bitcoin());
59 let usdt = c.literals.asset_id(AssetId::tether_usd());
60 let satoshi_price = c.signals.number(&self.key_satoshi_price);
61 let quantity = c.signals.number(&self.key_satoshi_quantity);
62 c.commands.if_else(
63 c.conditions.signal(&self.key_market_exists),
64 c.commands.if_else(
65 c.conditions.compare(
66 c.portfolio.asset_in_cex(cex.clone(), usdt.clone()),
67 Ordering::Greater,
68 satoshi_price,
69 ),
70 c.commands.plan(vec![c.actions.send_order_request(
71 cex.clone(),
72 c.orders.single(
73 btc,
74 usdt.clone(),
75 OrderSide::Buy,
76 OrderActivation::Immediate,
77 OrderPricing::Market,
78 OrderQuantity::OfQuote(quantity),
79 vec![OrderConstraint::FillPolicy {
80 allow_partial: true,
81 }],
82 OrderTag::new("CostAveraging"),
83 ),
84 RecoveryPolicy::with_default(ErrorResponse::Stop).on_error(
85 ErrorCause::TemporaryCexRejection,
86 ErrorResponse::Retry { max_retries: 3 },
87 ),
88 )]),
89 c.commands.no_op(),
90 ),
91 c.commands.no_op(),
92 )
93 }
94}