修复迟到成交跨公司行为的经济账本校正
This commit is contained in:
@@ -482,9 +482,80 @@ pub struct ManualReplayApplication {
|
||||
pub ledger_gross_amount: String,
|
||||
pub ledger_fees: String,
|
||||
pub cash_delta: String,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub corporate_adjustment: Option<ManualCorporateAdjustment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ManualCorporateAdjustment {
|
||||
pub schema: String,
|
||||
pub observed_at: DateTime<Utc>,
|
||||
pub cash_dividends_enabled: bool,
|
||||
pub dividend_cost_basis_adjustment: bool,
|
||||
pub actions: Vec<ManualCorporateActionReference>,
|
||||
pub cash_before: String,
|
||||
pub cash_after: String,
|
||||
pub corporate_cash_delta: String,
|
||||
pub positions: BTreeMap<String, ManualCorporatePositionChange>,
|
||||
pub reference_sha256: String,
|
||||
pub replayed_sha256: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ManualCorporateActionReference {
|
||||
pub date: NaiveDate,
|
||||
pub symbol: String,
|
||||
pub successor_symbol: Option<String>,
|
||||
pub share_cash: String,
|
||||
pub split_ratio: String,
|
||||
pub successor_ratio: Option<String>,
|
||||
pub successor_cash: Option<String>,
|
||||
pub sha256: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
||||
pub struct ManualCorporatePositionChange {
|
||||
pub quantity_before: u32,
|
||||
pub quantity_after: u32,
|
||||
pub cost_basis_before: String,
|
||||
pub cost_basis_after: String,
|
||||
}
|
||||
|
||||
impl ManualReplayCursor {
|
||||
pub(crate) fn frozen_source(&self) -> std::sync::Arc<ManualExecutionReplay> {
|
||||
self.replay.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn next_observation(&self) -> Option<ManualFillObservation<'_>> {
|
||||
self.indices.get(self.cursor).map(|&(a, o, f)| ManualFillObservation {
|
||||
action: &self.replay.actions[a], order: &self.replay.actions[a].orders[o],
|
||||
fill: &self.replay.actions[a].orders[o].fills[f],
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn advance_next_projected<F>(
|
||||
&mut self, portfolio: &mut PortfolioState, project: F,
|
||||
) -> Result<Option<ManualReplayApplication>, String>
|
||||
where F: FnOnce(ManualFillObservation<'_>, &mut PortfolioState) -> Result<(AppliedManualFill, ManualCorporateAdjustment), String> {
|
||||
let Some(observation) = self.next_observation() else { return Ok(None); };
|
||||
let at = observation.fill.observed_at;
|
||||
if at > self.replay.observation_cutoff || self.clock.is_some_and(|clock| at < clock) {
|
||||
return Err("manual projected observation clock violates the frozen trace".into());
|
||||
}
|
||||
let mut next = portfolio.clone();
|
||||
let (applied, adjustment) = project(observation, &mut next)?;
|
||||
let mut application = observation.application(applied)?;
|
||||
application.corporate_adjustment = Some(adjustment);
|
||||
crate::finite_serialization::validate(&application).map_err(|error| error.to_string())?;
|
||||
*portfolio = next;
|
||||
self.cursor += 1;
|
||||
self.clock = Some(at);
|
||||
Ok(Some(application))
|
||||
}
|
||||
|
||||
pub fn new(replay: ManualExecutionReplay) -> Result<Self, String> {
|
||||
Self::from_shared(std::sync::Arc::new(replay))
|
||||
}
|
||||
@@ -594,6 +665,7 @@ impl ManualReplayCursor {
|
||||
ledger_gross_amount: applied.gross.to_decimal_string(),
|
||||
ledger_fees: applied.fees.to_decimal_string(),
|
||||
cash_delta: applied.cash_delta.to_decimal_string(),
|
||||
corporate_adjustment: None,
|
||||
});
|
||||
}
|
||||
*portfolio = next;
|
||||
@@ -604,6 +676,21 @@ impl ManualReplayCursor {
|
||||
}
|
||||
|
||||
impl ManualFillObservation<'_> {
|
||||
fn application(&self, applied: AppliedManualFill) -> Result<ManualReplayApplication, String> {
|
||||
Ok(ManualReplayApplication {
|
||||
action_id: self.action.action_id.clone(), order_id: self.order.order_id.clone(),
|
||||
trade_id: self.fill.trade_id.clone(), observation_event_id: self.fill.observation_event_id.clone(),
|
||||
observation_sequence: self.fill.observation_sequence, observed_at: self.fill.observed_at,
|
||||
fee_observation_event_id: self.fill.fee_observation_event_id.clone(), fee_observed_at: self.fill.fee_observed_at,
|
||||
executed_at: self.fill.executed_at, symbol: self.order.symbol.clone(), side: self.order.side,
|
||||
quantity: self.fill.quantity, quantity_after: applied.quantity_after, price: self.fill.price.to_string(),
|
||||
commission: self.fill.commission.map(|fee| fee.to_string()), stamp_tax: self.fill.stamp_tax.map(|fee| fee.to_string()),
|
||||
transfer_fee: self.fill.transfer_fee.map(|fee| fee.to_string()), source_total_fee: self.fill.total_fee.to_string(),
|
||||
source_gross_amount: self.fill.gross_amount()?.to_string(), ledger_gross_amount: applied.gross.to_decimal_string(),
|
||||
ledger_fees: applied.fees.to_decimal_string(), cash_delta: applied.cash_delta.to_decimal_string(), corporate_adjustment: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn apply(
|
||||
&self,
|
||||
portfolio: &mut PortfolioState,
|
||||
|
||||
Reference in New Issue
Block a user