use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Candle {
pub time: i64,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
}
impl Candle {
pub fn from_raw(arr: &[serde_json::Value]) -> Option<Self> {
let num = |i: usize| -> Option<f64> {
let v = arr.get(i)?;
if let Some(s) = v.as_str() {
s.parse().ok()
} else {
v.as_f64()
}
};
Some(Self {
time: arr.first()?.as_i64()?,
open: num(1)?,
high: num(2)?,
low: num(3)?,
close: num(4)?,
volume: num(5)?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Side {
Buy,
Sell,
}
impl Side {
pub const fn as_str(self) -> &'static str {
match self {
Self::Buy => "buy",
Self::Sell => "sell",
}
}
#[must_use]
pub const fn flip(self) -> Self {
match self {
Self::Buy => Self::Sell,
Self::Sell => Self::Buy,
}
}
}
impl std::fmt::Display for Side {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrderType {
Market,
Limit,
}
impl OrderType {
pub const fn as_str(self) -> &'static str {
match self {
Self::Market => "market",
Self::Limit => "limit",
}
}
}
impl std::fmt::Display for OrderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
pub fn contract_value(symbol: &str) -> f64 {
match symbol {
"XBTUSDM" | "ETHUSDM" => 1.0,
"XBTUSDTM" => 0.001, "ETHUSDTM" | "BNBUSDTM" => 0.01,
"SOLUSDTM" | "AVAXUSDTM" => 0.1,
"XRPUSDTM" | "ADAUSDTM" => 10.0,
"DOGEUSDTM" => 100.0, "LINKUSDTM" => 1.0,
_ => 1.0, }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn candle_parse_string_fields() {
let arr = serde_json::json!([
1_713_000_000_000_i64,
"86000.0",
"87000.0",
"85000.0",
"86500.0",
"1234.5"
]);
let row = arr.as_array().unwrap();
let c = Candle::from_raw(row).expect("should parse");
assert_eq!(c.time, 1_713_000_000_000);
assert!((c.open - 86000.0).abs() < 1e-9);
assert!((c.close - 86500.0).abs() < 1e-9);
}
#[test]
fn side_flip() {
assert_eq!(Side::Buy.flip(), Side::Sell);
assert_eq!(Side::Sell.flip(), Side::Buy);
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum TimeInForce {
#[default]
GTC,
GTT,
IOC,
FOK,
}
impl TimeInForce {
pub const fn as_str(self) -> &'static str {
match self {
Self::GTC => "GTC",
Self::GTT => "GTT",
Self::IOC => "IOC",
Self::FOK => "FOK",
}
}
}
impl std::fmt::Display for TimeInForce {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "UPPERCASE")]
pub enum STP {
DC,
CO,
CN,
CB,
}
impl STP {
pub const fn as_str(self) -> &'static str {
match self {
Self::DC => "DC",
Self::CO => "CO",
Self::CN => "CN",
Self::CB => "CB",
}
}
}
impl std::fmt::Display for STP {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}