将股票执行资金切换为定点账本
This commit is contained in:
@@ -10,6 +10,7 @@ use crate::data::{
|
||||
};
|
||||
use crate::engine::BacktestError;
|
||||
use crate::events::{FillEvent, OrderEvent, OrderSide, OrderStatus, ProcessEvent};
|
||||
use crate::fixed_point::FixedMoney;
|
||||
use crate::futures::{FuturesAccountState, FuturesOrderIntent};
|
||||
use crate::instrument::Instrument;
|
||||
use crate::portfolio::PortfolioState;
|
||||
@@ -1750,14 +1751,47 @@ impl OmniMicroCapStrategy {
|
||||
ChinaAShareCostModel::from_trading_constraints(self.config.risk_config.trading_constraints)
|
||||
}
|
||||
|
||||
fn buy_commission(&self, gross_amount: f64) -> f64 {
|
||||
self.cost_model().commission_for(gross_amount)
|
||||
fn buy_cost(&self, gross_amount: f64) -> f64 {
|
||||
let model = self.cost_model();
|
||||
FixedMoney::checked_sum_f64([
|
||||
model.commission_for(gross_amount),
|
||||
model.transfer_fee_for(gross_amount),
|
||||
])
|
||||
.expect("projected buy costs must be finite fixed-point money")
|
||||
.to_f64()
|
||||
}
|
||||
|
||||
fn sell_cost(&self, date: NaiveDate, gross_amount: f64) -> f64 {
|
||||
let model = self.cost_model();
|
||||
model.commission_for(gross_amount)
|
||||
+ model.stamp_tax_for(date, OrderSide::Sell, gross_amount)
|
||||
FixedMoney::checked_sum_f64([
|
||||
model.commission_for(gross_amount),
|
||||
model.stamp_tax_for(date, OrderSide::Sell, gross_amount),
|
||||
model.transfer_fee_for(gross_amount),
|
||||
])
|
||||
.expect("projected sell costs must be finite fixed-point money")
|
||||
.to_f64()
|
||||
}
|
||||
|
||||
fn buy_cash_out(&self, gross_amount: f64) -> f64 {
|
||||
FixedMoney::checked_sum_f64([gross_amount, self.buy_cost(gross_amount)])
|
||||
.expect("projected buy cash must be finite fixed-point money")
|
||||
.to_f64()
|
||||
}
|
||||
|
||||
fn sell_net_cash(&self, date: NaiveDate, gross_amount: f64) -> f64 {
|
||||
let gross = FixedMoney::from_f64(gross_amount)
|
||||
.expect("projected sell gross must be finite fixed-point money");
|
||||
gross
|
||||
.checked_sub(
|
||||
FixedMoney::from_f64(self.sell_cost(date, gross.to_f64()))
|
||||
.expect("projected sell costs must be finite fixed-point money"),
|
||||
)
|
||||
.expect("projected sell proceeds underflow")
|
||||
.to_f64()
|
||||
}
|
||||
|
||||
fn fixed_cash_fits(value: f64, limit: f64) -> bool {
|
||||
FixedMoney::f64_fits_within(value, limit).unwrap_or(false)
|
||||
}
|
||||
|
||||
fn round_lot_quantity(
|
||||
@@ -1826,7 +1860,7 @@ impl OmniMicroCapStrategy {
|
||||
let mut quantity = self.round_lot_quantity((cash / sizing_price).floor() as u32, 100, 100);
|
||||
while quantity > 0 {
|
||||
let gross_amount = execution_price * quantity as f64;
|
||||
if gross_amount + self.buy_commission(gross_amount) <= cash + 1e-6 {
|
||||
if Self::fixed_cash_fits(self.buy_cash_out(gross_amount), cash) {
|
||||
return quantity;
|
||||
}
|
||||
quantity = self.decrement_order_quantity(quantity, 100, 100);
|
||||
@@ -1874,8 +1908,10 @@ impl OmniMicroCapStrategy {
|
||||
);
|
||||
while snapshot_requested_qty > 0 {
|
||||
let gross_amount = sizing_price * snapshot_requested_qty as f64;
|
||||
let cash_out = gross_amount + self.buy_commission(gross_amount);
|
||||
if cash_out <= order_value + 1e-6 && cash_out <= projected.cash() + 1e-6 {
|
||||
let cash_out = self.buy_cash_out(gross_amount);
|
||||
if Self::fixed_cash_fits(cash_out, order_value)
|
||||
&& Self::fixed_cash_fits(cash_out, projected.cash())
|
||||
{
|
||||
break;
|
||||
}
|
||||
snapshot_requested_qty = self.decrement_order_quantity(
|
||||
@@ -1902,8 +1938,10 @@ impl OmniMicroCapStrategy {
|
||||
let mut quantity = snapshot_requested_qty;
|
||||
while quantity > 0 {
|
||||
let gross_amount = projected_execution_price * quantity as f64;
|
||||
let cash_out = gross_amount + self.buy_commission(gross_amount);
|
||||
if cash_out <= order_value + 1e-6 && cash_out <= projected.cash() + 1e-6 {
|
||||
let cash_out = self.buy_cash_out(gross_amount);
|
||||
if Self::fixed_cash_fits(cash_out, order_value)
|
||||
&& Self::fixed_cash_fits(cash_out, projected.cash())
|
||||
{
|
||||
break;
|
||||
}
|
||||
quantity =
|
||||
@@ -1918,8 +1956,10 @@ impl OmniMicroCapStrategy {
|
||||
.unwrap_or(projected_execution_price);
|
||||
while quantity > 0 {
|
||||
let gross_amount = execution_price * quantity as f64;
|
||||
let cash_out = gross_amount + self.buy_commission(gross_amount);
|
||||
if cash_out <= order_value + 1e-6 && cash_out <= projected.cash() + 1e-6 {
|
||||
let cash_out = self.buy_cash_out(gross_amount);
|
||||
if Self::fixed_cash_fits(cash_out, order_value)
|
||||
&& Self::fixed_cash_fits(cash_out, projected.cash())
|
||||
{
|
||||
break;
|
||||
}
|
||||
quantity =
|
||||
@@ -1934,11 +1974,15 @@ impl OmniMicroCapStrategy {
|
||||
next_cursor: date.and_time(self.intraday_execution_start_time()) + Duration::seconds(1),
|
||||
};
|
||||
let gross_amount = fill.price * fill.quantity as f64;
|
||||
let cash_out = gross_amount + self.buy_commission(gross_amount);
|
||||
if cash_out > projected.cash() + 1e-6 || cash_out > order_value + 1e-6 {
|
||||
let cash_out = self.buy_cash_out(gross_amount);
|
||||
if !Self::fixed_cash_fits(cash_out, projected.cash())
|
||||
|| !Self::fixed_cash_fits(cash_out, order_value)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
projected.apply_cash_delta(-cash_out);
|
||||
projected
|
||||
.apply_cash_delta(-cash_out)
|
||||
.expect("projected buy cash must fit fixed-point ledger");
|
||||
projected
|
||||
.position_mut(symbol)
|
||||
.buy(date, fill.quantity, fill.price);
|
||||
@@ -1994,12 +2038,14 @@ impl OmniMicroCapStrategy {
|
||||
+ Duration::seconds(1),
|
||||
});
|
||||
let gross_amount = fill.price * fill.quantity as f64;
|
||||
let net_cash = gross_amount - self.sell_cost(date, gross_amount);
|
||||
let net_cash = self.sell_net_cash(date, gross_amount);
|
||||
projected
|
||||
.position_mut(symbol)
|
||||
.sell(fill.quantity, fill.price)
|
||||
.ok()?;
|
||||
projected.apply_cash_delta(net_cash);
|
||||
projected
|
||||
.apply_cash_delta(net_cash)
|
||||
.expect("projected sell cash must fit fixed-point ledger");
|
||||
*execution_state
|
||||
.intraday_turnover
|
||||
.entry(symbol.to_string())
|
||||
@@ -2144,7 +2190,9 @@ impl OmniMicroCapStrategy {
|
||||
);
|
||||
while take_qty > 0 {
|
||||
let candidate_gross = execution_price * take_qty as f64;
|
||||
if gross_limit.is_some_and(|limit| candidate_gross > limit + 1e-6) {
|
||||
if gross_limit
|
||||
.is_some_and(|limit| !Self::fixed_cash_fits(candidate_gross, limit))
|
||||
{
|
||||
take_qty = self.decrement_order_quantity(
|
||||
take_qty,
|
||||
minimum_order_quantity,
|
||||
@@ -2152,9 +2200,8 @@ impl OmniMicroCapStrategy {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
let candidate_cash =
|
||||
candidate_gross + self.buy_commission(candidate_gross);
|
||||
if candidate_cash <= cash + 1e-6 {
|
||||
let candidate_cash = self.buy_cash_out(candidate_gross);
|
||||
if Self::fixed_cash_fits(candidate_cash, cash) {
|
||||
break;
|
||||
}
|
||||
take_qty = self.decrement_order_quantity(
|
||||
@@ -2254,7 +2301,9 @@ impl OmniMicroCapStrategy {
|
||||
if let Some(cash) = cash_limit {
|
||||
while take_qty > 0 {
|
||||
let candidate_gross = gross_amount + quote_price * take_qty as f64;
|
||||
if gross_limit.is_some_and(|limit| candidate_gross > limit + 1e-6) {
|
||||
if gross_limit
|
||||
.is_some_and(|limit| !Self::fixed_cash_fits(candidate_gross, limit))
|
||||
{
|
||||
take_qty = self.decrement_order_quantity(
|
||||
take_qty,
|
||||
minimum_order_quantity,
|
||||
@@ -2262,7 +2311,7 @@ impl OmniMicroCapStrategy {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if candidate_gross + self.buy_commission(candidate_gross) <= cash + 1e-6 {
|
||||
if Self::fixed_cash_fits(self.buy_cash_out(candidate_gross), cash) {
|
||||
break;
|
||||
}
|
||||
take_qty = self.decrement_order_quantity(
|
||||
@@ -2870,8 +2919,8 @@ mod tests {
|
||||
.stamp_tax_rate_after_change = 0.0005;
|
||||
let strategy = OmniMicroCapStrategy::new(cfg);
|
||||
|
||||
assert!((strategy.buy_commission(100_000.0) - 30.0).abs() < 1e-9);
|
||||
assert!((strategy.buy_commission(1_000.0) - 5.0).abs() < 1e-9);
|
||||
assert!((strategy.buy_cost(100_000.0) - 30.0).abs() < 1e-9);
|
||||
assert!((strategy.buy_cost(1_000.0) - 5.0).abs() < 1e-9);
|
||||
assert!(
|
||||
(strategy.sell_cost(NaiveDate::from_ymd_opt(2025, 1, 2).unwrap(), 100_000.0) - 80.0)
|
||||
.abs()
|
||||
|
||||
Reference in New Issue
Block a user