sandbox_quant/app/
runtime.rs1use crate::app::commands::AppCommand;
2use crate::app::bootstrap::AppBootstrap;
3use crate::execution::command::ExecutionCommand;
4use crate::storage::event_log::log;
5use serde_json::json;
6
7#[derive(Debug, Default)]
8pub struct AppRuntime {
9 pub last_command: Option<AppCommand>,
10}
11
12impl AppRuntime {
13 pub fn record_command(&mut self, command: AppCommand) {
14 self.last_command = Some(command);
15 }
16
17 pub fn run<E: crate::exchange::facade::ExchangeFacade<Error = crate::error::exchange_error::ExchangeError>>(
18 &mut self,
19 app: &mut AppBootstrap<E>,
20 command: AppCommand,
21 ) -> Result<(), crate::error::app_error::AppError> {
22 self.record_command(command.clone());
23
24 match command {
25 AppCommand::Execution(command) => {
26 let report = app
27 .portfolio_sync
28 .refresh_authoritative(&app.exchange, &mut app.portfolio_store)?;
29 log(
30 &mut app.event_log,
31 "app.portfolio.refreshed",
32 json!({
33 "positions": report.positions,
34 "open_order_groups": report.open_order_groups,
35 "balances": report.balances,
36 }),
37 );
38
39 if let ExecutionCommand::SetTargetExposure { instrument, .. } = &command {
40 let market = app
41 .portfolio_store
42 .snapshot
43 .positions
44 .get(instrument)
45 .map(|position| position.market)
46 .or_else(|| {
47 if app
48 .exchange
49 .load_symbol_rules(instrument, crate::domain::market::Market::Futures)
50 .is_ok()
51 {
52 Some(crate::domain::market::Market::Futures)
53 } else if app
54 .exchange
55 .load_symbol_rules(instrument, crate::domain::market::Market::Spot)
56 .is_ok()
57 {
58 Some(crate::domain::market::Market::Spot)
59 } else {
60 None
61 }
62 });
63
64 if let Some(market) = market {
65 let price = app.market_data.refresh_price(
66 &app.exchange,
67 &mut app.price_store,
68 instrument.clone(),
69 market,
70 )?;
71 log(
72 &mut app.event_log,
73 "app.market_data.price_refreshed",
74 json!({
75 "instrument": instrument.0,
76 "market": format!("{market:?}"),
77 "price": price,
78 }),
79 );
80 }
81 }
82 let outcome = app.execution.execute(
83 &app.exchange,
84 &app.portfolio_store,
85 &app.price_store,
86 command.clone(),
87 )?;
88 log(
89 &mut app.event_log,
90 "app.execution.completed",
91 execution_payload(&command, &outcome),
92 );
93 }
94 AppCommand::RefreshAuthoritativeState => {
95 let report = app
96 .portfolio_sync
97 .refresh_authoritative(&app.exchange, &mut app.portfolio_store)?;
98 log(
99 &mut app.event_log,
100 "app.portfolio.refreshed",
101 json!({
102 "positions": report.positions,
103 "open_order_groups": report.open_order_groups,
104 "balances": report.balances,
105 }),
106 );
107 }
108 }
109
110 Ok(())
111 }
112}
113
114fn execution_payload(
115 command: &ExecutionCommand,
116 outcome: &crate::execution::service::ExecutionOutcome,
117) -> serde_json::Value {
118 match (command, outcome) {
119 (
120 ExecutionCommand::SetTargetExposure {
121 instrument, target, ..
122 },
123 crate::execution::service::ExecutionOutcome::TargetExposureSubmitted { .. },
124 ) => json!({
125 "command_kind": "set_target_exposure",
126 "instrument": instrument.0,
127 "target": target.value(),
128 "outcome_kind": "submitted",
129 }),
130 (
131 ExecutionCommand::CloseSymbol { instrument, .. },
132 crate::execution::service::ExecutionOutcome::CloseSymbol(result),
133 ) => json!({
134 "command_kind": "close_symbol",
135 "instrument": instrument.0,
136 "outcome_kind": format!("{:?}", result.result),
137 }),
138 (
139 ExecutionCommand::CloseAll { .. },
140 crate::execution::service::ExecutionOutcome::CloseAll(result),
141 ) => {
142 let submitted = result
143 .results
144 .iter()
145 .filter(|item| matches!(item.result, crate::execution::close_symbol::CloseSubmitResult::Submitted))
146 .count();
147 let skipped = result
148 .results
149 .iter()
150 .filter(|item| matches!(item.result, crate::execution::close_symbol::CloseSubmitResult::SkippedNoPosition))
151 .count();
152 let rejected = result
153 .results
154 .iter()
155 .filter(|item| matches!(item.result, crate::execution::close_symbol::CloseSubmitResult::Rejected))
156 .count();
157 json!({
158 "command_kind": "close_all",
159 "batch_id": result.batch_id.0,
160 "submitted": submitted,
161 "skipped": skipped,
162 "rejected": rejected,
163 "outcome_kind": "batch_completed",
164 })
165 }
166 _ => json!({
167 "command_kind": "unknown",
168 "outcome_kind": "unknown",
169 }),
170 }
171}