nt_agentdb_client/
schema.rs

1// AgentDB Schema Definitions
2//
3// Matches the schemas defined in 05_Memory_and_AgentDB.md
4
5use chrono::{DateTime, Utc};
6use rust_decimal::Decimal;
7use serde::{Serialize, Deserialize};
8use uuid::Uuid;
9
10/// Market observation with deterministic embedding
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Observation {
13    pub id: Uuid,
14    pub timestamp_us: i64,
15    pub symbol: String,
16    pub price: Decimal,
17    pub volume: Decimal,
18    pub spread: Decimal,
19    pub book_depth: BookDepth,
20
21    #[serde(skip_serializing_if = "Vec::is_empty", default)]
22    pub embedding: Vec<f32>,
23
24    #[serde(default)]
25    pub metadata: serde_json::Value,
26
27    pub provenance: Provenance,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize, Default)]
31pub struct BookDepth {
32    pub bids: Vec<(Decimal, Decimal)>,
33    pub asks: Vec<(Decimal, Decimal)>,
34}
35
36impl Observation {
37    pub fn new(symbol: String, price: Decimal, volume: Decimal) -> Self {
38        let id = Uuid::new_v4();
39        let timestamp_us = Utc::now().timestamp_micros();
40
41        Self {
42            id,
43            timestamp_us,
44            symbol,
45            price,
46            volume,
47            spread: Decimal::ZERO,
48            book_depth: BookDepth::default(),
49            embedding: Vec::new(),
50            metadata: serde_json::json!({}),
51            provenance: Provenance::new("market_data_collector"),
52        }
53    }
54
55    pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self {
56        self.embedding = embedding;
57        self
58    }
59}
60
61/// Trading signal with causal links
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct Signal {
64    pub id: Uuid,
65    pub strategy_id: String,
66    pub timestamp_us: i64,
67    pub symbol: String,
68    pub direction: Direction,
69    pub confidence: f64,
70    pub features: Vec<f64>,
71    pub reasoning: String,
72    pub causal_links: Vec<Uuid>,
73
74    #[serde(skip_serializing_if = "Vec::is_empty", default)]
75    pub embedding: Vec<f32>,
76
77    pub provenance: Provenance,
78}
79
80#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
81pub enum Direction {
82    Long,
83    Short,
84    Neutral,
85    Close,
86}
87
88impl Signal {
89    pub fn new(strategy_id: String, symbol: String, direction: Direction, confidence: f64) -> Self {
90        Self {
91            id: Uuid::new_v4(),
92            strategy_id,
93            timestamp_us: Utc::now().timestamp_micros(),
94            symbol,
95            direction,
96            confidence,
97            features: Vec::new(),
98            reasoning: String::new(),
99            causal_links: Vec::new(),
100            embedding: Vec::new(),
101            provenance: Provenance::new("strategy_engine"),
102        }
103    }
104
105    pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self {
106        self.embedding = embedding;
107        self
108    }
109}
110
111/// Order lifecycle tracking
112#[derive(Debug, Clone, Serialize, Deserialize)]
113pub struct Order {
114    pub id: Uuid,
115    pub signal_id: Uuid,
116    pub symbol: String,
117    pub side: OrderSide,
118    pub quantity: u32,
119    pub order_type: OrderType,
120    pub limit_price: Option<Decimal>,
121    pub stop_price: Option<Decimal>,
122    pub status: OrderStatus,
123    pub timestamps: OrderTimestamps,
124    pub fills: Vec<Fill>,
125
126    #[serde(skip_serializing_if = "Vec::is_empty", default)]
127    pub embedding: Vec<f32>,
128
129    pub provenance: Provenance,
130}
131
132#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
133pub enum OrderSide {
134    Buy,
135    Sell,
136}
137
138#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
139pub enum OrderType {
140    Market,
141    Limit,
142    StopLoss,
143    StopLimit,
144}
145
146#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
147pub enum OrderStatus {
148    Pending,
149    Submitted,
150    PartiallyFilled,
151    Filled,
152    Cancelled,
153    Rejected,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, Default)]
157pub struct OrderTimestamps {
158    pub created_us: i64,
159    pub submitted_us: Option<i64>,
160    pub first_fill_us: Option<i64>,
161    pub completed_us: Option<i64>,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165pub struct Fill {
166    pub timestamp_us: i64,
167    pub quantity: u32,
168    pub price: Decimal,
169    pub fee: Decimal,
170}
171
172impl Order {
173    pub fn new(signal_id: Uuid, symbol: String, side: OrderSide, quantity: u32) -> Self {
174        Self {
175            id: Uuid::new_v4(),
176            signal_id,
177            symbol,
178            side,
179            quantity,
180            order_type: OrderType::Market,
181            limit_price: None,
182            stop_price: None,
183            status: OrderStatus::Pending,
184            timestamps: OrderTimestamps {
185                created_us: Utc::now().timestamp_micros(),
186                ..Default::default()
187            },
188            fills: Vec::new(),
189            embedding: Vec::new(),
190            provenance: Provenance::new("order_manager"),
191        }
192    }
193
194    pub fn with_embedding(mut self, embedding: Vec<f32>) -> Self {
195        self.embedding = embedding;
196        self
197    }
198}
199
200/// ReasoningBank reflexion trace
201#[derive(Debug, Clone, Serialize, Deserialize)]
202pub struct ReflexionTrace {
203    pub id: Uuid,
204    pub decision_id: Uuid,
205    pub decision_type: DecisionType,
206    pub trajectory: Vec<StateAction>,
207    pub verdict: Verdict,
208    pub learned_patterns: Vec<Pattern>,
209    pub counterfactuals: Vec<Counterfactual>,
210
211    #[serde(skip_serializing_if = "Vec::is_empty", default)]
212    pub embedding: Vec<f32>,
213
214    pub provenance: Provenance,
215}
216
217#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
218pub enum DecisionType {
219    Signal,
220    Order,
221    StrategySwitch,
222    RiskLimit,
223}
224
225#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct StateAction {
227    pub timestamp_us: i64,
228    pub state: PortfolioState,
229    pub action: TradingAction,
230    pub reward: f64,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
234pub struct PortfolioState {
235    pub cash: Decimal,
236    pub unrealized_pnl: Decimal,
237    pub market_features: Vec<f64>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
241pub struct TradingAction {
242    pub action_type: ActionType,
243    pub symbol: String,
244    pub quantity: u32,
245}
246
247#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
248pub enum ActionType {
249    Buy,
250    Sell,
251    Hold,
252    ClosePosition,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct Verdict {
257    pub score: f64,
258    pub roi: f64,
259    pub sharpe: f64,
260    pub max_drawdown: f64,
261    pub explanation: String,
262    pub successes: Vec<String>,
263    pub failures: Vec<String>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
267pub struct Pattern {
268    pub pattern_type: PatternType,
269    pub description: String,
270    pub confidence: f64,
271    pub occurrences: usize,
272    pub embedding: Vec<f32>,
273}
274
275#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
276pub enum PatternType {
277    EntryTiming,
278    ExitTiming,
279    RiskManagement,
280    MarketRegime,
281    Correlation,
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct Counterfactual {
286    pub description: String,
287    pub alternative_action: TradingAction,
288    pub estimated_outcome: f64,
289    pub probability: f64,
290}
291
292/// Data provenance tracking
293#[derive(Debug, Clone, Serialize, Deserialize)]
294pub struct Provenance {
295    pub creator: String,
296    pub created_us: i64,
297    pub parent_id: Option<Uuid>,
298
299    #[serde(skip_serializing_if = "Vec::is_empty", default)]
300    pub signature: Vec<u8>,
301
302    #[serde(skip_serializing_if = "Vec::is_empty", default)]
303    pub public_key: Vec<u8>,
304
305    #[serde(skip_serializing_if = "Vec::is_empty", default)]
306    pub hash: Vec<u8>,
307}
308
309impl Provenance {
310    pub fn new(creator: &str) -> Self {
311        Self {
312            creator: creator.to_string(),
313            created_us: Utc::now().timestamp_micros(),
314            parent_id: None,
315            signature: Vec::new(),
316            public_key: Vec::new(),
317            hash: Vec::new(),
318        }
319    }
320
321    pub fn with_parent(mut self, parent_id: Uuid) -> Self {
322        self.parent_id = Some(parent_id);
323        self
324    }
325}
326
327#[cfg(test)]
328mod tests {
329    use super::*;
330    use rust_decimal_macros::dec;
331
332    #[test]
333    fn test_observation_creation() {
334        let obs = Observation::new("AAPL".to_string(), dec!(150.00), dec!(1000));
335
336        assert_eq!(obs.symbol, "AAPL");
337        assert_eq!(obs.price, dec!(150.00));
338        assert_eq!(obs.volume, dec!(1000));
339    }
340
341    #[test]
342    fn test_signal_creation() {
343        let signal = Signal::new(
344            "momentum_v1".to_string(),
345            "AAPL".to_string(),
346            Direction::Long,
347            0.85,
348        );
349
350        assert_eq!(signal.strategy_id, "momentum_v1");
351        assert_eq!(signal.direction, Direction::Long);
352        assert_eq!(signal.confidence, 0.85);
353    }
354
355    #[test]
356    fn test_order_creation() {
357        let signal_id = Uuid::new_v4();
358        let order = Order::new(signal_id, "AAPL".to_string(), OrderSide::Buy, 100);
359
360        assert_eq!(order.signal_id, signal_id);
361        assert_eq!(order.quantity, 100);
362        assert_eq!(order.status, OrderStatus::Pending);
363    }
364
365    #[test]
366    fn test_provenance_with_parent() {
367        let parent_id = Uuid::new_v4();
368        let provenance = Provenance::new("test_creator").with_parent(parent_id);
369
370        assert_eq!(provenance.creator, "test_creator");
371        assert_eq!(provenance.parent_id, Some(parent_id));
372    }
373}