book: scaffold + ch00 (execution trail as spine) — evidence exp 8-31, round 3
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
"""Minimal beta-neutral 3L/3S strategy + record — stub of tac_qlib/contrib/strategy/beta_neutral.py.
|
||||
|
||||
Strategy side: subclass BaseSignalStrategy, hold ~3 long + 3 short equally
|
||||
weighted (dollar-neutral) with TP/SL and a hard close at the horizon. The beta
|
||||
comes from regression of daily returns on the benchmark in `_prepare_betas`.
|
||||
|
||||
Record side (BetaNeutralRecord): a custom `Record` that simulates the 3L/3S
|
||||
portfolio after training and logs report / trades / risk.csv into the MLflow
|
||||
run — the pattern to follow for any custom Record.
|
||||
|
||||
Wire the record into the workflow YAML:
|
||||
|
||||
record:
|
||||
- class: BetaNeutralRecord
|
||||
module_path: tac_qlib.contrib.strategy.beta_neutral
|
||||
kwargs: { benchmark: QQQ, n_long: 3, n_short: 3 }
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Dict, List
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from qlib.backtest import Order
|
||||
from qlib.backtest.decision import OrderDir, TradeDecisionWO
|
||||
from qlib.contrib.strategy.signal_strategy import BaseSignalStrategy
|
||||
|
||||
|
||||
class BetaNeutralStrategy(BaseSignalStrategy):
|
||||
"""3 long / 3 short dollar-neutral template with TP/SL and hard close."""
|
||||
|
||||
def __init__(self, *, n_long: int = 3, n_short: int = 3, tp: float = 0.06, sl: float = -0.05, **kwargs: Any):
|
||||
super().__init__(**kwargs)
|
||||
self.n_long = n_long
|
||||
self.n_short = n_short
|
||||
self.tp = tp
|
||||
self.sl = sl
|
||||
|
||||
def generate_trade_decision(self, execute_result=None):
|
||||
trade_step = self.trade_calendar.get_trade_step()
|
||||
start_time, end_time = self.trade_calendar.get_step_time(trade_step)
|
||||
pred_start, pred_end = self.trade_calendar.get_step_time(trade_step - 1)
|
||||
pred = self.signal.get_signal(start_time=pred_start, end_time=pred_end)
|
||||
|
||||
orders: List[Order] = []
|
||||
if pred is not None and len(pred):
|
||||
daily = pred.groupby(level=0).mean().iloc[-1].dropna().sort_values()
|
||||
longs = daily.tail(self.n_long).index.tolist()
|
||||
shorts = daily.head(self.n_short).index.tolist()
|
||||
for inst in longs:
|
||||
orders.append(self._order(inst, 1, start_time, end_time))
|
||||
for inst in shorts:
|
||||
orders.append(self._order(inst, -1, start_time, end_time))
|
||||
return TradeDecisionWO(orders, self)
|
||||
|
||||
def _order(self, inst, direction, start_time, end_time):
|
||||
price = self.trade_exchange.get_close(inst, end_time) or 1.0
|
||||
qty = int(self.trade_exchange.account.cash / (len(self.trade_exchange.get_positions()) + 1) / price)
|
||||
return Order(
|
||||
inst,
|
||||
qty,
|
||||
start_time,
|
||||
end_time,
|
||||
direction=OrderDir.BUY if direction > 0 else OrderDir.SELL,
|
||||
type="market",
|
||||
)
|
||||
|
||||
|
||||
class BetaNeutralRecord: # subclass qlib.workflow.record_temp.Record in the real impl
|
||||
"""Custom record that backtests 3L/3S and logs report/trades/risk.csv."""
|
||||
|
||||
def __init__(self, *, benchmark: str = "QQQ", n_long: int = 3, n_short: int = 3, **_: Any):
|
||||
self.benchmark = benchmark
|
||||
self.n_long = n_long
|
||||
self.n_short = n_short
|
||||
|
||||
def generate(self, **kwargs):
|
||||
# Real impl: run qlib.backtest with BetaNeutralStrategy on the recorded
|
||||
# pred, write report_normal.csv / positions_normal.csv / risk.csv into
|
||||
# the current MLflow run's artifact dir, then log the headline metrics.
|
||||
print("BetaNeutralRecord.generate: simulate 3L/3S and log artifacts")
|
||||
Reference in New Issue
Block a user