hyper_agent_core/
trade_markers.rs1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
9#[serde(rename_all = "camelCase")]
10pub struct TradeRecord {
11 pub time: i64, pub side: String, pub price: f64,
14 pub size: f64,
15 pub symbol: String,
16 pub is_paper: bool,
17 pub agent_id: String,
18}
19
20pub fn parse_timestamp_to_unix(ts: &str) -> Option<i64> {
27 if let Ok(dt) = chrono::DateTime::parse_from_rfc3339(ts) {
29 return Some(dt.timestamp());
30 }
31 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S") {
33 return Some(dt.and_utc().timestamp());
34 }
35 if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(ts, "%Y-%m-%dT%H:%M:%S%.f") {
36 return Some(dt.and_utc().timestamp());
37 }
38 None
39}
40
41pub fn action_to_side(action: &str) -> Option<String> {
44 let lower = action.to_lowercase();
45 if lower == "buy" || lower == "long" {
46 Some("buy".to_string())
47 } else if lower == "sell" || lower == "short" {
48 Some("sell".to_string())
49 } else {
50 None
51 }
52}
53
54#[cfg(test)]
59mod tests {
60 use super::*;
61
62 #[test]
63 fn test_parse_timestamp_rfc3339() {
64 let ts = "2026-03-09T14:32:00Z";
65 let unix = parse_timestamp_to_unix(ts).unwrap();
66 assert!(unix > 0);
67 }
68
69 #[test]
70 fn test_parse_timestamp_with_offset() {
71 let ts = "2026-03-09T14:32:00+08:00";
72 let unix = parse_timestamp_to_unix(ts).unwrap();
73 assert!(unix > 0);
74 }
75
76 #[test]
77 fn test_parse_timestamp_naive() {
78 let ts = "2026-03-09T14:32:00";
79 let unix = parse_timestamp_to_unix(ts).unwrap();
80 assert!(unix > 0);
81 }
82
83 #[test]
84 fn test_parse_timestamp_invalid() {
85 assert!(parse_timestamp_to_unix("not-a-date").is_none());
86 }
87
88 #[test]
89 fn test_action_to_side_buy() {
90 assert_eq!(action_to_side("buy"), Some("buy".to_string()));
91 assert_eq!(action_to_side("Buy"), Some("buy".to_string()));
92 assert_eq!(action_to_side("long"), Some("buy".to_string()));
93 }
94
95 #[test]
96 fn test_action_to_side_sell() {
97 assert_eq!(action_to_side("sell"), Some("sell".to_string()));
98 assert_eq!(action_to_side("Sell"), Some("sell".to_string()));
99 assert_eq!(action_to_side("short"), Some("sell".to_string()));
100 }
101
102 #[test]
103 fn test_action_to_side_none() {
104 assert_eq!(action_to_side("hold"), None);
105 assert_eq!(action_to_side(""), None);
106 }
107
108 #[test]
109 fn test_trade_record_serialization() {
110 let record = TradeRecord {
111 time: 1741520000,
112 side: "buy".to_string(),
113 price: 95000.0,
114 size: 0.05,
115 symbol: "BTC-PERP".to_string(),
116 is_paper: false,
117 agent_id: "agent-1".to_string(),
118 };
119 let json = serde_json::to_value(&record).unwrap();
120 assert_eq!(json["time"], 1741520000);
121 assert_eq!(json["side"], "buy");
122 assert_eq!(json["price"], 95000.0);
123 assert_eq!(json["size"], 0.05);
124 assert_eq!(json["symbol"], "BTC-PERP");
125 assert_eq!(json["isPaper"], false);
126 assert_eq!(json["agentId"], "agent-1");
127 }
128
129 #[test]
130 fn test_trade_record_deserialization() {
131 let json = serde_json::json!({
132 "time": 1741520000,
133 "side": "sell",
134 "price": 96000.0,
135 "size": 0.1,
136 "symbol": "ETH-PERP",
137 "isPaper": true,
138 "agentId": "agent-2"
139 });
140 let record: TradeRecord = serde_json::from_value(json).unwrap();
141 assert_eq!(record.time, 1741520000);
142 assert_eq!(record.side, "sell");
143 assert_eq!(record.price, 96000.0);
144 assert_eq!(record.size, 0.1);
145 assert_eq!(record.symbol, "ETH-PERP");
146 assert!(record.is_paper);
147 assert_eq!(record.agent_id, "agent-2");
148 }
149}