1use std::collections::BTreeMap;
9
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct Root {
18 pub name: String,
19 pub version: String,
20 pub status: String,
21 pub ts: Option<String>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct Health {
29 pub status: String,
31 #[serde(default)]
32 pub components: BTreeMap<String, ComponentHealth>,
33 #[serde(default)]
34 pub dependencies: BTreeMap<String, String>,
35 #[serde(default)]
36 pub circuit_breakers: BTreeMap<String, String>,
37 #[serde(default)]
38 pub risk: RiskSummary,
39 #[serde(default)]
40 pub recovery: Option<RecoveryStatus>,
41 #[serde(default)]
42 pub ws_connections: u64,
43}
44
45#[derive(Debug, Clone, Default, Serialize, Deserialize)]
47#[serde(default)]
48pub struct RecoveryStatus {
49 pub status: Option<String>,
50 pub source: Option<String>,
51 pub durable: bool,
52 pub journal_path: Option<String>,
53 pub decisions_recovered: Option<u32>,
54 pub fills_recovered: Option<u32>,
55 pub rejections_recovered: Option<u32>,
56 pub positions_recovered: Option<u32>,
57 pub last_decision_at: Option<String>,
58 pub current_decisions: Option<u32>,
59 pub current_fills: Option<u32>,
60 pub current_rejections: Option<u32>,
61 pub current_positions: Option<u32>,
62 #[serde(flatten)]
63 pub extra: BTreeMap<String, Value>,
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize)]
69#[serde(default)]
70pub struct HyperliquidStatus {
71 pub enabled: bool,
72 pub exchange: Option<String>,
73 pub endpoint: Option<String>,
74 pub coins: Option<u32>,
75 pub mids: BTreeMap<String, f64>,
76 pub secrets_required: Option<bool>,
77 pub reason: Option<String>,
78 #[serde(flatten)]
79 pub extra: BTreeMap<String, Value>,
80}
81
82#[derive(Debug, Clone, Default, Serialize, Deserialize)]
85#[serde(default)]
86pub struct HyperliquidAccount {
87 pub schema_version: String,
88 pub exchange: String,
89 pub user: String,
90 pub as_of: Option<String>,
91 pub account_value: Option<f64>,
92 pub margin_used: Option<f64>,
93 pub withdrawable: Option<f64>,
94 pub positions: Vec<HyperliquidAccountPosition>,
95 pub open_orders: Vec<Value>,
96 #[serde(flatten)]
97 pub extra: BTreeMap<String, Value>,
98}
99
100#[derive(Debug, Clone, Default, Serialize, Deserialize)]
101#[serde(default)]
102pub struct HyperliquidAccountPosition {
103 pub symbol: String,
104 pub side: String,
105 pub quantity: f64,
106 pub entry_price: f64,
107 pub position_value: f64,
108 pub unrealized_pnl: f64,
109 pub margin_used: f64,
110 #[serde(flatten)]
111 pub extra: BTreeMap<String, Value>,
112}
113
114#[derive(Debug, Clone, Default, Serialize, Deserialize)]
115#[serde(default)]
116pub struct HyperliquidReconciliation {
117 pub schema_version: String,
118 pub exchange: String,
119 pub status: String,
120 pub risk_increasing_allowed: bool,
121 pub reason: String,
122 pub as_of: Option<String>,
123 pub drifts: Vec<HyperliquidReconciliationDrift>,
124 #[serde(flatten)]
125 pub extra: BTreeMap<String, Value>,
126}
127
128#[derive(Debug, Clone, Default, Serialize, Deserialize)]
129#[serde(default)]
130pub struct HyperliquidReconciliationDrift {
131 pub code: String,
132 pub severity: String,
133 pub symbol: Option<String>,
134 pub reason: String,
135 pub local_quantity: Option<f64>,
136 pub exchange_quantity: Option<f64>,
137 #[serde(flatten)]
138 pub extra: BTreeMap<String, Value>,
139}
140
141#[derive(Debug, Clone, Default, Serialize, Deserialize)]
144#[serde(default)]
145pub struct MarketQuote {
146 pub symbol: String,
147 pub price: f64,
148 pub source: String,
149 pub as_of: Option<String>,
150 pub mode: Option<String>,
151 pub live: bool,
152 #[serde(flatten)]
153 pub extra: BTreeMap<String, Value>,
154}
155
156#[derive(Debug, Clone, Default, Serialize, Deserialize)]
159#[serde(default)]
160pub struct LivePreflight {
161 pub schema_version: String,
162 pub exchange: String,
163 pub mode: String,
164 pub ready: bool,
165 pub live_mode: String,
166 pub controls_ready: bool,
167 pub checks: Vec<LivePreflightCheck>,
168 #[serde(flatten)]
169 pub extra: BTreeMap<String, Value>,
170}
171
172#[derive(Debug, Clone, Default, Serialize, Deserialize)]
173#[serde(default)]
174pub struct LivePreflightCheck {
175 pub name: String,
176 pub status: String,
177 pub note: String,
178 #[serde(flatten)]
179 pub extra: BTreeMap<String, Value>,
180}
181
182#[derive(Debug, Clone, Default, Serialize, Deserialize)]
185#[serde(default)]
186pub struct LiveCertification {
187 pub schema_version: String,
188 pub mode: String,
189 pub passed: bool,
190 pub live_start_certified: bool,
191 pub summary: BTreeMap<String, Value>,
192 pub drills: Vec<LiveCertificationDrill>,
193 pub evidence_requirements: Vec<String>,
194 #[serde(flatten)]
195 pub extra: BTreeMap<String, Value>,
196}
197
198#[derive(Debug, Clone, Default, Serialize, Deserialize)]
199#[serde(default)]
200pub struct LiveCertificationDrill {
201 pub name: String,
202 pub status: String,
203 pub note: String,
204 pub evidence: BTreeMap<String, Value>,
205 #[serde(flatten)]
206 pub extra: BTreeMap<String, Value>,
207}
208
209#[derive(Debug, Clone, Default, Serialize, Deserialize)]
212#[serde(default)]
213pub struct LiveEvidence {
214 pub schema_version: String,
215 pub generated_at: Option<String>,
216 pub mode: String,
217 pub live_mode: String,
218 pub ready: bool,
219 pub risk_increasing_allowed: bool,
220 pub operator_context: OperatorContext,
221 pub summary: BTreeMap<String, Value>,
222 pub artifacts: Vec<LiveEvidenceArtifact>,
223 pub canary_rule: BTreeMap<String, Value>,
224 pub privacy: BTreeMap<String, Value>,
225 pub evidence_hash: String,
226 pub signature: BTreeMap<String, Value>,
227 #[serde(flatten)]
228 pub extra: BTreeMap<String, Value>,
229}
230
231#[derive(Debug, Clone, Default, Serialize, Deserialize)]
232#[serde(default)]
233pub struct LiveEvidenceArtifact {
234 pub name: String,
235 pub schema_version: String,
236 pub status: String,
237 pub hash: String,
238 pub included: String,
239 #[serde(flatten)]
240 pub extra: BTreeMap<String, Value>,
241}
242
243#[derive(Debug, Clone, Default, Serialize, Deserialize)]
246#[serde(default)]
247pub struct LiveCanaryPolicy {
248 pub schema_version: String,
249 pub policy_version: String,
250 pub generated_at: Option<String>,
251 pub mode: String,
252 pub summary: LiveCanaryPolicySummary,
253 pub policy: BTreeMap<String, Value>,
254 pub phases: Vec<LiveCanaryPolicyPhase>,
255 pub recommendation: LiveCanaryRecommendation,
256 pub operator_context: OperatorContext,
257 pub request: Option<BTreeMap<String, Value>>,
258 pub privacy: BTreeMap<String, Value>,
259 #[serde(flatten)]
260 pub extra: BTreeMap<String, Value>,
261}
262
263#[derive(Debug, Clone, Default, Serialize, Deserialize)]
264#[allow(clippy::struct_excessive_bools)] #[serde(default)]
266pub struct LiveCanaryPolicySummary {
267 pub ready_for_canary: bool,
268 pub policy_armed: bool,
269 pub live_order_attempted: bool,
270 pub live_order_accepted: bool,
271 pub receipts_accepted: u64,
272 pub exchange_evidence_attached: bool,
273 pub publishable_canary_evidence: bool,
274 pub refusal_evidence_qualified: bool,
275 pub qualified: bool,
276 pub next_step: String,
277 #[serde(flatten)]
278 pub extra: BTreeMap<String, Value>,
279}
280
281#[derive(Debug, Clone, Default, Serialize, Deserialize)]
282#[serde(default)]
283pub struct LiveCanaryPolicyPhase {
284 pub name: String,
285 pub status: String,
286 pub detail: String,
287 #[serde(flatten)]
288 pub extra: BTreeMap<String, Value>,
289}
290
291#[derive(Debug, Clone, Default, Serialize, Deserialize)]
292#[serde(default)]
293pub struct LiveCanaryRecommendation {
294 pub action: String,
295 pub risk_direction: String,
296 pub reason: String,
297 #[serde(flatten)]
298 pub extra: BTreeMap<String, Value>,
299}
300
301#[derive(Debug, Clone, Default, Serialize, Deserialize)]
304#[allow(clippy::struct_excessive_bools)] #[serde(default)]
306pub struct RuntimeParity {
307 pub schema_version: String,
308 pub available: bool,
309 pub ok: bool,
310 pub mode: String,
311 pub source: Option<String>,
312 pub generated_at: Option<String>,
313 pub cycles_requested: u64,
314 pub cycles_run: u64,
315 pub paper_only: bool,
316 pub places_live_orders: bool,
317 pub paper: RuntimeParityPaper,
318 pub live_shadow: RuntimeParityLiveShadow,
319 pub feedback: RuntimeParityFeedback,
320 pub certification: LiveCertification,
321 pub checks: BTreeMap<String, Value>,
322 pub claim_boundary: BTreeMap<String, Value>,
323 #[serde(flatten)]
324 pub extra: BTreeMap<String, Value>,
325}
326
327#[derive(Debug, Clone, Default, Serialize, Deserialize)]
328#[serde(default)]
329pub struct RuntimeParityPaper {
330 pub decisions: u64,
331 pub fills: u64,
332 pub rejections: u64,
333 pub open_positions: u64,
334 #[serde(flatten)]
335 pub extra: BTreeMap<String, Value>,
336}
337
338#[derive(Debug, Clone, Default, Serialize, Deserialize)]
339#[serde(default)]
340pub struct RuntimeParityLiveShadow {
341 pub mode: String,
342 pub accepted: u64,
343 pub refused: u64,
344 pub adapter_orders_placed: u64,
345 pub records: Vec<Value>,
346 #[serde(flatten)]
347 pub extra: BTreeMap<String, Value>,
348}
349
350#[derive(Debug, Clone, Default, Serialize, Deserialize)]
351#[serde(default)]
352pub struct RuntimeParityFeedback {
353 pub schema_version: String,
354 pub cycles: u64,
355 pub sample_size: u64,
356 pub fills: u64,
357 pub rejections: u64,
358 pub rejection_rate: f64,
359 pub by_rejection_reason: BTreeMap<String, u64>,
360 pub by_rejection_symbol: BTreeMap<String, u64>,
361 pub items: Vec<Value>,
362 #[serde(flatten)]
363 pub extra: BTreeMap<String, Value>,
364}
365
366#[derive(Debug, Clone, Default, Serialize, Deserialize)]
369#[serde(default)]
370pub struct LiveExecutionReceipts {
371 pub schema_version: String,
372 pub generated_at: Option<String>,
373 pub mode: String,
374 pub operator_context: OperatorContext,
375 pub summary: BTreeMap<String, Value>,
376 pub receipts: Vec<LiveExecutionReceipt>,
377 pub privacy: BTreeMap<String, Value>,
378 pub receipts_hash: String,
379 #[serde(flatten)]
380 pub extra: BTreeMap<String, Value>,
381}
382
383#[derive(Debug, Clone, Default, Serialize, Deserialize)]
384#[serde(default)]
385pub struct LiveExecutionReceipt {
386 pub schema_version: String,
387 pub accepted: bool,
388 pub status: String,
389 pub reason: String,
390 pub as_of: Option<f64>,
391 pub request: BTreeMap<String, Value>,
392 pub request_hash: String,
393 pub operator_context_hash: Option<String>,
394 pub trace_hash: Option<String>,
395 pub idempotency_hash: Option<String>,
396 pub venue_ack_hash: Option<String>,
397 pub receipt_hash: String,
398 #[serde(flatten)]
399 pub extra: BTreeMap<String, Value>,
400}
401
402#[derive(Debug, Clone, Default, Serialize, Deserialize)]
405#[serde(default)]
406pub struct LiveCockpit {
407 pub schema_version: String,
408 pub generated_at: Option<String>,
409 pub mode: String,
410 pub live_mode: String,
411 pub ready: bool,
412 pub controls_ready: bool,
413 pub risk_increasing_allowed: bool,
414 pub next_action: String,
415 pub operator_context: OperatorContext,
416 pub preflight: LiveCockpitPreflight,
417 pub immune: LiveCockpitImmune,
418 pub reconciliation: LiveCockpitReconciliation,
419 pub certification: LiveCockpitCertification,
420 pub heartbeat: LiveCockpitHeartbeat,
421 pub live_records: LiveCockpitRecords,
422 pub operator_actions: BTreeMap<String, Value>,
423 #[serde(flatten)]
424 pub extra: BTreeMap<String, Value>,
425}
426
427#[derive(Debug, Clone, Default, Serialize, Deserialize)]
428#[serde(default)]
429pub struct OperatorContext {
430 pub schema_version: Option<String>,
431 pub operator_id: String,
432 pub handle: String,
433 pub role: String,
434 pub scope: String,
435 pub source: Option<String>,
436 #[serde(flatten)]
437 pub extra: BTreeMap<String, Value>,
438}
439
440#[derive(Debug, Clone, Default, Serialize, Deserialize)]
441#[serde(default)]
442pub struct LiveCockpitPreflight {
443 pub schema_version: String,
444 pub ready: bool,
445 pub live_mode: String,
446 pub controls_ready: bool,
447 pub summary: BTreeMap<String, Value>,
448 pub failed_checks: Vec<LivePreflightCheck>,
449 #[serde(flatten)]
450 pub extra: BTreeMap<String, Value>,
451}
452
453#[derive(Debug, Clone, Default, Serialize, Deserialize)]
454#[serde(default)]
455pub struct LiveCockpitImmune {
456 pub schema_version: String,
457 pub risk_increasing_allowed: bool,
458 pub summary: BTreeMap<String, Value>,
459 pub open_breakers: Vec<ImmuneBreaker>,
460 #[serde(flatten)]
461 pub extra: BTreeMap<String, Value>,
462}
463
464#[derive(Debug, Clone, Default, Serialize, Deserialize)]
465#[serde(default)]
466pub struct LiveCockpitReconciliation {
467 pub schema_version: String,
468 pub status: String,
469 pub risk_increasing_allowed: bool,
470 pub reason: String,
471 pub drifts: u64,
472 #[serde(flatten)]
473 pub extra: BTreeMap<String, Value>,
474}
475
476#[derive(Debug, Clone, Default, Serialize, Deserialize)]
477#[serde(default)]
478pub struct LiveCockpitCertification {
479 pub schema_version: String,
480 pub mode: String,
481 pub passed: bool,
482 pub live_start_certified: bool,
483 pub summary: BTreeMap<String, Value>,
484 pub failed_drills: Vec<LiveCertificationDrill>,
485 #[serde(flatten)]
486 pub extra: BTreeMap<String, Value>,
487}
488
489#[derive(Debug, Clone, Default, Serialize, Deserialize)]
490#[serde(default)]
491pub struct LiveCockpitHeartbeat {
492 pub configured: bool,
493 pub expired: bool,
494 pub last_heartbeat_at: Option<f64>,
495 pub timeout_s: Option<f64>,
496 #[serde(flatten)]
497 pub extra: BTreeMap<String, Value>,
498}
499
500#[derive(Debug, Clone, Default, Serialize, Deserialize)]
501#[serde(default)]
502pub struct LiveCockpitRecords {
503 pub total: u64,
504 pub accepted: u64,
505 pub refused: u64,
506 pub exchange_error: u64,
507 pub recent: Vec<Value>,
508 #[serde(flatten)]
509 pub extra: BTreeMap<String, Value>,
510}
511
512#[derive(Debug, Clone, Default, Serialize, Deserialize)]
515#[serde(default)]
516pub struct ImmuneReport {
517 pub schema_version: String,
518 pub generated_at: Option<String>,
519 pub mode: String,
520 pub risk_increasing_allowed: bool,
521 pub summary: BTreeMap<String, Value>,
522 pub breakers: Vec<ImmuneBreaker>,
523 #[serde(flatten)]
524 pub extra: BTreeMap<String, Value>,
525}
526
527#[derive(Debug, Clone, Default, Serialize, Deserialize)]
528#[serde(default)]
529pub struct ImmuneBreaker {
530 pub name: String,
531 pub status: String,
532 pub blocks_risk: bool,
533 pub severity: String,
534 pub reason: String,
535 pub evidence: BTreeMap<String, Value>,
536 #[serde(flatten)]
537 pub extra: BTreeMap<String, Value>,
538}
539
540#[derive(Debug, Clone, Default, Serialize, Deserialize)]
551#[serde(default)]
552pub struct LiveControlResponse {
553 pub ok: bool,
554 pub state: Option<String>,
555 pub reason: Option<String>,
556 pub orders: Vec<Value>,
557 pub operator_context: Option<OperatorContext>,
558 pub action: Option<String>,
559 pub risk_direction: Option<String>,
560 #[serde(flatten)]
561 pub extra: BTreeMap<String, Value>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565pub struct ComponentHealth {
566 pub status: String,
567 pub last_seen: Option<String>,
568 #[serde(default)]
569 pub age_s: Option<f64>,
570}
571
572impl ComponentHealth {
573 #[must_use]
574 pub fn is_healthy(&self) -> bool {
575 self.status == "healthy"
576 }
577
578 #[must_use]
579 pub fn is_dead(&self) -> bool {
580 self.status == "dead"
581 }
582}
583
584#[derive(Debug, Clone, Default, Serialize, Deserialize)]
585pub struct RiskSummary {
586 pub equity: Option<f64>,
587 pub drawdown_pct: Option<f64>,
588 #[serde(default)]
589 pub kill_all: bool,
590}
591
592impl Health {
593 #[must_use]
594 pub fn is_ok(&self) -> bool {
595 self.status == "ok"
596 }
597
598 #[must_use]
599 pub fn component_counts(&self) -> ComponentCounts {
600 let mut c = ComponentCounts::default();
601 for comp in self.components.values() {
602 match comp.status.as_str() {
603 "healthy" => c.healthy += 1,
604 "stale" => c.stale += 1,
605 "dead" => c.dead += 1,
606 _ => c.unknown += 1,
607 }
608 }
609 c
610 }
611}
612
613#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
614pub struct ComponentCounts {
615 pub healthy: u32,
616 pub stale: u32,
617 pub dead: u32,
618 pub unknown: u32,
619}
620
621#[derive(Debug, Clone, Default, Serialize, Deserialize)]
625#[serde(default)]
626pub struct Position {
627 #[serde(alias = "coin")]
631 pub symbol: String,
632 #[serde(alias = "direction")]
635 pub side: String,
636 #[serde(alias = "size_coins")]
639 pub size: f64,
640 #[serde(alias = "entry_price")]
641 pub entry: f64,
642 pub mark: Option<f64>,
643 pub unrealized_pnl: Option<f64>,
644 pub unrealized_r: Option<f64>,
645 pub stop: Option<f64>,
646 pub target: Option<f64>,
647 pub lens_id: Option<String>,
648 pub age_s: Option<f64>,
649 #[serde(flatten)]
650 pub extra: BTreeMap<String, Value>,
651}
652
653#[derive(Debug, Clone, Default, Serialize, Deserialize)]
657pub struct Positions {
658 #[serde(alias = "positions", default)]
659 pub items: Vec<Position>,
660 #[serde(default)]
661 pub account_value: Option<f64>,
662 #[serde(default)]
663 pub total_unrealized_pnl: Option<f64>,
664}
665
666#[allow(clippy::struct_excessive_bools)]
694#[derive(Debug, Clone, Default, Serialize, Deserialize)]
695#[serde(default)]
696pub struct Risk {
697 pub account_value: Option<f64>,
698 pub drawdown_pct: Option<f64>,
699 pub daily_pnl_usd: Option<f64>,
700 pub daily_loss_usd: Option<f64>,
701 pub peak_equity: Option<f64>,
702 pub peak_equity_30d: Option<f64>,
703 pub open_count: Option<u32>,
704 pub halted: bool,
705 pub global_halt: bool,
706 pub stop_failure_halt: bool,
707 pub capital_floor_hit: bool,
708 pub halt_reason: Option<String>,
709 pub halt_until: Option<String>,
710 pub updated_at: Option<String>,
711 pub daily_loss_since: Option<String>,
712 pub last_drawdown_alert_pct: Option<f64>,
713 pub per_runner: BTreeMap<String, Value>,
714 #[serde(flatten)]
715 pub extra: BTreeMap<String, Value>,
716}
717
718impl Risk {
719 #[must_use]
724 pub fn is_halted(&self) -> bool {
725 self.halted || self.global_halt || self.stop_failure_halt
726 }
727
728 #[must_use]
733 pub fn daily_loss_pct(&self) -> Option<f64> {
734 let loss = self.daily_loss_usd?;
735 let peak = self.peak_equity.or(self.peak_equity_30d)?;
736 if peak <= 0.0 {
737 return None;
738 }
739 Some((loss / peak) * 100.0)
740 }
741}
742
743#[derive(Debug, Clone, Default, Serialize, Deserialize)]
749#[serde(default)]
750pub struct Regime {
751 pub regime: Option<String>,
753 pub confidence: Option<f64>,
754 pub coin: Option<String>,
755 #[serde(flatten)]
756 pub extra: BTreeMap<String, Value>,
757}
758
759#[derive(Debug, Clone, Default, Serialize, Deserialize)]
772#[serde(default)]
773pub struct Brief {
774 pub timestamp: Option<String>,
775 pub fear_greed: Option<i64>,
776 pub open_positions: Option<u32>,
777 pub positions: Vec<Position>,
778 pub recent_signals: Vec<Value>,
779 pub approaching: Vec<Value>,
780 pub last_cycle: Value,
781 #[serde(flatten)]
782 pub extra: BTreeMap<String, Value>,
783}
784
785impl Brief {
786 #[must_use]
792 pub fn has_content(&self) -> bool {
793 self.fear_greed.is_some()
794 || self.open_positions.is_some_and(|n| n > 0)
795 || !self.positions.is_empty()
796 || !self.recent_signals.is_empty()
797 || !self.approaching.is_empty()
798 || !self.last_cycle.is_null()
799 && self.last_cycle.as_object().is_some_and(|o| !o.is_empty())
800 }
801}
802
803#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
811#[serde(default)]
812pub struct EvaluationLayer {
813 pub layer: String,
814 pub passed: bool,
815 pub value: Value,
816 pub detail: String,
817}
818
819#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
830#[serde(default)]
831pub struct Evaluation {
832 pub coin: Option<String>,
833 pub price: Option<f64>,
834 pub consensus: Option<i64>,
835 pub conviction: Option<f64>,
836 pub direction: Option<String>,
838 pub regime: Option<String>,
839 pub layers: Vec<EvaluationLayer>,
840 pub data_fresh: Option<bool>,
841 pub timestamp: Option<String>,
842 #[serde(flatten)]
843 pub extra: BTreeMap<String, Value>,
844}
845
846impl Evaluation {
847 #[must_use]
853 pub fn verdict(&self) -> &'static str {
854 if self.layers.iter().any(|l| !l.passed) {
855 "REJECT"
856 } else if self
857 .direction
858 .as_deref()
859 .is_some_and(|d| d.eq_ignore_ascii_case("LONG") || d.eq_ignore_ascii_case("SHORT"))
860 {
861 "PASS"
862 } else {
863 "HOLD"
864 }
865 }
866}
867
868#[derive(Debug, Clone, Default, Serialize, Deserialize)]
873#[serde(default)]
874pub struct PulseEvent {
875 pub kind: Option<String>,
876 pub coin: Option<String>,
877 pub message: Option<String>,
878 pub ts: Option<String>,
879 pub severity: Option<String>,
880 #[serde(flatten)]
881 pub extra: BTreeMap<String, Value>,
882}
883
884#[derive(Debug, Clone, Default, Serialize, Deserialize)]
886pub struct Pulse {
887 #[serde(alias = "pulse", alias = "events", default)]
888 pub items: Vec<PulseEvent>,
889}
890
891#[derive(Debug, Clone, Default, Serialize, Deserialize)]
895#[serde(default)]
896pub struct Approaching {
897 pub coin: String,
898 pub direction: Option<String>,
899 pub distance_to_gate: Option<f64>,
900 pub gate: Option<String>,
901 pub ts: Option<String>,
902 #[serde(flatten)]
903 pub extra: BTreeMap<String, Value>,
904}
905
906#[derive(Debug, Clone, Default, Serialize, Deserialize)]
908pub struct ApproachingFeed {
909 #[serde(alias = "approaching", alias = "items", default)]
910 pub items: Vec<Approaching>,
911}
912
913#[derive(Debug, Clone, Default, Serialize, Deserialize)]
917#[serde(default)]
918pub struct Rejection {
919 pub coin: Option<String>,
920 pub direction: Option<String>,
921 pub stage: Option<String>,
922 pub reason: Option<String>,
923 pub ts: Option<String>,
924 #[serde(flatten)]
925 pub extra: BTreeMap<String, Value>,
926}
927
928#[derive(Debug, Clone, Default, Serialize, Deserialize)]
930pub struct RejectionsFeed {
931 #[serde(alias = "rejections", alias = "items", default)]
932 pub items: Vec<Rejection>,
933}
934
935#[derive(Debug, Clone, Default, Serialize, Deserialize)]
939#[serde(default)]
940pub struct V2Confidence {
941 pub score: Option<f64>,
943 pub level: Option<String>,
945}
946
947#[derive(Debug, Clone, Default, Serialize, Deserialize)]
949#[serde(default)]
950pub struct V2Market {
951 pub regime: Option<String>,
952 pub health: Option<f64>,
953 pub signal: Option<String>,
954 pub prediction: Option<String>,
955 pub fear_greed: Option<i64>,
956 pub coins_tradeable: Option<u32>,
957 #[serde(flatten)]
958 pub extra: BTreeMap<String, Value>,
959}
960
961#[derive(Debug, Clone, Default, Serialize, Deserialize)]
963#[serde(default)]
964pub struct V2Positions {
965 pub open: Option<u32>,
966 pub unrealized_pnl: Option<f64>,
967 pub equity: Option<f64>,
968 #[serde(flatten)]
969 pub extra: BTreeMap<String, Value>,
970}
971
972#[derive(Debug, Clone, Default, Serialize, Deserialize)]
974#[serde(default)]
975pub struct V2Today {
976 pub trades: Option<u32>,
977 pub wins: Option<u32>,
978 pub pnl: Option<f64>,
979 pub streak: Option<i32>,
980 pub sizing_mult: Option<f64>,
981 #[serde(flatten)]
982 pub extra: BTreeMap<String, Value>,
983}
984
985#[derive(Debug, Clone, Default, Serialize, Deserialize)]
999#[serde(default)]
1000pub struct V2Status {
1001 pub confidence: V2Confidence,
1002 pub market: V2Market,
1003 pub positions: V2Positions,
1004 pub today: V2Today,
1005 pub approaching: Vec<Value>,
1006 pub blind_spots: Vec<Value>,
1007 pub alert: Option<Value>,
1008 pub recovery: Option<RecoveryStatus>,
1009 pub ts: Option<String>,
1010 pub hl_rate: Option<HlRate>,
1022 #[serde(flatten)]
1023 pub extra: BTreeMap<String, Value>,
1024}
1025
1026#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq, Eq)]
1040pub struct HlRate {
1041 pub used: u32,
1042 pub cap: u32,
1043}
1044
1045impl V2Status {
1046 #[must_use]
1048 pub fn regime(&self) -> Option<&str> {
1049 self.market.regime.as_deref()
1050 }
1051
1052 #[must_use]
1058 pub fn engine_confidence(&self) -> Option<f64> {
1059 self.confidence.score
1060 }
1061
1062 #[must_use]
1064 pub fn confidence_level(&self) -> Option<&str> {
1065 self.confidence.level.as_deref()
1066 }
1067
1068 #[must_use]
1070 pub fn equity(&self) -> Option<f64> {
1071 self.positions.equity
1072 }
1073
1074 #[must_use]
1076 pub fn open(&self) -> Option<u32> {
1077 self.positions.open
1078 }
1079
1080 #[must_use]
1082 pub fn unrealized_pnl(&self) -> Option<f64> {
1083 self.positions.unrealized_pnl
1084 }
1085
1086 #[must_use]
1090 #[allow(clippy::unused_self)]
1091 pub fn drawdown_pct(&self) -> Option<f64> {
1092 None
1093 }
1094}
1095
1096#[derive(Debug, Clone, Deserialize, Serialize)]
1108pub struct OperatorEventsAccepted {
1109 pub accepted: u32,
1110 pub snapshot: zero_operator_state::Snapshot,
1111}
1112
1113#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1135#[serde(rename_all = "lowercase")]
1136pub enum ExecuteSide {
1137 Buy,
1138 Sell,
1139}
1140
1141impl ExecuteSide {
1142 #[must_use]
1143 pub const fn as_wire(self) -> &'static str {
1144 match self {
1145 Self::Buy => "buy",
1146 Self::Sell => "sell",
1147 }
1148 }
1149}
1150
1151#[derive(Debug, Clone, Serialize, Deserialize)]
1165pub struct ExecuteRequest {
1166 pub coin: String,
1167 pub side: ExecuteSide,
1168 pub size: f64,
1169 pub idempotency_key: String,
1170}
1171
1172#[derive(Debug, Clone, Serialize, Deserialize)]
1188pub struct ExecuteResponse {
1189 pub accepted: bool,
1190 #[serde(default)]
1191 pub simulated: bool,
1192 #[serde(default)]
1193 pub fill_id: Option<String>,
1194 #[serde(default)]
1195 pub coin: Option<String>,
1196 #[serde(default)]
1197 pub side: Option<ExecuteSide>,
1198 #[serde(default)]
1199 pub size: Option<f64>,
1200 #[serde(default)]
1201 pub reason: Option<String>,
1202 #[serde(flatten)]
1203 pub extra: BTreeMap<String, Value>,
1204}
1205
1206#[derive(Debug, Clone, Serialize, Deserialize)]
1219pub struct AutoToggleRequest {
1220 pub enabled: bool,
1221}
1222
1223#[derive(Debug, Clone, Serialize, Deserialize)]
1234pub struct AutoToggleResponse {
1235 pub state: AutoState,
1236 #[serde(default)]
1237 pub simulated: bool,
1238 #[serde(default)]
1239 pub reason: Option<String>,
1240 #[serde(flatten)]
1241 pub extra: BTreeMap<String, Value>,
1242}
1243
1244#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1249#[serde(rename_all = "lowercase")]
1250pub enum AutoState {
1251 On,
1252 Off,
1253}
1254
1255impl AutoState {
1256 #[must_use]
1257 pub const fn as_wire(self) -> &'static str {
1258 match self {
1259 Self::On => "on",
1260 Self::Off => "off",
1261 }
1262 }
1263}
1264
1265#[cfg(test)]
1266mod wire_compat_tests {
1267 use super::*;
1274
1275 #[test]
1276 fn positions_bus_file_shape_parses_with_aliases() {
1277 let raw = r#"{
1279 "updated_at": "2026-04-22T12:19:29.563466+00:00",
1280 "positions": [
1281 {
1282 "coin": "TRX",
1283 "direction": "LONG",
1284 "entry_price": 0.33444,
1285 "size_coins": 149.0,
1286 "size_usd": 49.83156,
1287 "stop_loss_pct": 0.025,
1288 "id": "TRX_LONG_1776857828",
1289 "strategy": "production",
1290 "lens_id": "lens_flow"
1291 },
1292 {
1293 "coin": "BTC",
1294 "direction": "SHORT",
1295 "entry_price": 63450.0,
1296 "size_coins": 0.0012,
1297 "size_usd": 76.14
1298 }
1299 ]
1300 }"#;
1301 let parsed: Positions = serde_json::from_str(raw).expect("engine shape must parse");
1302 assert_eq!(parsed.items.len(), 2);
1303 assert_eq!(parsed.items[0].symbol, "TRX");
1304 assert_eq!(parsed.items[0].side, "LONG");
1305 assert!((parsed.items[0].size - 149.0).abs() < f64::EPSILON);
1306 assert!((parsed.items[0].entry - 0.33444).abs() < f64::EPSILON);
1307 assert_eq!(parsed.items[0].lens_id.as_deref(), Some("lens_flow"));
1308 assert!(parsed.items[0].extra.contains_key("size_usd"));
1312 }
1313
1314 #[test]
1315 fn risk_bus_file_shape_parses() {
1316 let raw = r#"{
1317 "account_value": 581.49647,
1318 "updated_at": "2026-04-22T12:19:29.564814+00:00",
1319 "daily_pnl_usd": 0.0,
1320 "daily_loss_usd": 0.0,
1321 "global_halt": false,
1322 "halted": false,
1323 "drawdown_pct": 9.24,
1324 "peak_equity": 613.450419,
1325 "peak_equity_30d": 640.7,
1326 "open_count": 2
1327 }"#;
1328 let parsed: Risk = serde_json::from_str(raw).expect("risk shape must parse");
1329 assert_eq!(parsed.account_value, Some(581.49647));
1330 assert_eq!(parsed.drawdown_pct, Some(9.24));
1331 assert_eq!(parsed.open_count, Some(2));
1332 assert!(!parsed.halted);
1333 }
1334}