use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
#[cfg_attr(feature = "python", pyo3::prelude::pyclass)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
pub struct Ticker {
pub date: String,
#[serde(rename = "type")]
pub data_type: Option<String>,
pub exchange: Option<String>,
pub market: Option<String>,
pub symbol: String,
pub name: Option<String>,
#[serde(rename = "nameEn")]
pub name_en: Option<String>,
pub industry: Option<String>,
#[serde(rename = "securityType")]
pub security_type: Option<String>,
#[serde(rename = "referencePrice")]
pub reference_price: Option<f64>,
#[serde(rename = "limitUpPrice")]
pub limit_up_price: Option<f64>,
#[serde(rename = "limitDownPrice")]
pub limit_down_price: Option<f64>,
#[serde(rename = "previousClose")]
pub previous_close: Option<f64>,
#[serde(rename = "canDayTrade", default)]
pub can_day_trade: bool,
#[serde(rename = "canBuyDayTrade", default)]
pub can_buy_day_trade: bool,
#[serde(rename = "canBelowFlatMarginShortSell", default)]
pub can_below_flat_margin_short_sell: bool,
#[serde(rename = "canBelowFlatSBLShortSell", default)]
pub can_below_flat_sbl_short_sell: bool,
#[serde(rename = "isAttention", default)]
pub is_attention: bool,
#[serde(rename = "isDisposition", default)]
pub is_disposition: bool,
#[serde(rename = "isUnusuallyRecommended", default)]
pub is_unusually_recommended: bool,
#[serde(rename = "isSpecificAbnormally", default)]
pub is_specific_abnormally: bool,
#[serde(rename = "isNewlyCompiled", default)]
pub is_newly_compiled: bool,
#[serde(rename = "matchingInterval")]
pub matching_interval: Option<i32>,
#[serde(rename = "securityStatus")]
pub security_status: Option<String>,
#[serde(rename = "boardLot")]
pub board_lot: Option<i32>,
#[serde(rename = "tradingCurrency")]
pub trading_currency: Option<String>,
#[serde(rename = "exercisePrice")]
pub exercise_price: Option<f64>,
#[serde(rename = "exercisedVolume")]
pub exercised_volume: Option<i64>,
#[serde(rename = "cancelledVolume")]
pub cancelled_volume: Option<i64>,
#[serde(rename = "remainingVolume")]
pub remaining_volume: Option<i64>,
#[serde(rename = "exerciseRatio")]
pub exercise_ratio: Option<f64>,
#[serde(rename = "capPrice")]
pub cap_price: Option<f64>,
#[serde(rename = "floorPrice")]
pub floor_price: Option<f64>,
#[serde(rename = "maturityDate")]
pub maturity_date: Option<String>,
#[serde(rename = "openTime")]
pub open_time: Option<String>,
#[serde(rename = "closeTime")]
pub close_time: Option<String>,
}
impl Ticker {
pub fn price_range(&self) -> Option<(f64, f64)> {
match (self.limit_down_price, self.limit_up_price) {
(Some(down), Some(up)) => Some((down, up)),
_ => None,
}
}
pub fn is_tradeable(&self) -> bool {
!self.is_attention && !self.is_disposition
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ticker_deserialization() {
let json = r#"{
"date": "2024-01-15",
"type": "EQUITY",
"exchange": "TWSE",
"market": "TSE",
"symbol": "2330",
"name": "台積電",
"nameEn": "TSMC",
"industry": "半導體業",
"securityType": "STOCK",
"referencePrice": 580.0,
"limitUpPrice": 638.0,
"limitDownPrice": 522.0,
"canDayTrade": true,
"canBuyDayTrade": true,
"isAttention": false,
"isDisposition": false,
"matchingInterval": 5,
"boardLot": 1000
}"#;
let ticker: Ticker = serde_json::from_str(json).unwrap();
assert_eq!(ticker.symbol, "2330");
assert_eq!(ticker.name.as_deref(), Some("台積電"));
assert_eq!(ticker.name_en.as_deref(), Some("TSMC"));
assert_eq!(ticker.limit_up_price, Some(638.0));
assert!(ticker.can_day_trade);
assert!(ticker.is_tradeable());
}
#[test]
fn test_ticker_price_range() {
let ticker = Ticker {
limit_down_price: Some(522.0),
limit_up_price: Some(638.0),
..Default::default()
};
assert_eq!(ticker.price_range(), Some((522.0, 638.0)));
}
#[test]
fn test_ticker_attention_stock() {
let ticker = Ticker {
is_attention: true,
..Default::default()
};
assert!(!ticker.is_tradeable());
}
}