use std::collections::HashMap;
use chrono::{DateTime, FixedOffset};
#[derive(Debug, Clone)]
pub struct Position {
pub symbol: String,
pub size: f64,
pub entry_price: f64,
pub current_price: f64,
pub unrealized_pnl: f64,
pub realized_pnl: f64,
pub funding_pnl: f64,
pub timestamp: DateTime<FixedOffset>,
pub leverage: f64,
pub liquidation_price: Option<f64>,
pub margin: Option<f64>,
pub metadata: HashMap<String, String>,
}
impl Position {
pub fn new(
symbol: &str,
size: f64,
entry_price: f64,
current_price: f64,
timestamp: DateTime<FixedOffset>,
) -> Self {
let unrealized_pnl = if size != 0.0 {
size * (current_price - entry_price)
} else {
0.0
};
Self {
symbol: symbol.to_string(),
size,
entry_price,
current_price,
unrealized_pnl,
realized_pnl: 0.0,
funding_pnl: 0.0,
timestamp,
leverage: 1.0,
liquidation_price: None,
margin: None,
metadata: HashMap::new(),
}
}
pub fn update_price(&mut self, price: f64) {
self.current_price = price;
if self.size != 0.0 {
self.unrealized_pnl = self.size * (price - self.entry_price);
}
}
pub fn apply_funding_payment(&mut self, payment: f64) {
self.funding_pnl += payment;
}
pub fn total_pnl(&self) -> f64 {
self.realized_pnl + self.unrealized_pnl + self.funding_pnl
}
pub fn notional_value(&self) -> f64 {
self.size.abs() * self.current_price
}
pub fn is_long(&self) -> bool {
self.size > 0.0
}
pub fn is_short(&self) -> bool {
self.size < 0.0
}
pub fn is_flat(&self) -> bool {
self.size == 0.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderSide {
Buy,
Sell,
}
impl std::fmt::Display for OrderSide {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OrderSide::Buy => write!(f, "Buy"),
OrderSide::Sell => write!(f, "Sell"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderType {
Market,
Limit,
StopMarket,
StopLimit,
TakeProfitMarket,
TakeProfitLimit,
}
impl std::fmt::Display for OrderType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OrderType::Market => write!(f, "Market"),
OrderType::Limit => write!(f, "Limit"),
OrderType::StopMarket => write!(f, "StopMarket"),
OrderType::StopLimit => write!(f, "StopLimit"),
OrderType::TakeProfitMarket => write!(f, "TakeProfitMarket"),
OrderType::TakeProfitLimit => write!(f, "TakeProfitLimit"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TimeInForce {
GoodTillCancel,
ImmediateOrCancel,
FillOrKill,
GoodTillDate,
}
impl std::fmt::Display for TimeInForce {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TimeInForce::GoodTillCancel => write!(f, "GoodTillCancel"),
TimeInForce::ImmediateOrCancel => write!(f, "ImmediateOrCancel"),
TimeInForce::FillOrKill => write!(f, "FillOrKill"),
TimeInForce::GoodTillDate => write!(f, "GoodTillDate"),
}
}
}
#[derive(Debug, Clone)]
pub struct OrderRequest {
pub symbol: String,
pub side: OrderSide,
pub order_type: OrderType,
pub quantity: f64,
pub price: Option<f64>,
pub reduce_only: bool,
pub time_in_force: TimeInForce,
pub stop_price: Option<f64>,
pub client_order_id: Option<String>,
pub parameters: HashMap<String, String>,
}
impl OrderRequest {
pub fn market(symbol: &str, side: OrderSide, quantity: f64) -> Self {
Self {
symbol: symbol.to_string(),
side,
order_type: OrderType::Market,
quantity,
price: None,
reduce_only: false,
time_in_force: TimeInForce::GoodTillCancel,
stop_price: None,
client_order_id: None,
parameters: HashMap::new(),
}
}
pub fn limit(symbol: &str, side: OrderSide, quantity: f64, price: f64) -> Self {
Self {
symbol: symbol.to_string(),
side,
order_type: OrderType::Limit,
quantity,
price: Some(price),
reduce_only: false,
time_in_force: TimeInForce::GoodTillCancel,
stop_price: None,
client_order_id: None,
parameters: HashMap::new(),
}
}
pub fn reduce_only(mut self) -> Self {
self.reduce_only = true;
self
}
pub fn with_time_in_force(mut self, time_in_force: TimeInForce) -> Self {
self.time_in_force = time_in_force;
self
}
pub fn with_client_order_id(mut self, client_order_id: &str) -> Self {
self.client_order_id = Some(client_order_id.to_string());
self
}
pub fn with_parameter(mut self, key: &str, value: &str) -> Self {
self.parameters.insert(key.to_string(), value.to_string());
self
}
pub fn validate(&self) -> Result<(), String> {
if self.quantity <= 0.0 {
return Err("Order quantity must be positive".to_string());
}
if matches!(self.order_type, OrderType::Limit | OrderType::StopLimit | OrderType::TakeProfitLimit)
&& self.price.is_none() {
return Err(format!("Price is required for {} orders", self.order_type));
}
if matches!(self.order_type, OrderType::StopMarket | OrderType::StopLimit)
&& self.stop_price.is_none() {
return Err(format!("Stop price is required for {} orders", self.order_type));
}
if matches!(self.order_type, OrderType::TakeProfitMarket | OrderType::TakeProfitLimit)
&& self.stop_price.is_none() {
return Err(format!("Take profit price is required for {} orders", self.order_type));
}
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderStatus {
Created,
Submitted,
PartiallyFilled,
Filled,
Cancelled,
Rejected,
Expired,
}
impl std::fmt::Display for OrderStatus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OrderStatus::Created => write!(f, "Created"),
OrderStatus::Submitted => write!(f, "Submitted"),
OrderStatus::PartiallyFilled => write!(f, "PartiallyFilled"),
OrderStatus::Filled => write!(f, "Filled"),
OrderStatus::Cancelled => write!(f, "Cancelled"),
OrderStatus::Rejected => write!(f, "Rejected"),
OrderStatus::Expired => write!(f, "Expired"),
}
}
}
#[derive(Debug, Clone)]
pub struct OrderResult {
pub order_id: String,
pub symbol: String,
pub side: OrderSide,
pub order_type: OrderType,
pub requested_quantity: f64,
pub filled_quantity: f64,
pub average_price: Option<f64>,
pub status: OrderStatus,
pub timestamp: DateTime<FixedOffset>,
pub fees: Option<f64>,
pub error: Option<String>,
pub client_order_id: Option<String>,
pub metadata: HashMap<String, String>,
}
impl OrderResult {
pub fn new(
order_id: &str,
symbol: &str,
side: OrderSide,
order_type: OrderType,
requested_quantity: f64,
timestamp: DateTime<FixedOffset>,
) -> Self {
Self {
order_id: order_id.to_string(),
symbol: symbol.to_string(),
side,
order_type,
requested_quantity,
filled_quantity: 0.0,
average_price: None,
status: OrderStatus::Created,
timestamp,
fees: None,
error: None,
client_order_id: None,
metadata: HashMap::new(),
}
}
pub fn is_active(&self) -> bool {
matches!(self.status, OrderStatus::Created | OrderStatus::Submitted | OrderStatus::PartiallyFilled)
}
pub fn is_complete(&self) -> bool {
matches!(self.status, OrderStatus::Filled | OrderStatus::Cancelled | OrderStatus::Rejected | OrderStatus::Expired)
}
pub fn is_filled(&self) -> bool {
matches!(self.status, OrderStatus::PartiallyFilled | OrderStatus::Filled)
}
pub fn fill_percentage(&self) -> f64 {
if self.requested_quantity > 0.0 {
self.filled_quantity / self.requested_quantity * 100.0
} else {
0.0
}
}
pub fn filled_notional(&self) -> Option<f64> {
self.average_price.map(|price| self.filled_quantity * price)
}
}
#[derive(Debug, Clone)]
pub struct MarketData {
pub symbol: String,
pub price: f64,
pub bid: f64,
pub ask: f64,
pub volume: f64,
pub timestamp: DateTime<FixedOffset>,
pub funding_rate: Option<f64>,
pub next_funding_time: Option<DateTime<FixedOffset>>,
pub open_interest: Option<f64>,
pub depth: Option<OrderBookSnapshot>,
pub recent_trades: Option<Vec<Trade>>,
pub price_change_24h_pct: Option<f64>,
pub high_24h: Option<f64>,
pub low_24h: Option<f64>,
pub metadata: HashMap<String, String>,
}
impl MarketData {
pub fn new(
symbol: &str,
price: f64,
bid: f64,
ask: f64,
volume: f64,
timestamp: DateTime<FixedOffset>,
) -> Self {
Self {
symbol: symbol.to_string(),
price,
bid,
ask,
volume,
timestamp,
funding_rate: None,
next_funding_time: None,
open_interest: None,
depth: None,
recent_trades: None,
price_change_24h_pct: None,
high_24h: None,
low_24h: None,
metadata: HashMap::new(),
}
}
pub fn mid_price(&self) -> f64 {
(self.bid + self.ask) / 2.0
}
pub fn spread(&self) -> f64 {
self.ask - self.bid
}
pub fn spread_percentage(&self) -> f64 {
let mid = self.mid_price();
if mid > 0.0 {
self.spread() / mid * 100.0
} else {
0.0
}
}
pub fn with_funding_rate(
mut self,
funding_rate: f64,
next_funding_time: DateTime<FixedOffset>,
) -> Self {
self.funding_rate = Some(funding_rate);
self.next_funding_time = Some(next_funding_time);
self
}
pub fn with_open_interest(mut self, open_interest: f64) -> Self {
self.open_interest = Some(open_interest);
self
}
pub fn with_24h_stats(
mut self,
price_change_pct: f64,
high: f64,
low: f64,
) -> Self {
self.price_change_24h_pct = Some(price_change_pct);
self.high_24h = Some(high);
self.low_24h = Some(low);
self
}
pub fn with_metadata(mut self, key: &str, value: &str) -> Self {
self.metadata.insert(key.to_string(), value.to_string());
self
}
}
#[derive(Debug, Clone)]
pub struct OrderBookLevel {
pub price: f64,
pub quantity: f64,
}
#[derive(Debug, Clone)]
pub struct OrderBookSnapshot {
pub bids: Vec<OrderBookLevel>,
pub asks: Vec<OrderBookLevel>,
pub timestamp: DateTime<FixedOffset>,
}
#[derive(Debug, Clone)]
pub struct Trade {
pub id: String,
pub price: f64,
pub quantity: f64,
pub timestamp: DateTime<FixedOffset>,
pub side: Option<OrderSide>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SignalDirection {
Buy,
Sell,
Neutral,
Close,
}
#[derive(Debug, Clone)]
pub struct Signal {
pub symbol: String,
pub direction: SignalDirection,
pub strength: f64,
pub timestamp: DateTime<FixedOffset>,
pub metadata: HashMap<String, String>,
}
pub trait TradingStrategy: Send + Sync {
fn name(&self) -> &str;
fn on_market_data(&mut self, data: &MarketData) -> Result<Vec<OrderRequest>, String>;
fn on_order_fill(&mut self, fill: &OrderResult) -> Result<(), String>;
fn on_funding_payment(&mut self, payment: &FundingPayment) -> Result<(), String>;
fn get_current_signals(&self) -> HashMap<String, Signal>;
}
#[derive(Debug, Clone)]
pub struct FundingPayment {
pub symbol: String,
pub rate: f64,
pub position_size: f64,
pub amount: f64,
pub timestamp: DateTime<FixedOffset>,
}
#[derive(Debug, Clone)]
pub struct TradingConfig {
pub initial_balance: f64,
pub risk_config: Option<RiskConfig>,
pub slippage_config: Option<SlippageConfig>,
pub api_config: Option<ApiConfig>,
pub parameters: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct RiskConfig {
pub max_position_size_pct: f64,
pub max_daily_loss_pct: f64,
pub stop_loss_pct: f64,
pub take_profit_pct: f64,
pub max_leverage: f64,
pub max_positions: usize,
pub max_drawdown_pct: f64,
pub use_trailing_stop: bool,
pub trailing_stop_distance_pct: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct SlippageConfig {
pub base_slippage_pct: f64,
pub volume_impact_factor: f64,
pub volatility_impact_factor: f64,
pub random_slippage_max_pct: f64,
pub simulated_latency_ms: u64,
pub use_order_book: bool,
pub max_slippage_pct: f64,
}
#[derive(Debug, Clone)]
pub struct ApiConfig {
pub api_key: String,
pub api_secret: String,
pub endpoint: String,
pub use_testnet: bool,
pub timeout_ms: u64,
pub rate_limit: Option<f64>,
pub retry_attempts: u32,
pub retry_delay_ms: u64,
}