Skip to content

Commit cfb6e51

Browse files
committed
fix: current_trades now includes recently exited trades
Use exit_sig_date == weight_date instead of exit_date == last_price_date to match Finlab's current_trades behavior
1 parent 8dbde14 commit cfb6e51

2 files changed

Lines changed: 44 additions & 13 deletions

File tree

polars_backtest/src/report.rs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -334,25 +334,56 @@ impl PyBacktestReport {
334334
Ok(PyDataFrame(pivoted))
335335
}
336336

337-
/// Get current trades (active positions)
337+
/// Get current trades (active positions and recent actions)
338+
///
339+
/// Returns trades that are relevant to the current portfolio state:
340+
/// 1. Open positions: entry_date is not null AND exit_date is null
341+
/// 2. Recently exited: exit_sig_date == weight_date (exited on last rebalance)
342+
/// 3. Pending entries: entry_date is null (will enter on next trading day)
338343
fn current_trades(&self) -> PyResult<PyDataFrame> {
339344
let trades = &self.trades_df;
340345
if trades.height() == 0 {
341346
return Ok(PyDataFrame(trades.clone()));
342347
}
343348

344-
// Get last date from creturn
345-
let last_date = self.get_last_date_expr()?;
349+
// Get weight_date from weights_df (the last rebalance signal date)
350+
let weight_date = if let Some(weights) = &self.weights_df {
351+
if weights.height() > 0 {
352+
let date_col = weights.column("date")
353+
.map_err(to_py_err)?
354+
.date()
355+
.map_err(to_py_err)?;
356+
date_col.phys.get(0)
357+
} else {
358+
None
359+
}
360+
} else {
361+
None
362+
};
346363

347-
let current = trades
348-
.clone()
349-
.lazy()
350-
.filter(
351-
col("exit_date").is_null()
352-
.or(col("exit_date").eq(lit(last_date)))
353-
)
354-
.collect()
355-
.map_err(to_py_err)?;
364+
// Filter trades based on current portfolio relevance
365+
let current = if let Some(w_date) = weight_date {
366+
// Include:
367+
// 1. exit_date is null (open positions + pending entries)
368+
// 2. exit_sig_date == weight_date (recently exited on last rebalance)
369+
trades
370+
.clone()
371+
.lazy()
372+
.filter(
373+
col("exit_date").is_null()
374+
.or(col("exit_sig_date").eq(lit(w_date)))
375+
)
376+
.collect()
377+
.map_err(to_py_err)?
378+
} else {
379+
// Fallback: just use exit_date is null
380+
trades
381+
.clone()
382+
.lazy()
383+
.filter(col("exit_date").is_null())
384+
.collect()
385+
.map_err(to_py_err)?
386+
};
356387

357388
Ok(PyDataFrame(current))
358389
}

polars_backtest/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)