接入独立手工仓位时间线并保留原策略配置
This commit is contained in:
@@ -627,8 +627,18 @@ impl<S, C, R> BacktestEngine<S, C, R> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_observed_manual_executions(mut self, replay: crate::manual_execution::ManualExecutionReplay) -> Result<Self, BacktestError> {
|
||||
pub fn with_observed_manual_executions(
|
||||
mut self,
|
||||
replay: crate::manual_execution::ManualExecutionReplay,
|
||||
) -> Result<Self, BacktestError>
|
||||
where
|
||||
S: Strategy,
|
||||
{
|
||||
replay.validate().map_err(BacktestError::Execution)?;
|
||||
self.strategy.bind_runtime_position_configuration(
|
||||
&replay.position_exposure_events,
|
||||
&replay.legacy_position_exposure_bps,
|
||||
)?;
|
||||
self.manual_execution_source = Some(std::sync::Arc::new(replay));
|
||||
Ok(self)
|
||||
}
|
||||
@@ -5416,28 +5426,73 @@ mod tests {
|
||||
|
||||
fn observed_manual_replay(date: NaiveDate, times: &[(u32, u32)]) -> crate::manual_execution::ManualExecutionReplay {
|
||||
use crate::manual_execution::*;
|
||||
let utc = |local: NaiveDateTime| chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(local - chrono::Duration::hours(8), chrono::Utc);
|
||||
let actions = times.iter().enumerate().map(|(index, &(hour, minute))| {
|
||||
let observed = date.and_hms_opt(hour, minute, 0).unwrap();
|
||||
let executed = if hour < 9 || (hour == 9 && minute < 25) {
|
||||
date.pred_opt().unwrap().and_hms_opt(15, 20, 0).unwrap()
|
||||
} else { observed.min(date.and_hms_opt(15, 20, 0).unwrap()) };
|
||||
let created = utc(executed - chrono::Duration::seconds(1));
|
||||
ManualExecutionAction { action_id: format!("manual-action-{index}"), source: ManualExecutionSource::ManualSecurityTrade,
|
||||
audit_event_ids: vec![format!("manual-audit-{index}")], confirmed_at: created, confirmation_observed_at: created,
|
||||
outcome: ManualActionOutcome::OrdersTerminal, orders: vec![ManualExecutionOrder {
|
||||
order_id: format!("manual-order-{index}"), broker_order_id: Some(format!("broker-order-{index}")), source_adapter: Some("gt-api".into()),
|
||||
symbol: SYMBOL.into(), side: OrderSide::Buy, quantity: 100, order_created_at: created,
|
||||
terminal_observed_at: utc(observed), terminal_status: ManualOrderTerminalStatus::Filled,
|
||||
fills: vec![ManualExecutionFill { trade_id: format!("manual-trade-{index}"), observation_event_id: format!("manual-receipt-{index}"),
|
||||
observation_sequence: index as u64 + 1, fee_observation_event_id: format!("manual-receipt-{index}"), fee_observation_sequence: index as u64 + 1,
|
||||
fee_observed_at: utc(observed), trade_date: executed.date(), executed_at: utc(executed), observed_at: utc(observed),
|
||||
timestamp_precision: ManualTimestampPrecision::Second, quantity: 100, price: 10.into(), commission: Some("1.5".parse().unwrap()),
|
||||
stamp_tax: None, transfer_fee: None, total_fee: "1.5".parse().unwrap() }],
|
||||
}] }
|
||||
}).collect();
|
||||
let mut replay = ManualExecutionReplay { schema: MANUAL_REPLAY_SCHEMA.into(), runtime_id: "manual-runtime".into(), account_id: "manual-account".into(),
|
||||
source_contract_sha256: "a".repeat(64), content_sha256: String::new(), observation_cutoff: utc(date.and_hms_opt(23, 59, 59).unwrap()), actions };
|
||||
let utc = |local: NaiveDateTime| {
|
||||
chrono::DateTime::<chrono::Utc>::from_naive_utc_and_offset(
|
||||
local - chrono::Duration::hours(8),
|
||||
chrono::Utc,
|
||||
)
|
||||
};
|
||||
let actions = times
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, &(hour, minute))| {
|
||||
let observed = date.and_hms_opt(hour, minute, 0).unwrap();
|
||||
let executed = if hour < 9 || (hour == 9 && minute < 25) {
|
||||
date.pred_opt().unwrap().and_hms_opt(15, 20, 0).unwrap()
|
||||
} else {
|
||||
observed.min(date.and_hms_opt(15, 20, 0).unwrap())
|
||||
};
|
||||
let created = utc(executed - chrono::Duration::seconds(1));
|
||||
ManualExecutionAction {
|
||||
action_id: format!("manual-action-{index}"),
|
||||
source: ManualExecutionSource::ManualSecurityTrade,
|
||||
audit_event_ids: vec![format!("manual-audit-{index}")],
|
||||
confirmed_at: created,
|
||||
confirmation_observed_at: created,
|
||||
outcome: ManualActionOutcome::OrdersTerminal,
|
||||
orders: vec![ManualExecutionOrder {
|
||||
order_id: format!("manual-order-{index}"),
|
||||
broker_order_id: Some(format!("broker-order-{index}")),
|
||||
source_adapter: Some("gt-api".into()),
|
||||
symbol: SYMBOL.into(),
|
||||
side: OrderSide::Buy,
|
||||
quantity: 100,
|
||||
order_created_at: created,
|
||||
terminal_observed_at: utc(observed),
|
||||
terminal_status: ManualOrderTerminalStatus::Filled,
|
||||
fills: vec![ManualExecutionFill {
|
||||
trade_id: format!("manual-trade-{index}"),
|
||||
observation_event_id: format!("manual-receipt-{index}"),
|
||||
observation_sequence: index as u64 + 1,
|
||||
fee_observation_event_id: format!("manual-receipt-{index}"),
|
||||
fee_observation_sequence: index as u64 + 1,
|
||||
fee_observed_at: utc(observed),
|
||||
trade_date: executed.date(),
|
||||
executed_at: utc(executed),
|
||||
observed_at: utc(observed),
|
||||
timestamp_precision: ManualTimestampPrecision::Second,
|
||||
quantity: 100,
|
||||
price: 10.into(),
|
||||
commission: Some("1.5".parse().unwrap()),
|
||||
stamp_tax: None,
|
||||
transfer_fee: None,
|
||||
total_fee: "1.5".parse().unwrap(),
|
||||
}],
|
||||
}],
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut replay = ManualExecutionReplay {
|
||||
schema: MANUAL_REPLAY_SCHEMA.into(),
|
||||
runtime_id: "manual-runtime".into(),
|
||||
account_id: "manual-account".into(),
|
||||
source_contract_sha256: "a".repeat(64),
|
||||
content_sha256: String::new(),
|
||||
observation_cutoff: utc(date.and_hms_opt(23, 59, 59).unwrap()),
|
||||
actions,
|
||||
position_exposure_events: vec![],
|
||||
legacy_position_exposure_bps: BTreeMap::new(),
|
||||
};
|
||||
replay.content_sha256 = replay.content_digest().unwrap();
|
||||
replay.validate().unwrap();
|
||||
replay
|
||||
|
||||
Reference in New Issue
Block a user