feat: dump alpha values + daily PnL as parquet (pyarrow)
This commit is contained in:
@@ -122,6 +122,51 @@ def plot_ic(signal_eval: dict, output_path: str = "reports/ic.png") -> str:
|
|||||||
return output_path
|
return output_path
|
||||||
|
|
||||||
|
|
||||||
|
def dump_signals(signals_df: pd.DataFrame, output_dir: str = "results/") -> str:
|
||||||
|
"""Save the signal matrix (date x stock) as a parquet file.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
signals_df: Date-indexed DataFrame of per-stock signal values.
|
||||||
|
output_dir: Directory to write the parquet file into.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path the parquet file was written to.
|
||||||
|
"""
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
path = os.path.join(output_dir, "signals.parquet")
|
||||||
|
signals_df.to_parquet(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
def dump_daily_pnl(
|
||||||
|
results: list, output_dir: str = "results/", initial_cash: float = 1_000_000.0
|
||||||
|
) -> str:
|
||||||
|
"""Extract the daily portfolio value from a backtest run and save as parquet.
|
||||||
|
|
||||||
|
Compounds the per-day TimeReturn analyzer into an equity curve.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
results: The list returned by ``cerebro.run()``.
|
||||||
|
output_dir: Directory to write the parquet file into.
|
||||||
|
initial_cash: Starting portfolio value for scaling the curve.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The path the parquet file was written to.
|
||||||
|
"""
|
||||||
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
series = pd.Series(dtype=float)
|
||||||
|
if results:
|
||||||
|
tr = results[0].analyzers.timereturn.get_analysis()
|
||||||
|
series = pd.Series(tr).sort_index()
|
||||||
|
|
||||||
|
equity = (1.0 + series).cumprod() * initial_cash
|
||||||
|
pnl_df = pd.DataFrame({"date": equity.index, "value": equity.values})
|
||||||
|
|
||||||
|
path = os.path.join(output_dir, "daily_pnl.parquet")
|
||||||
|
pnl_df.to_parquet(path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
def generate_report(
|
def generate_report(
|
||||||
results: list,
|
results: list,
|
||||||
signal_eval: dict,
|
signal_eval: dict,
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+9
-3
@@ -11,7 +11,7 @@ import logging
|
|||||||
|
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from analysis.report import generate_report
|
from analysis.report import dump_daily_pnl, dump_signals, generate_report
|
||||||
from backtest.config import BacktestConfig
|
from backtest.config import BacktestConfig
|
||||||
from backtest.runner import BacktestRunner
|
from backtest.runner import BacktestRunner
|
||||||
from data.downloader import download_batch
|
from data.downloader import download_batch
|
||||||
@@ -36,7 +36,8 @@ def _forward_returns(data: dict[str, pd.DataFrame], horizon: int) -> pd.DataFram
|
|||||||
return pd.DataFrame(forward_returns)
|
return pd.DataFrame(forward_returns)
|
||||||
|
|
||||||
|
|
||||||
def main(forward_horizon: int = 5, universe: str = "csi500", signal_name: str = "reversal_vol"):
|
def main(forward_horizon: int = 5, universe: str = "csi500", signal_name: str = "reversal_vol",
|
||||||
|
dump_dir: str = "results/"):
|
||||||
universes = {"hs300": SYMBOLS, "csi500": CSI500_SYMBOLS}
|
universes = {"hs300": SYMBOLS, "csi500": CSI500_SYMBOLS}
|
||||||
symbols = universes.get(universe, CSI500_SYMBOLS)[:30]
|
symbols = universes.get(universe, CSI500_SYMBOLS)[:30]
|
||||||
|
|
||||||
@@ -97,6 +98,10 @@ def main(forward_horizon: int = 5, universe: str = "csi500", signal_name: str =
|
|||||||
results, signal_eval, output_dir="reports/", initial_cash=initial_cash
|
results, signal_eval, output_dir="reports/", initial_cash=initial_cash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 7b. Dump signals and daily PnL.
|
||||||
|
dump_signals(signals_df, dump_dir)
|
||||||
|
dump_daily_pnl(results, dump_dir, initial_cash=initial_cash)
|
||||||
|
|
||||||
# 8. Print summary.
|
# 8. Print summary.
|
||||||
print("\nSIGNAL IC")
|
print("\nSIGNAL IC")
|
||||||
print("=" * 50)
|
print("=" * 50)
|
||||||
@@ -122,5 +127,6 @@ if __name__ == "__main__":
|
|||||||
parser = argparse.ArgumentParser(description="Chinese equity quant backtest")
|
parser = argparse.ArgumentParser(description="Chinese equity quant backtest")
|
||||||
parser.add_argument("--universe", default="csi500", choices=["hs300", "csi500"])
|
parser.add_argument("--universe", default="csi500", choices=["hs300", "csi500"])
|
||||||
parser.add_argument("--signal", default="reversal_vol", choices=["reversal", "reversal_vol"])
|
parser.add_argument("--signal", default="reversal_vol", choices=["reversal", "reversal_vol"])
|
||||||
|
parser.add_argument("--dump-dir", default="results/")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
main(universe=args.universe, signal_name=args.signal)
|
main(universe=args.universe, signal_name=args.signal, dump_dir=args.dump_dir)
|
||||||
|
|||||||
Reference in New Issue
Block a user