use crate::TastyTrade;
use crate::api::base::TastyApiResponse;
use crate::types::instrument::InstrumentType;
use crate::{AsSymbol, Symbol, TastyResult};
use pretty_simple_display::{DebugPretty, DisplaySimple};
use serde::Deserialize;
use serde::Serialize;
use tracing::{debug, error};
impl TastyTrade {
pub async fn quote_streamer_tokens(&self) -> TastyResult<QuoteStreamerTokens> {
let url = format!("{}/api-quote-tokens", self.config.base_url);
debug!("Requesting quote streamer tokens from: {}", url);
let response = self.client.get(&url).send().await?;
let status = response.status();
debug!("Response status: {}", status);
if !status.is_success() {
error!("Failed to get quote streamer tokens: HTTP {}", status);
let text = response.text().await?;
error!("Response body: {}", text);
return Err(crate::TastyTradeError::Connection(format!(
"Failed to get quote streamer tokens: HTTP {}, Body: {}",
status, text
)));
}
let text = response.text().await?;
debug!("Response body: {}", text);
match serde_json::from_str::<TastyApiResponse<QuoteStreamerTokens>>(&text) {
Ok(TastyApiResponse::Success(s)) => Ok(s.data),
Ok(TastyApiResponse::Error { error }) => Err(error.into()),
Err(e) => {
error!("Failed to parse response: {}", e);
Err(crate::TastyTradeError::Json(e))
}
}
}
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct QuoteStreamerTokens {
pub token: String,
#[serde(rename = "dxlink-url")]
pub streamer_url: String,
pub level: String,
}
#[derive(
DebugPretty, DisplaySimple, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(transparent)]
pub struct DxFeedSymbol(pub String);
impl AsSymbol for DxFeedSymbol {
fn as_symbol(&self) -> Symbol {
Symbol(self.0.clone())
}
}
impl AsSymbol for &DxFeedSymbol {
fn as_symbol(&self) -> Symbol {
Symbol(self.0.clone())
}
}
impl TastyTrade {
pub async fn get_streamer_symbol(
&self,
instrument_type: &InstrumentType,
symbol: &Symbol,
) -> TastyResult<DxFeedSymbol> {
use InstrumentType::*;
let sym = match instrument_type {
Equity => self.get_equity_info(symbol).await?.streamer_symbol,
EquityOption => self.get_option_info(symbol).await?.streamer_symbol,
EquityOffering => self.get_equity_info(symbol).await?.streamer_symbol, Future => self.get_future(symbol).await?.streamer_symbol,
FutureOption => self.get_future_option(symbol).await?.streamer_symbol,
Cryptocurrency => self.get_cryptocurrency(symbol).await?.streamer_symbol,
};
Ok(sym)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::instrument::InstrumentType;
#[test]
fn test_quote_streamer_tokens_deserialization() {
let json = r#"{
"token": "abc123token",
"dxlink-url": "wss://streamer.example.com",
"level": "delayed"
}"#;
let tokens: QuoteStreamerTokens = serde_json::from_str(json).unwrap();
assert_eq!(tokens.token, "abc123token");
assert_eq!(tokens.streamer_url, "wss://streamer.example.com");
assert_eq!(tokens.level, "delayed");
}
#[test]
fn test_quote_streamer_tokens_debug() {
let tokens = QuoteStreamerTokens {
token: "test_token".to_string(),
streamer_url: "wss://test.com".to_string(),
level: "realtime".to_string(),
};
let debug_str = format!("{:?}", tokens);
assert!(debug_str.contains("test_token"));
assert!(debug_str.contains("wss://test.com"));
assert!(debug_str.contains("realtime"));
}
#[test]
fn test_dxfeed_symbol_creation() {
let symbol = DxFeedSymbol("AAPL".to_string());
assert_eq!(symbol.0, "AAPL");
}
#[test]
fn test_dxfeed_symbol_as_symbol_trait() {
let dxfeed_symbol = DxFeedSymbol("MSFT".to_string());
let symbol = dxfeed_symbol.as_symbol();
assert_eq!(symbol.0, "MSFT");
let symbol_ref = &dxfeed_symbol;
let symbol = symbol_ref.as_symbol();
assert_eq!(symbol.0, "MSFT");
}
#[test]
fn test_dxfeed_symbol_serialization() {
let symbol = DxFeedSymbol("TSLA".to_string());
let serialized = serde_json::to_string(&symbol).unwrap();
assert_eq!(serialized, "\"TSLA\"");
let deserialized: DxFeedSymbol = serde_json::from_str(&serialized).unwrap();
assert_eq!(deserialized.0, "TSLA");
}
#[test]
fn test_dxfeed_symbol_traits() {
let symbol1 = DxFeedSymbol("AAPL".to_string());
let symbol2 = DxFeedSymbol("AAPL".to_string());
let symbol3 = DxFeedSymbol("MSFT".to_string());
let cloned = symbol1.clone();
assert_eq!(cloned.0, "AAPL");
assert_eq!(symbol1, symbol2);
assert_ne!(symbol1, symbol3);
assert!(symbol1 < symbol3); assert!(symbol3 > symbol1);
let debug_str = format!("{:?}", symbol1);
assert!(debug_str.contains("AAPL"));
}
#[test]
fn test_dxfeed_symbol_ordering() {
let mut symbols = [
DxFeedSymbol("TSLA".to_string()),
DxFeedSymbol("AAPL".to_string()),
DxFeedSymbol("MSFT".to_string()),
];
symbols.sort();
assert_eq!(symbols[0].0, "AAPL");
assert_eq!(symbols[1].0, "MSFT");
assert_eq!(symbols[2].0, "TSLA");
}
#[test]
fn test_dxfeed_symbol_hash() {
use std::collections::HashMap;
let mut map = HashMap::new();
let symbol1 = DxFeedSymbol("AAPL".to_string());
let symbol2 = DxFeedSymbol("AAPL".to_string());
map.insert(symbol1, "Apple");
assert_eq!(map.get(&symbol2), Some(&"Apple"));
}
#[test]
fn test_instrument_type_matching() {
let instrument_types = [
InstrumentType::Equity,
InstrumentType::EquityOption,
InstrumentType::EquityOffering,
InstrumentType::Future,
InstrumentType::FutureOption,
InstrumentType::Cryptocurrency,
];
assert_eq!(instrument_types.len(), 6);
}
#[test]
fn test_dxfeed_symbol_transparent_serde() {
let symbol = DxFeedSymbol("TEST123".to_string());
let json = serde_json::to_string(&symbol).unwrap();
assert_eq!(json, "\"TEST123\"");
let deserialized: DxFeedSymbol = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized, symbol);
}
}