38 lines
1.7 KiB
Python
38 lines
1.7 KiB
Python
from __future__ import annotations
|
|
import os, json
|
|
import pandas as pd
|
|
from typing import List
|
|
from metrics import portfolio_metrics
|
|
|
|
def ensure_dirs(root_dir: str, tickers: List[str]):
|
|
os.makedirs(root_dir, exist_ok=True)
|
|
for tk in tickers:
|
|
os.makedirs(os.path.join(root_dir, tk), exist_ok=True)
|
|
|
|
def save_outputs(root_dir: str, tickers: List[str], trades: pd.DataFrame, portfolio_eq: pd.DataFrame, cash: float):
|
|
ensure_dirs(root_dir, tickers)
|
|
|
|
if not trades.empty:
|
|
for tk, tdf in trades.groupby("ticker"):
|
|
tdf2 = tdf.copy()
|
|
tdf2["datetime"] = pd.to_datetime(tdf2["time"], unit="ms", utc=True)
|
|
tdf2.to_csv(os.path.join(root_dir, tk, f"{tk}_trades.csv"), index=False)
|
|
|
|
if not portfolio_eq.empty:
|
|
portfolio_eq["datetime"] = pd.to_datetime(portfolio_eq["time"], unit="ms", utc=True)
|
|
portfolio_eq.to_csv(os.path.join(root_dir, "portfolio_equity.csv"), index=False)
|
|
|
|
eq_series = portfolio_eq.set_index("time")["equity"] if not portfolio_eq.empty else pd.Series(dtype=float)
|
|
metrics = portfolio_metrics(eq_series) if not eq_series.empty else {"max_dd":0.0,"sharpe":0.0,"cagr":0.0}
|
|
|
|
summary = {
|
|
"ending_cash": round(cash, 2),
|
|
"n_open_positions": int(trades[trades["action"]=="OPEN"]["ticker"].nunique()) if not trades.empty else 0,
|
|
"n_trades_closed": int((trades["action"]=="CLOSE").sum()) if not trades.empty else 0,
|
|
"max_drawdown": metrics["max_dd"],
|
|
"sharpe_annualized": metrics["sharpe"],
|
|
"cagr": metrics["cagr"],
|
|
}
|
|
with open(os.path.join(root_dir, "portfolio_summary.txt"), "w", encoding="utf-8") as f:
|
|
json.dump(summary, f, indent=2, ensure_ascii=False)
|