Close RQAlpha P0-P2 parity gaps

This commit is contained in:
boris
2026-04-23 21:07:59 -07:00
parent 6be87c9982
commit beb9c7a7ae
8 changed files with 1830 additions and 86 deletions

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeMap;
use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use crate::events::{
AccountEvent, FillEvent, OrderEvent, OrderSide, OrderStatus, PositionEvent, ProcessEvent,
@@ -69,6 +70,108 @@ pub struct FuturesContractSpec {
pub short_margin_rate: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum FuturesCommissionType {
ByMoney,
ByVolume,
}
impl FuturesCommissionType {
pub fn parse(value: &str) -> Self {
match value.trim().to_ascii_lowercase().as_str() {
"by_volume" | "volume" | "byvolume" => Self::ByVolume,
_ => Self::ByMoney,
}
}
pub fn as_str(&self) -> &'static str {
match self {
Self::ByMoney => "by_money",
Self::ByVolume => "by_volume",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FuturesTradingParameter {
pub symbol: String,
pub effective_date: Option<NaiveDate>,
pub contract_multiplier: f64,
pub long_margin_rate: f64,
pub short_margin_rate: f64,
pub commission_type: FuturesCommissionType,
pub open_commission_ratio: f64,
pub close_commission_ratio: f64,
pub close_today_commission_ratio: f64,
pub price_tick: f64,
}
impl FuturesTradingParameter {
pub fn spec(&self) -> FuturesContractSpec {
FuturesContractSpec::new(
self.contract_multiplier,
self.long_margin_rate,
self.short_margin_rate,
)
}
}
#[derive(Debug, Clone, Copy)]
pub struct FuturesTransactionCostModel {
pub commission_multiplier: f64,
}
impl Default for FuturesTransactionCostModel {
fn default() -> Self {
Self {
commission_multiplier: 1.0,
}
}
}
impl FuturesTransactionCostModel {
pub fn calculate(
&self,
params: &FuturesTradingParameter,
effect: FuturesPositionEffect,
price: f64,
quantity: u32,
close_today_quantity: u32,
) -> f64 {
if quantity == 0 || !price.is_finite() || price <= 0.0 {
return 0.0;
}
let quantity = quantity as f64;
let close_today_quantity = close_today_quantity.min(quantity as u32) as f64;
let close_yesterday_quantity = (quantity - close_today_quantity).max(0.0);
let raw = match params.commission_type {
FuturesCommissionType::ByMoney => match effect {
FuturesPositionEffect::Open => {
price * quantity * params.contract_multiplier * params.open_commission_ratio
}
FuturesPositionEffect::Close
| FuturesPositionEffect::CloseToday
| FuturesPositionEffect::CloseYesterday => {
price
* params.contract_multiplier
* (close_yesterday_quantity * params.close_commission_ratio
+ close_today_quantity * params.close_today_commission_ratio)
}
},
FuturesCommissionType::ByVolume => match effect {
FuturesPositionEffect::Open => quantity * params.open_commission_ratio,
FuturesPositionEffect::Close
| FuturesPositionEffect::CloseToday
| FuturesPositionEffect::CloseYesterday => {
close_yesterday_quantity * params.close_commission_ratio
+ close_today_quantity * params.close_today_commission_ratio
}
},
};
raw.max(0.0) * self.commission_multiplier.max(0.0)
}
}
#[derive(Debug, Clone)]
pub struct FuturesOrderIntent {
pub symbol: String,
@@ -78,6 +181,8 @@ pub struct FuturesOrderIntent {
pub quantity: u32,
pub price: f64,
pub transaction_cost: f64,
pub limit_price: Option<f64>,
pub allow_pending: bool,
pub reason: String,
}
@@ -99,6 +204,8 @@ impl FuturesOrderIntent {
quantity,
price,
transaction_cost,
limit_price: None,
allow_pending: false,
reason: reason.into(),
}
}
@@ -121,10 +228,80 @@ impl FuturesOrderIntent {
quantity,
price,
transaction_cost,
limit_price: None,
allow_pending: false,
reason: reason.into(),
}
}
pub fn limit_open(
symbol: impl Into<String>,
direction: FuturesDirection,
spec: FuturesContractSpec,
quantity: u32,
limit_price: f64,
transaction_cost: f64,
reason: impl Into<String>,
) -> Self {
Self::open(
symbol,
direction,
spec,
quantity,
limit_price,
transaction_cost,
reason,
)
.with_limit_price(limit_price)
}
pub fn limit_close(
symbol: impl Into<String>,
direction: FuturesDirection,
effect: FuturesPositionEffect,
spec: FuturesContractSpec,
quantity: u32,
limit_price: f64,
transaction_cost: f64,
reason: impl Into<String>,
) -> Self {
Self::close(
symbol,
direction,
effect,
spec,
quantity,
limit_price,
transaction_cost,
reason,
)
.with_limit_price(limit_price)
}
pub fn with_limit_price(mut self, limit_price: f64) -> Self {
self.limit_price = limit_price
.is_finite()
.then_some(limit_price)
.filter(|v| *v > 0.0);
self.allow_pending = self.limit_price.is_some();
self
}
pub fn with_allow_pending(mut self, allow_pending: bool) -> Self {
self.allow_pending = allow_pending;
self
}
pub fn with_price(mut self, price: f64) -> Self {
self.price = price;
self
}
pub fn with_transaction_cost(mut self, transaction_cost: f64) -> Self {
self.transaction_cost = transaction_cost;
self
}
pub fn side(&self) -> OrderSide {
if self.effect == FuturesPositionEffect::Open {
self.direction.open_side()
@@ -132,6 +309,29 @@ impl FuturesOrderIntent {
self.direction.close_side()
}
}
pub fn with_trading_parameter(
mut self,
params: &FuturesTradingParameter,
cost_model: FuturesTransactionCostModel,
) -> Self {
self.spec = params.spec();
if self.transaction_cost <= 0.0 {
let close_today_quantity = if self.effect == FuturesPositionEffect::CloseToday {
self.quantity
} else {
0
};
self.transaction_cost = cost_model.calculate(
params,
self.effect,
self.price,
self.quantity,
close_today_quantity,
);
}
self
}
}
#[derive(Debug, Clone, Default)]