Add JoinQuant comparison plugin
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
"""CLI commands for the JoinQuant comparison plugin."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
|
||||
from plugins.joinquant.export_targets import export_targets
|
||||
from plugins.joinquant.ingest import ingest_joinquant_outputs
|
||||
from plugins.joinquant.reconcile import reconcile_joinquant
|
||||
from plugins.joinquant.wrapper_strategy import write_wrapper_strategy
|
||||
|
||||
|
||||
@click.group(name="joinquant")
|
||||
def joinquant():
|
||||
"""Compare internal portfolio simulation with JoinQuant output."""
|
||||
|
||||
|
||||
@joinquant.command("export-targets")
|
||||
@click.option("--positions-path", required=True, help="Portfolio positions parquet from `portfolio build`")
|
||||
@click.option("--portfolio-name", required=True, help="Portfolio run to export")
|
||||
@click.option(
|
||||
"--mode",
|
||||
"mode",
|
||||
type=click.Choice(["target_shares", "target_value"]),
|
||||
default="target_shares",
|
||||
show_default=True,
|
||||
help="JoinQuant target order mode",
|
||||
)
|
||||
@click.option("--start-date", default=None, help="Inclusive YYYY-MM-DD start date")
|
||||
@click.option("--end-date", default=None, help="Inclusive YYYY-MM-DD end date")
|
||||
@click.option("--out-dir", default="plugins_output/joinquant/targets", show_default=True)
|
||||
@click.option("--force", is_flag=True, help="Overwrite frozen target/snapshot files")
|
||||
def export_targets_cmd(positions_path, portfolio_name, mode, start_date, end_date, out_dir, force):
|
||||
"""Export frozen daily target files for JoinQuant."""
|
||||
snapshots = export_targets(
|
||||
positions_path=positions_path,
|
||||
portfolio_name=portfolio_name,
|
||||
mode=mode,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
out_dir=out_dir,
|
||||
force=force,
|
||||
)
|
||||
click.echo(f"Exported JoinQuant targets: {len(snapshots)} day(s)")
|
||||
for snapshot in snapshots:
|
||||
click.echo(
|
||||
f" {snapshot['date']}: {snapshot['n_symbols']} symbols, "
|
||||
f"sha256={str(snapshot['file_sha256'])[:12]}"
|
||||
)
|
||||
|
||||
|
||||
@joinquant.command("ingest")
|
||||
@click.option("--portfolio-name", required=True, help="Portfolio run name")
|
||||
@click.option("--fills-csv", required=True, help="JoinQuant fills CSV")
|
||||
@click.option("--positions-csv", required=True, help="JoinQuant positions CSV")
|
||||
@click.option("--pnl-csv", required=True, help="JoinQuant daily PnL CSV")
|
||||
@click.option("--out-dir", default="plugins_output/joinquant/ingested", show_default=True)
|
||||
def ingest_cmd(portfolio_name, fills_csv, positions_csv, pnl_csv, out_dir):
|
||||
"""Normalize JoinQuant CSV exports to parquet."""
|
||||
paths = ingest_joinquant_outputs(
|
||||
portfolio_name=portfolio_name,
|
||||
fills_csv=fills_csv,
|
||||
positions_csv=positions_csv,
|
||||
pnl_csv=pnl_csv,
|
||||
out_dir=out_dir,
|
||||
)
|
||||
click.echo(f"Saved JoinQuant fills: {paths['fills']}")
|
||||
click.echo(f"Saved JoinQuant positions: {paths['positions']}")
|
||||
click.echo(f"Saved JoinQuant pnl: {paths['pnl']}")
|
||||
|
||||
|
||||
@joinquant.command("reconcile")
|
||||
@click.option("--portfolio-name", required=True, help="Portfolio run name")
|
||||
@click.option("--targets-dir", required=True, help="Directory containing exported daily target files")
|
||||
@click.option("--our-fills-path", required=True, help="Internal simulator fills parquet")
|
||||
@click.option("--our-positions-path", required=True, help="Internal portfolio positions parquet")
|
||||
@click.option("--our-pnl-path", required=True, help="Internal simulator PnL parquet")
|
||||
@click.option("--jq-fills-path", required=True, help="Normalized JoinQuant fills parquet")
|
||||
@click.option("--jq-positions-path", required=True, help="Normalized JoinQuant positions parquet")
|
||||
@click.option("--jq-pnl-path", required=True, help="Normalized JoinQuant PnL parquet")
|
||||
@click.option("--out-dir", default="plugins_output/joinquant/reconcile", show_default=True)
|
||||
@click.option("--share-tolerance", default=0.0, show_default=True, type=float)
|
||||
@click.option("--price-rel-tolerance", default=1e-4, show_default=True, type=float)
|
||||
@click.option("--pnl-tolerance", default=1.0, show_default=True, type=float)
|
||||
@click.option("--booksize", default=None, type=float, help="Booksize for value tolerance inference")
|
||||
def reconcile_cmd(
|
||||
portfolio_name,
|
||||
targets_dir,
|
||||
our_fills_path,
|
||||
our_positions_path,
|
||||
our_pnl_path,
|
||||
jq_fills_path,
|
||||
jq_positions_path,
|
||||
jq_pnl_path,
|
||||
out_dir,
|
||||
share_tolerance,
|
||||
price_rel_tolerance,
|
||||
pnl_tolerance,
|
||||
booksize,
|
||||
):
|
||||
"""Write per-symbol and daily JoinQuant reconciliation reports."""
|
||||
paths = reconcile_joinquant(
|
||||
portfolio_name=portfolio_name,
|
||||
targets_dir=targets_dir,
|
||||
our_fills_path=our_fills_path,
|
||||
our_positions_path=our_positions_path,
|
||||
our_pnl_path=our_pnl_path,
|
||||
jq_fills_path=jq_fills_path,
|
||||
jq_positions_path=jq_positions_path,
|
||||
jq_pnl_path=jq_pnl_path,
|
||||
out_dir=out_dir,
|
||||
share_tolerance=share_tolerance,
|
||||
price_rel_tolerance=price_rel_tolerance,
|
||||
pnl_tolerance=pnl_tolerance,
|
||||
booksize=booksize,
|
||||
)
|
||||
click.echo(f"Saved reconciliation parquet: {paths['daily_reconcile']}")
|
||||
click.echo(f"Saved reconciliation summary: {paths['summary_md']}")
|
||||
click.echo(f"Saved reconciliation CSV: {paths['summary_csv']}")
|
||||
|
||||
|
||||
@joinquant.command("write-wrapper")
|
||||
@click.option("--portfolio-name", required=True, help="Portfolio run name")
|
||||
@click.option(
|
||||
"--mode",
|
||||
"mode",
|
||||
type=click.Choice(["target_shares", "target_value"]),
|
||||
default="target_shares",
|
||||
show_default=True,
|
||||
)
|
||||
@click.option("--out-path", required=True, help="Path for generated standalone strategy")
|
||||
@click.option("--allow-short", is_flag=True, help="Do not clip negative targets in the generated wrapper")
|
||||
def write_wrapper_cmd(portfolio_name, mode, out_path, allow_short):
|
||||
"""Generate a standalone JoinQuant wrapper strategy."""
|
||||
path = write_wrapper_strategy(
|
||||
portfolio_name=portfolio_name,
|
||||
mode=mode,
|
||||
out_path=out_path,
|
||||
allow_short=allow_short,
|
||||
)
|
||||
click.echo(f"Saved JoinQuant wrapper strategy: {path}")
|
||||
|
||||
Reference in New Issue
Block a user