修复盘中止盈止损行情缓存
This commit is contained in:
@@ -1263,8 +1263,9 @@ pub struct PlatformExprStrategy {
|
||||
stock_text_factors_required: bool,
|
||||
stock_state_cache_date: RefCell<Option<NaiveDate>>,
|
||||
stock_state_cache_calendar_index: RefCell<Option<usize>>,
|
||||
stock_state_cache:
|
||||
RefCell<AHashMap<(NaiveDate, u32, Option<NaiveTime>, bool), Arc<StockExpressionState>>>,
|
||||
stock_state_cache: RefCell<
|
||||
AHashMap<(NaiveDate, u32, Option<NaiveTime>, bool, bool), Arc<StockExpressionState>>,
|
||||
>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
@@ -4329,7 +4330,17 @@ impl PlatformExprStrategy {
|
||||
S: StockStateSnapshotSource<'a>,
|
||||
{
|
||||
let calendar_index = self.prepare_stock_state_cache_date(ctx, date);
|
||||
let cache_key = (factor_date, symbol_id, execution_time, use_intraday_quote);
|
||||
let has_intraday_quote = use_intraday_quote
|
||||
&& self
|
||||
.scheduled_quote_at_time(ctx, date, symbol, execution_time)
|
||||
.is_some();
|
||||
let cache_key = (
|
||||
factor_date,
|
||||
symbol_id,
|
||||
execution_time,
|
||||
use_intraday_quote,
|
||||
has_intraday_quote,
|
||||
);
|
||||
if let Some(state) = self.stock_state_cache.borrow().get(&cache_key) {
|
||||
return Ok(Arc::clone(state));
|
||||
}
|
||||
@@ -11573,14 +11584,6 @@ impl PlatformExprStrategy {
|
||||
}
|
||||
let scheduled_time = self.intraday_execution_start_time();
|
||||
if self.uses_intraday_execution_quotes()
|
||||
&& matches!(
|
||||
self.config.matching_type,
|
||||
MatchingType::MinuteLast
|
||||
| MatchingType::MinuteBestOwn
|
||||
| MatchingType::MinuteBestCounterparty
|
||||
| MatchingType::Vwap
|
||||
| MatchingType::Twap
|
||||
)
|
||||
&& self
|
||||
.scheduled_quote_at_time(ctx, signal_date, symbol, Some(scheduled_time))
|
||||
.is_none()
|
||||
@@ -18685,6 +18688,152 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timed_current_close_stop_loss_reloads_state_after_quote_arrives() {
|
||||
let prev_date = d(2025, 10, 10);
|
||||
let date = d(2025, 10, 13);
|
||||
let symbol = "603726.SH";
|
||||
let instrument = Instrument {
|
||||
symbol: symbol.to_string(),
|
||||
name: symbol.to_string(),
|
||||
board: "SH".to_string(),
|
||||
round_lot: 100,
|
||||
listed_at: Some(d(2020, 1, 1)),
|
||||
delisted_at: None,
|
||||
status: "active".to_string(),
|
||||
};
|
||||
let market = DailyMarketSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
timestamp: Some("2025-10-13 15:00:00".to_string()),
|
||||
day_open: 23.46,
|
||||
open: 23.46,
|
||||
high: 26.20,
|
||||
low: 23.20,
|
||||
close: 25.90,
|
||||
last_price: 25.90,
|
||||
bid1: 25.89,
|
||||
ask1: 25.90,
|
||||
prev_close: 26.07,
|
||||
volume: 1_000_000,
|
||||
minute_volume: 10_000,
|
||||
bid1_volume: 10_000,
|
||||
ask1_volume: 10_000,
|
||||
trading_phase: Some("continuous".to_string()),
|
||||
paused: false,
|
||||
upper_limit: 28.68,
|
||||
lower_limit: 23.46,
|
||||
price_tick: 0.01,
|
||||
};
|
||||
let factor = DailyFactorSnapshot {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
market_cap_bn: 10.0,
|
||||
free_float_cap_bn: 8.0,
|
||||
pe_ttm: 8.0,
|
||||
turnover_ratio: Some(1.0),
|
||||
effective_turnover_ratio: Some(1.0),
|
||||
extra_factors: BTreeMap::new(),
|
||||
};
|
||||
let candidate = CandidateEligibility {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
is_st: false,
|
||||
is_star_st: false,
|
||||
is_new_listing: false,
|
||||
is_paused: false,
|
||||
allow_buy: true,
|
||||
allow_sell: true,
|
||||
is_kcb: false,
|
||||
is_one_yuan: false,
|
||||
risk_level_code: None,
|
||||
};
|
||||
let benchmark = BenchmarkSnapshot {
|
||||
date,
|
||||
benchmark: "000300.SH".to_string(),
|
||||
open: 4000.0,
|
||||
close: 4010.0,
|
||||
prev_close: 3990.0,
|
||||
volume: 1_000_000,
|
||||
};
|
||||
let without_quote = DataSet::from_components(
|
||||
vec![instrument.clone()],
|
||||
vec![market.clone()],
|
||||
vec![factor.clone()],
|
||||
vec![candidate.clone()],
|
||||
vec![benchmark.clone()],
|
||||
)
|
||||
.expect("dataset without quote");
|
||||
let with_quote = DataSet::from_components_with_actions_and_quotes(
|
||||
vec![instrument],
|
||||
vec![market],
|
||||
vec![factor],
|
||||
vec![candidate],
|
||||
vec![benchmark],
|
||||
Vec::new(),
|
||||
vec![IntradayExecutionQuote {
|
||||
date,
|
||||
symbol: symbol.to_string(),
|
||||
timestamp: date.and_hms_opt(9, 30, 0).expect("timestamp"),
|
||||
last_price: 23.46,
|
||||
bid1: 23.45,
|
||||
ask1: 23.46,
|
||||
bid1_volume: 10_000,
|
||||
ask1_volume: 10_000,
|
||||
volume_delta: 10_000,
|
||||
amount_delta: 234_600.0,
|
||||
trading_phase: Some("continuous".to_string()),
|
||||
}],
|
||||
)
|
||||
.expect("dataset with quote");
|
||||
let mut portfolio = PortfolioState::new(1_000_000.0);
|
||||
portfolio
|
||||
.position_mut(symbol)
|
||||
.buy(prev_date, 16_400, 26.700856097560973);
|
||||
let subscriptions = BTreeSet::new();
|
||||
let context = |data| StrategyContext {
|
||||
execution_date: date,
|
||||
decision_date: date,
|
||||
decision_index: 40,
|
||||
data,
|
||||
portfolio: &portfolio,
|
||||
futures_account: None,
|
||||
open_orders: &[],
|
||||
dynamic_universe: None,
|
||||
subscriptions: &subscriptions,
|
||||
process_events: &[],
|
||||
active_process_event: None,
|
||||
active_datetime: None,
|
||||
order_events: &[],
|
||||
fills: &[],
|
||||
};
|
||||
let mut cfg = PlatformExprStrategyConfig::microcap_rotation();
|
||||
cfg.rotation_enabled = false;
|
||||
cfg.matching_type = MatchingType::CurrentBarClose;
|
||||
cfg.intraday_execution_time = Some(NaiveTime::from_hms_opt(9, 30, 0).expect("time"));
|
||||
cfg.signal_symbol = symbol.to_string();
|
||||
cfg.stop_loss_expr = "holding_return <= -0.08".to_string();
|
||||
cfg.take_profit_expr.clear();
|
||||
let strategy = PlatformExprStrategy::new(cfg);
|
||||
let no_quote_ctx = context(&without_quote);
|
||||
let no_quote_day = strategy.day_state(&no_quote_ctx, date).expect("day state");
|
||||
assert_eq!(
|
||||
strategy
|
||||
.stop_take_action(&no_quote_ctx, date, date, &no_quote_day, symbol)
|
||||
.expect("no quote stop check"),
|
||||
(false, false),
|
||||
);
|
||||
|
||||
let quote_ctx = context(&with_quote);
|
||||
let quote_day = strategy.day_state("e_ctx, date).expect("day state");
|
||||
assert_eq!(
|
||||
strategy
|
||||
.stop_take_action("e_ctx, date, date, "e_day, symbol)
|
||||
.expect("quoted stop check"),
|
||||
(true, false),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn platform_stop_loss_residual_pending_rechecks_signal_next_day() {
|
||||
let prev_date = d(2025, 1, 6);
|
||||
|
||||
Reference in New Issue
Block a user