ccxt_core/symbol/
formatter.rs

1//! Symbol formatter implementation
2//!
3//! This module provides formatting functionality for converting `ParsedSymbol`
4//! structures back into unified symbol strings.
5//!
6//! # Symbol Format
7//!
8//! The unified symbol format follows the CCXT standard:
9//! - **Spot**: `BASE/QUOTE` (e.g., "BTC/USDT")
10//! - **Perpetual Swap**: `BASE/QUOTE:SETTLE` (e.g., "BTC/USDT:USDT")
11//! - **Futures**: `BASE/QUOTE:SETTLE-YYMMDD` (e.g., "BTC/USDT:USDT-241231")
12//!
13//! # Example
14//!
15//! ```rust
16//! use ccxt_core::symbol::{ParsedSymbol, SymbolFormatter};
17//! use ccxt_core::types::symbol::ExpiryDate;
18//!
19//! // Format a spot symbol
20//! let spot = ParsedSymbol::spot("BTC".to_string(), "USDT".to_string());
21//! assert_eq!(SymbolFormatter::format(&spot), "BTC/USDT");
22//!
23//! // Format a swap symbol
24//! let swap = ParsedSymbol::linear_swap("BTC".to_string(), "USDT".to_string());
25//! assert_eq!(SymbolFormatter::format(&swap), "BTC/USDT:USDT");
26//!
27//! // Format a futures symbol
28//! let expiry = ExpiryDate::new(24, 12, 31).unwrap();
29//! let futures = ParsedSymbol::futures("BTC".to_string(), "USDT".to_string(), "USDT".to_string(), expiry);
30//! assert_eq!(SymbolFormatter::format(&futures), "BTC/USDT:USDT-241231");
31//! ```
32
33use crate::types::symbol::ParsedSymbol;
34
35/// Symbol formatter for converting ParsedSymbol to unified symbol strings
36///
37/// Provides methods to format `ParsedSymbol` structures into their
38/// canonical unified symbol string representation.
39pub struct SymbolFormatter;
40
41impl SymbolFormatter {
42    /// Format a `ParsedSymbol` into a unified symbol string
43    ///
44    /// # Arguments
45    ///
46    /// * `parsed` - The parsed symbol to format
47    ///
48    /// # Returns
49    ///
50    /// Returns the unified symbol string in the appropriate format:
51    /// - Spot: `BASE/QUOTE`
52    /// - Swap: `BASE/QUOTE:SETTLE`
53    /// - Futures: `BASE/QUOTE:SETTLE-YYMMDD`
54    ///
55    /// # Examples
56    ///
57    /// ```rust
58    /// use ccxt_core::symbol::{ParsedSymbol, SymbolFormatter};
59    /// use ccxt_core::types::symbol::ExpiryDate;
60    ///
61    /// // Spot symbol
62    /// let spot = ParsedSymbol::spot("BTC".to_string(), "USDT".to_string());
63    /// assert_eq!(SymbolFormatter::format(&spot), "BTC/USDT");
64    ///
65    /// // Linear swap symbol
66    /// let swap = ParsedSymbol::linear_swap("ETH".to_string(), "USDT".to_string());
67    /// assert_eq!(SymbolFormatter::format(&swap), "ETH/USDT:USDT");
68    ///
69    /// // Inverse swap symbol
70    /// let inverse = ParsedSymbol::inverse_swap("BTC".to_string(), "USD".to_string());
71    /// assert_eq!(SymbolFormatter::format(&inverse), "BTC/USD:BTC");
72    ///
73    /// // Futures symbol
74    /// let expiry = ExpiryDate::new(24, 12, 31).unwrap();
75    /// let futures = ParsedSymbol::futures("BTC".to_string(), "USDT".to_string(), "USDT".to_string(), expiry);
76    /// assert_eq!(SymbolFormatter::format(&futures), "BTC/USDT:USDT-241231");
77    /// ```
78    pub fn format(parsed: &ParsedSymbol) -> String {
79        // Use the Display implementation which already handles all cases
80        parsed.to_string()
81    }
82
83    /// Format a spot symbol from base and quote currencies
84    ///
85    /// # Arguments
86    ///
87    /// * `base` - Base currency code
88    /// * `quote` - Quote currency code
89    ///
90    /// # Returns
91    ///
92    /// Returns the formatted spot symbol string `BASE/QUOTE`
93    pub fn format_spot(base: &str, quote: &str) -> String {
94        format!("{}/{}", base.to_uppercase(), quote.to_uppercase())
95    }
96
97    /// Format a swap symbol from base, quote, and settle currencies
98    ///
99    /// # Arguments
100    ///
101    /// * `base` - Base currency code
102    /// * `quote` - Quote currency code
103    /// * `settle` - Settlement currency code
104    ///
105    /// # Returns
106    ///
107    /// Returns the formatted swap symbol string `BASE/QUOTE:SETTLE`
108    pub fn format_swap(base: &str, quote: &str, settle: &str) -> String {
109        format!(
110            "{}/{}:{}",
111            base.to_uppercase(),
112            quote.to_uppercase(),
113            settle.to_uppercase()
114        )
115    }
116
117    /// Format a futures symbol from base, quote, settle currencies and expiry date
118    ///
119    /// # Arguments
120    ///
121    /// * `base` - Base currency code
122    /// * `quote` - Quote currency code
123    /// * `settle` - Settlement currency code
124    /// * `year` - 2-digit year (0-99)
125    /// * `month` - Month (1-12)
126    /// * `day` - Day (1-31)
127    ///
128    /// # Returns
129    ///
130    /// Returns the formatted futures symbol string `BASE/QUOTE:SETTLE-YYMMDD`
131    pub fn format_futures(
132        base: &str,
133        quote: &str,
134        settle: &str,
135        year: u8,
136        month: u8,
137        day: u8,
138    ) -> String {
139        format!(
140            "{}/{}:{}-{:02}{:02}{:02}",
141            base.to_uppercase(),
142            quote.to_uppercase(),
143            settle.to_uppercase(),
144            year,
145            month,
146            day
147        )
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::types::symbol::ExpiryDate;
155
156    #[test]
157    fn test_format_spot() {
158        let symbol = ParsedSymbol::spot("BTC".to_string(), "USDT".to_string());
159        assert_eq!(SymbolFormatter::format(&symbol), "BTC/USDT");
160    }
161
162    #[test]
163    fn test_format_spot_lowercase_input() {
164        let symbol = ParsedSymbol::spot("btc".to_string(), "usdt".to_string());
165        assert_eq!(SymbolFormatter::format(&symbol), "BTC/USDT");
166    }
167
168    #[test]
169    fn test_format_linear_swap() {
170        let symbol = ParsedSymbol::linear_swap("BTC".to_string(), "USDT".to_string());
171        assert_eq!(SymbolFormatter::format(&symbol), "BTC/USDT:USDT");
172    }
173
174    #[test]
175    fn test_format_inverse_swap() {
176        let symbol = ParsedSymbol::inverse_swap("BTC".to_string(), "USD".to_string());
177        assert_eq!(SymbolFormatter::format(&symbol), "BTC/USD:BTC");
178    }
179
180    #[test]
181    fn test_format_futures() {
182        let expiry = ExpiryDate::new(24, 12, 31).unwrap();
183        let symbol = ParsedSymbol::futures(
184            "BTC".to_string(),
185            "USDT".to_string(),
186            "USDT".to_string(),
187            expiry,
188        );
189        assert_eq!(SymbolFormatter::format(&symbol), "BTC/USDT:USDT-241231");
190    }
191
192    #[test]
193    fn test_format_futures_inverse() {
194        let expiry = ExpiryDate::new(25, 3, 15).unwrap();
195        let symbol = ParsedSymbol::futures(
196            "BTC".to_string(),
197            "USD".to_string(),
198            "BTC".to_string(),
199            expiry,
200        );
201        assert_eq!(SymbolFormatter::format(&symbol), "BTC/USD:BTC-250315");
202    }
203
204    #[test]
205    fn test_format_spot_helper() {
206        assert_eq!(SymbolFormatter::format_spot("BTC", "USDT"), "BTC/USDT");
207        assert_eq!(SymbolFormatter::format_spot("btc", "usdt"), "BTC/USDT");
208    }
209
210    #[test]
211    fn test_format_swap_helper() {
212        assert_eq!(
213            SymbolFormatter::format_swap("BTC", "USDT", "USDT"),
214            "BTC/USDT:USDT"
215        );
216        assert_eq!(
217            SymbolFormatter::format_swap("btc", "usd", "btc"),
218            "BTC/USD:BTC"
219        );
220    }
221
222    #[test]
223    fn test_format_futures_helper() {
224        assert_eq!(
225            SymbolFormatter::format_futures("BTC", "USDT", "USDT", 24, 12, 31),
226            "BTC/USDT:USDT-241231"
227        );
228        assert_eq!(
229            SymbolFormatter::format_futures("btc", "usd", "btc", 25, 1, 5),
230            "BTC/USD:BTC-250105"
231        );
232    }
233
234    #[test]
235    fn test_format_futures_date_padding() {
236        // Ensure single-digit months and days are zero-padded
237        assert_eq!(
238            SymbolFormatter::format_futures("ETH", "USDT", "USDT", 25, 1, 5),
239            "ETH/USDT:USDT-250105"
240        );
241    }
242}