diff --git a/scripts/research/trackD_timing.py b/scripts/research/trackD_timing.py index e39feba..4b67568 100644 --- a/scripts/research/trackD_timing.py +++ b/scripts/research/trackD_timing.py @@ -77,7 +77,9 @@ def run_asset(df, bars_per_day, target_vol=TARGET_VOL, leverage=LEVERAGE, direction = sig_tsmom_blend(c, horizons=(h1, h3, h6)) tgt = build_target(direction, vol, target_vol, leverage, long_only) equity, net = equity_from_target(tgt, r, fee_side) - return dict(net=net, ts=df["datetime"], equity=equity, bpy=bpy) + # discrete position SIGN for trade counting (entry = sign change to a new non-zero state) + sign = np.sign(tgt) + return dict(net=net, ts=df["datetime"], equity=equity, bpy=bpy, sign=sign, target=tgt) def portfolio_series(sleeves): @@ -118,6 +120,20 @@ def per_year(idx, equity): return out +def trades_per_year(sleeves): + """Count entries per year, summed across both sleeves. An 'entry' = the position SIGN + changing to a new non-zero value (flat->long, flat->short, or a direction flip).""" + counts: dict[int, int] = {} + for a in ASSETS: + sign = sleeves[a]["sign"] + ts = pd.to_datetime(sleeves[a]["ts"].values) + for i in range(1, len(sign)): + s, prev = sign[i], sign[i - 1] + if s != 0 and s != prev: # entry: from flat or opposite into a non-zero state + counts[ts[i].year] = counts.get(ts[i].year, 0) + 1 + return counts + + ALL_YEARS = list(range(2018, 2027)) @@ -139,14 +155,18 @@ def main(): ov = overall_metrics(idx, combo, equity, sleeves["BTC"]["bpy"]) py = per_year(idx, equity) + tpy = trades_per_year(sleeves) + total_trades = sum(tpy.values()) print(f"\n ── TF {tf_key:<3s} │ ret {ov['total']*100:>+8.0f}% CAGR {ov['cagr']*100:>+6.1f}% " f"Sharpe {ov['sharpe']:>4.2f} maxDD {ov['max_dd']*100:>4.1f}% " - f"€/day(2k) {ov['daily_2k']:>+6.2f}") - # per-year PnL row + f"€/day(2k) {ov['daily_2k']:>+6.2f} trades {total_trades}") + # per-year PnL / DD / trades rows print(f" {'PnL %':<8s}" + "".join( (" . " if y not in py else f"{py[y][0]*100:>+7.0f}") for y in ALL_YEARS)) print(f" {'maxDD %':<8s}" + "".join( (" . " if y not in py else f"{py[y][1]*100:>7.1f}") for y in ALL_YEARS)) + print(f" {'trades':<8s}" + "".join( + (" . " if y not in py else f"{tpy.get(y,0):>7d}") for y in ALL_YEARS)) # year header for reference print("\n " + "year ".ljust(8) + "".join(f"{y:>7d}" for y in ALL_YEARS))