Add account cash flow intents
This commit is contained in:
@@ -157,10 +157,11 @@ where
|
||||
execution_date: NaiveDate,
|
||||
decision_date: NaiveDate,
|
||||
decision_index: usize,
|
||||
portfolio: &PortfolioState,
|
||||
portfolio: &mut PortfolioState,
|
||||
open_orders: &[crate::strategy::OpenOrderView],
|
||||
process_events: &mut Vec<ProcessEvent>,
|
||||
decision: &mut crate::strategy::StrategyDecision,
|
||||
directive_report: &mut BrokerExecutionReport,
|
||||
) -> Result<(), BacktestError> {
|
||||
if decision.order_intents.is_empty() {
|
||||
return Ok(());
|
||||
@@ -282,6 +283,119 @@ where
|
||||
)?;
|
||||
}
|
||||
}
|
||||
crate::strategy::OrderIntent::DepositWithdraw {
|
||||
amount,
|
||||
receiving_days,
|
||||
reason,
|
||||
} => {
|
||||
let cash_before = portfolio.cash();
|
||||
if receiving_days == 0 {
|
||||
portfolio
|
||||
.deposit_withdraw(amount)
|
||||
.map_err(BacktestError::Execution)?;
|
||||
directive_report.account_events.push(AccountEvent {
|
||||
date: execution_date,
|
||||
cash_before,
|
||||
cash_after: portfolio.cash(),
|
||||
total_equity: portfolio.total_equity(),
|
||||
note: format!("deposit_withdraw amount={amount:.2} reason={reason}"),
|
||||
});
|
||||
} else {
|
||||
let payable_date = self
|
||||
.data
|
||||
.next_trading_date(execution_date, receiving_days)
|
||||
.ok_or_else(|| {
|
||||
BacktestError::Execution(format!(
|
||||
"no trading date for deposit_withdraw receiving_days={receiving_days} from {execution_date}"
|
||||
))
|
||||
})?;
|
||||
portfolio
|
||||
.schedule_deposit_withdraw(payable_date, amount, reason.clone())
|
||||
.map_err(BacktestError::Execution)?;
|
||||
directive_report.account_events.push(AccountEvent {
|
||||
date: execution_date,
|
||||
cash_before,
|
||||
cash_after: portfolio.cash(),
|
||||
total_equity: portfolio.total_equity(),
|
||||
note: format!(
|
||||
"deposit_withdraw_scheduled amount={amount:.2} payable_date={payable_date} reason={reason}"
|
||||
),
|
||||
});
|
||||
}
|
||||
decision.diagnostics.push(format!(
|
||||
"account_deposit_withdraw amount={amount:.2} receiving_days={receiving_days}"
|
||||
));
|
||||
publish_custom_process_event(
|
||||
&mut self.strategy,
|
||||
&mut self.process_event_bus,
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&self.data,
|
||||
&*portfolio,
|
||||
open_orders,
|
||||
self.dynamic_universe.as_ref(),
|
||||
&self.subscriptions,
|
||||
process_events,
|
||||
ProcessEvent {
|
||||
date: execution_date,
|
||||
kind: ProcessEventKind::AccountDepositWithdraw,
|
||||
order_id: None,
|
||||
symbol: None,
|
||||
side: None,
|
||||
detail: format!(
|
||||
"reason={reason} amount={amount:.2} receiving_days={receiving_days} cash_before={cash_before:.2} cash_after={:.2}",
|
||||
portfolio.cash()
|
||||
),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
crate::strategy::OrderIntent::FinanceRepay { amount, reason } => {
|
||||
let cash_before = portfolio.cash();
|
||||
let liabilities_before = portfolio.cash_liabilities();
|
||||
portfolio
|
||||
.finance_repay(amount)
|
||||
.map_err(BacktestError::Execution)?;
|
||||
directive_report.account_events.push(AccountEvent {
|
||||
date: execution_date,
|
||||
cash_before,
|
||||
cash_after: portfolio.cash(),
|
||||
total_equity: portfolio.total_equity(),
|
||||
note: format!(
|
||||
"finance_repay amount={amount:.2} liabilities_before={liabilities_before:.2} liabilities_after={:.2} reason={reason}",
|
||||
portfolio.cash_liabilities()
|
||||
),
|
||||
});
|
||||
decision.diagnostics.push(format!(
|
||||
"account_finance_repay amount={amount:.2} liabilities={:.2}",
|
||||
portfolio.cash_liabilities()
|
||||
));
|
||||
publish_custom_process_event(
|
||||
&mut self.strategy,
|
||||
&mut self.process_event_bus,
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&self.data,
|
||||
&*portfolio,
|
||||
open_orders,
|
||||
self.dynamic_universe.as_ref(),
|
||||
&self.subscriptions,
|
||||
process_events,
|
||||
ProcessEvent {
|
||||
date: execution_date,
|
||||
kind: ProcessEventKind::AccountFinanceRepay,
|
||||
order_id: None,
|
||||
symbol: None,
|
||||
side: None,
|
||||
detail: format!(
|
||||
"reason={reason} amount={amount:.2} cash_before={cash_before:.2} cash_after={:.2} liabilities_before={liabilities_before:.2} liabilities_after={:.2}",
|
||||
portfolio.cash(),
|
||||
portfolio.cash_liabilities()
|
||||
),
|
||||
},
|
||||
)?;
|
||||
}
|
||||
other => retained.push(other),
|
||||
}
|
||||
}
|
||||
@@ -352,6 +466,12 @@ where
|
||||
for (execution_idx, execution_date) in execution_dates.iter().copied().enumerate() {
|
||||
let mut corporate_action_notes = Vec::new();
|
||||
portfolio.begin_trading_day();
|
||||
let pending_cash_flow_report = self.settle_pending_cash_flows(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
&mut corporate_action_notes,
|
||||
);
|
||||
self.extend_result(&mut result, pending_cash_flow_report);
|
||||
let receivable_report = self.settle_cash_receivables(
|
||||
execution_date,
|
||||
&mut portfolio,
|
||||
@@ -377,6 +497,7 @@ where
|
||||
let (decision_index, decision_date) =
|
||||
decision_slot.unwrap_or((execution_idx, execution_date));
|
||||
let mut process_events = Vec::new();
|
||||
let mut directive_report = BrokerExecutionReport::default();
|
||||
let pre_open_orders = self.broker.open_order_views();
|
||||
let schedule_rules = self.strategy.schedule_rules();
|
||||
publish_phase_event(
|
||||
@@ -452,10 +573,11 @@ where
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&portfolio,
|
||||
&mut portfolio,
|
||||
&pre_open_orders,
|
||||
&mut process_events,
|
||||
&mut before_trading_decision,
|
||||
&mut directive_report,
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
@@ -546,10 +668,11 @@ where
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&portfolio,
|
||||
&mut portfolio,
|
||||
&pre_open_orders,
|
||||
&mut process_events,
|
||||
&mut auction_decision,
|
||||
&mut directive_report,
|
||||
)?;
|
||||
let mut report = self.broker.execute(
|
||||
execution_date,
|
||||
@@ -738,10 +861,11 @@ where
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&portfolio,
|
||||
&mut portfolio,
|
||||
&on_day_open_orders,
|
||||
&mut process_events,
|
||||
&mut decision,
|
||||
&mut directive_report,
|
||||
)?;
|
||||
|
||||
let mut intraday_report =
|
||||
@@ -888,10 +1012,11 @@ where
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&portfolio,
|
||||
&mut portfolio,
|
||||
&tick_open_orders,
|
||||
&mut process_events,
|
||||
&mut tick_decision,
|
||||
&mut directive_report,
|
||||
)?;
|
||||
let mut tick_report = self.broker.execute_between(
|
||||
execution_date,
|
||||
@@ -1024,10 +1149,11 @@ where
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&portfolio,
|
||||
&mut portfolio,
|
||||
&post_trade_open_orders,
|
||||
&mut process_events,
|
||||
&mut after_trading_decision,
|
||||
&mut directive_report,
|
||||
)?;
|
||||
let mut close_report = self.broker.after_trading(execution_date);
|
||||
publish_process_events(
|
||||
@@ -1151,10 +1277,11 @@ where
|
||||
execution_date,
|
||||
decision_date,
|
||||
decision_index,
|
||||
&portfolio,
|
||||
&mut portfolio,
|
||||
&post_close_open_orders,
|
||||
&mut process_events,
|
||||
&mut settlement_decision,
|
||||
&mut directive_report,
|
||||
)?;
|
||||
publish_phase_event(
|
||||
&mut self.strategy,
|
||||
@@ -1172,6 +1299,7 @@ where
|
||||
ProcessEventKind::PostSettlement,
|
||||
"settlement:post",
|
||||
)?;
|
||||
merge_broker_report(&mut report, directive_report);
|
||||
let daily_fill_count = report.fill_events.len();
|
||||
let day_orders = report.order_events.clone();
|
||||
let day_fills = report.fill_events.clone();
|
||||
@@ -1542,6 +1670,31 @@ where
|
||||
Ok(report)
|
||||
}
|
||||
|
||||
fn settle_pending_cash_flows(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
portfolio: &mut PortfolioState,
|
||||
notes: &mut Vec<String>,
|
||||
) -> BrokerExecutionReport {
|
||||
let mut report = BrokerExecutionReport::default();
|
||||
for flow in portfolio.settle_pending_cash_flows(date) {
|
||||
let cash_before = portfolio.cash() - flow.amount;
|
||||
let note = format!(
|
||||
"deposit_withdraw_settled amount={:.2} payable_date={} reason={}",
|
||||
flow.amount, flow.payable_date, flow.reason
|
||||
);
|
||||
notes.push(note.clone());
|
||||
report.account_events.push(AccountEvent {
|
||||
date,
|
||||
cash_before,
|
||||
cash_after: portfolio.cash(),
|
||||
total_equity: portfolio.total_equity(),
|
||||
note,
|
||||
});
|
||||
}
|
||||
report
|
||||
}
|
||||
|
||||
fn settle_delisted_positions(
|
||||
&self,
|
||||
date: NaiveDate,
|
||||
|
||||
Reference in New Issue
Block a user