feat(ml): add trading_symbol field to UniverseAsset for micro contract mapping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-02-23 14:41:22 +01:00
parent 3a60f303e3
commit 8ce2d53d21

View File

@@ -25,9 +25,12 @@ pub enum AssetClass {
/// A single asset in the trading universe.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UniverseAsset {
/// Ticker symbol (e.g. "AAPL").
/// Ticker symbol (e.g. "ES.FUT") -- used for data/training.
pub symbol: String,
/// MIC exchange code (e.g. "XNAS").
/// Execution symbol (e.g. "MES.FUT") -- used for order routing.
/// When `None`, orders are routed to `symbol` directly.
pub trading_symbol: Option<String>,
/// MIC exchange code (e.g. "GLBX.MDP3").
pub exchange: String,
/// Asset classification.
pub asset_class: AssetClass,
@@ -39,6 +42,15 @@ pub struct UniverseAsset {
pub enabled: bool,
}
impl UniverseAsset {
/// The symbol to use for order execution.
/// Returns `trading_symbol` if set, otherwise `symbol`.
#[must_use]
pub fn execution_symbol(&self) -> &str {
self.trading_symbol.as_deref().unwrap_or(&self.symbol)
}
}
/// The full set of assets available for trading.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AssetUniverse {
@@ -84,6 +96,7 @@ impl AssetUniverse {
assets: vec![
UniverseAsset {
symbol: "SPY".to_string(),
trading_symbol: None,
exchange: "XNYS".to_string(),
asset_class: AssetClass::ETF,
sector: None,
@@ -92,6 +105,7 @@ impl AssetUniverse {
},
UniverseAsset {
symbol: "QQQ".to_string(),
trading_symbol: None,
exchange: "XNAS".to_string(),
asset_class: AssetClass::ETF,
sector: None,
@@ -100,6 +114,7 @@ impl AssetUniverse {
},
UniverseAsset {
symbol: "AAPL".to_string(),
trading_symbol: None,
exchange: "XNAS".to_string(),
asset_class: AssetClass::Equity,
sector: Some("Technology".to_string()),
@@ -108,6 +123,7 @@ impl AssetUniverse {
},
UniverseAsset {
symbol: "MSFT".to_string(),
trading_symbol: None,
exchange: "XNAS".to_string(),
asset_class: AssetClass::Equity,
sector: Some("Technology".to_string()),
@@ -116,6 +132,7 @@ impl AssetUniverse {
},
UniverseAsset {
symbol: "NVDA".to_string(),
trading_symbol: None,
exchange: "XNAS".to_string(),
asset_class: AssetClass::Equity,
sector: Some("Technology".to_string()),
@@ -124,6 +141,7 @@ impl AssetUniverse {
},
UniverseAsset {
symbol: "AMZN".to_string(),
trading_symbol: None,
exchange: "XNAS".to_string(),
asset_class: AssetClass::Equity,
sector: Some("Consumer".to_string()),
@@ -132,6 +150,7 @@ impl AssetUniverse {
},
UniverseAsset {
symbol: "IWM".to_string(),
trading_symbol: None,
exchange: "XNYS".to_string(),
asset_class: AssetClass::ETF,
sector: None,
@@ -209,4 +228,32 @@ mod tests {
serde_json::from_str(&json).unwrap_or_else(|_| AssetUniverse::new());
assert_eq!(restored.assets.len(), 7);
}
#[test]
fn test_trading_symbol_mapping() {
let asset = UniverseAsset {
symbol: "ES.FUT".to_string(),
trading_symbol: Some("MES.FUT".to_string()),
exchange: "GLBX.MDP3".to_string(),
asset_class: AssetClass::Future,
sector: None,
min_daily_volume: 1_000_000.0,
enabled: true,
};
assert_eq!(asset.execution_symbol(), "MES.FUT");
}
#[test]
fn test_trading_symbol_none_falls_back() {
let asset = UniverseAsset {
symbol: "ZN.FUT".to_string(),
trading_symbol: None,
exchange: "GLBX.MDP3".to_string(),
asset_class: AssetClass::Future,
sector: None,
min_daily_volume: 500_000.0,
enabled: true,
};
assert_eq!(asset.execution_symbol(), "ZN.FUT");
}
}