From 1caa63faeb1d7eaae9ab7dcabd8027bcc265705f Mon Sep 17 00:00:00 2001 From: Yuxuan Yan Date: Tue, 9 Jun 2026 14:07:07 +0800 Subject: [PATCH] refactor: class-based alpha factory + month-partitioned data pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the old signal/strategy/backtest modules with a decoupled data → alpha → combo pipeline (parquet between phases, .pq extension). Alphas: - BaseAlpha + @register_alpha factory/plugin registry; one file per built-in (reversal, reversal_vol, momentum); external alphas via --alpha-module. Alphas are z-scored position weights, not predictors. Data: - baostock primary / akshare fallback, treated consistently. - New --universe all (~5000 A-shares via query_all_stock, filtered). - login-once batch downloader; empty-string OHLCV coerced to NaN. - Month-partitioned dataset {output_dir}/{universe}/month=YYYY-MM/*.pq with chunked durability flushes; --data-path is the dataset dir. CLI logs at INFO by default (--log-level) so progress is visible. Docs (README, CLAUDE.md) updated incl. pipeline diagram and roadmap TODOs for portfolio construction / backtest / paper trading. Co-Authored-By: Claude Opus 4.7 --- .gitignore | 6 + CLAUDE.md | 89 +++++-- README.md | 256 ++++++++++++++++++++- analysis/report.py | 206 ----------------- backtest/config.py | 14 -- backtest/feed.py | 35 --- backtest/runner.py | 84 ------- cli.py | 43 ++++ data/downloader.py | 108 ++++++--- data/schema.py | 44 ---- data/universe.py | 73 +++--- eval/__init__.py | 4 - eval/metrics.py | 90 -------- {analysis => pipeline}/__init__.py | 0 {backtest => pipeline/alpha}/__init__.py | 0 pipeline/alpha/base.py | 57 +++++ pipeline/alpha/cli.py | 174 ++++++++++++++ pipeline/alpha/compute.py | 153 ++++++++++++ pipeline/alpha/library/__init__.py | 7 + pipeline/alpha/library/momentum.py | 18 ++ pipeline/alpha/library/reversal.py | 18 ++ pipeline/alpha/library/reversal_vol.py | 26 +++ pipeline/alpha/registry.py | 102 ++++++++ {strategies => pipeline/combo}/__init__.py | 0 pipeline/combo/cli.py | 47 ++++ pipeline/combo/combine.py | 85 +++++++ pipeline/common/__init__.py | 0 pipeline/common/schema.py | 33 +++ pipeline/data/__init__.py | 0 pipeline/data/cli.py | 44 ++++ pipeline/data/downloader.py | 179 ++++++++++++++ portfolio/__init__.py | 4 - portfolio/builder.py | 69 ------ reports/ic.png | Bin 41067 -> 0 bytes reports/pnl.png | Bin 60056 -> 0 bytes reports/summary.txt | 21 -- requirements.txt | 2 + results/daily_pnl.parquet | Bin 10443 -> 0 bytes results/signals.parquet | Bin 148034 -> 0 bytes run_example.py | 132 ----------- signals/__init__.py | 6 - signals/base.py | 28 --- signals/momentum.py | 21 -- signals/reversal.py | 22 -- signals/reversal_vol.py | 27 --- strategies/alpha_strategy.py | 52 ----- strategies/base.py | 23 -- strategies/reversal.py | 27 --- tests/test_alpha.py | 180 +++++++++++++++ tests/test_downloader.py | 18 +- tests/test_eval.py | 53 ----- tests/test_reversal.py | 21 -- tests/test_runner.py | 21 -- tests/test_signals.py | 38 --- 54 files changed, 1640 insertions(+), 1120 deletions(-) delete mode 100644 analysis/report.py delete mode 100644 backtest/config.py delete mode 100644 backtest/feed.py delete mode 100644 backtest/runner.py create mode 100644 cli.py delete mode 100644 data/schema.py delete mode 100644 eval/__init__.py delete mode 100644 eval/metrics.py rename {analysis => pipeline}/__init__.py (100%) rename {backtest => pipeline/alpha}/__init__.py (100%) create mode 100644 pipeline/alpha/base.py create mode 100644 pipeline/alpha/cli.py create mode 100644 pipeline/alpha/compute.py create mode 100644 pipeline/alpha/library/__init__.py create mode 100644 pipeline/alpha/library/momentum.py create mode 100644 pipeline/alpha/library/reversal.py create mode 100644 pipeline/alpha/library/reversal_vol.py create mode 100644 pipeline/alpha/registry.py rename {strategies => pipeline/combo}/__init__.py (100%) create mode 100644 pipeline/combo/cli.py create mode 100644 pipeline/combo/combine.py create mode 100644 pipeline/common/__init__.py create mode 100644 pipeline/common/schema.py create mode 100644 pipeline/data/__init__.py create mode 100644 pipeline/data/cli.py create mode 100644 pipeline/data/downloader.py delete mode 100644 portfolio/__init__.py delete mode 100644 portfolio/builder.py delete mode 100644 reports/ic.png delete mode 100644 reports/pnl.png delete mode 100644 reports/summary.txt delete mode 100644 results/daily_pnl.parquet delete mode 100644 results/signals.parquet delete mode 100644 run_example.py delete mode 100644 signals/__init__.py delete mode 100644 signals/base.py delete mode 100644 signals/momentum.py delete mode 100644 signals/reversal.py delete mode 100644 signals/reversal_vol.py delete mode 100644 strategies/alpha_strategy.py delete mode 100644 strategies/base.py delete mode 100644 strategies/reversal.py create mode 100644 tests/test_alpha.py delete mode 100644 tests/test_eval.py delete mode 100644 tests/test_reversal.py delete mode 100644 tests/test_runner.py delete mode 100644 tests/test_signals.py diff --git a/.gitignore b/.gitignore index 8a4864c..10c5dd1 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,9 @@ __pycache__/ *.egg-info/ .venv/ venv/ + +# Pipeline outputs (regenerated by the CLI; can be large) +data/daily_bars/ +alphas/ +combos/ +reports/ diff --git a/CLAUDE.md b/CLAUDE.md index 9a2d5f4..4071d0f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,16 +1,79 @@ -# Chinese Equity Quant Research Framework +# CLAUDE.md -## Architecture -- backtrader is the backtesting engine — never reimplement backtest logic -- akshare primary data source, baostock secondary fallback -- Daily frequency only (Phase 1) +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Key Commands -- `python3 run_example.py` — smoke test -- `python3 -m pytest tests/ -v` — run tests -- `pip install -r requirements.txt` — install deps +A modular Chinese A-share quant research framework. Daily frequency only (Phase 1). -## Code Standards -- Type hints on public functions -- Google-style docstrings -- 4-space indentation for Python +## Commands + +```bash +pip install -r requirements.txt # install deps +python3 -m pytest tests/ -v # all tests +python3 -m pytest tests/test_alpha.py -v # single file (test_alpha is network-free) +python3 -m pytest tests/test_alpha.py::test_evaluate_alpha_keys -v # single test + +# Pipeline — each phase is independent: reads parquet, writes parquet. +python3 cli.py data download --universe csi500 --start-date 2017-01-01 # → data/daily_bars/csi500/ (month-partitioned) +python3 cli.py alpha reversal --data-path data/daily_bars/ # --data-path is the dataset DIR +python3 cli.py alpha eval --alpha-path alphas/.pq --data-path data/daily_bars/ +python3 cli.py combo combine --alpha-paths a.pq,b.pq --combo-name eq --method equal_weight +``` + +Note: `tests/test_downloader.py` hits the network (live baostock/akshare); `tests/test_alpha.py` is pure and fast. + +## Architecture: one decoupled pipeline + +The system is a phase-based CLI (`cli.py` + `pipeline/`). Each phase communicates **only** through parquet files on disk, so phases can be run, cached, and inspected independently: + +``` +data → alpha → combo +``` + +- `pipeline/data/` — download daily bars for a universe → `data/daily_bars/{universe}/month=YYYY-MM/*.pq` (Hive-partitioned dataset; pass the `{universe}` dir as `--data-path`) +- `pipeline/alpha/` — compute one alpha's position weights from a data parquet → `alphas/*.pq`, and `alpha eval` to score it +- `pipeline/combo/` — combine several alpha parquets into one → `combos/*.pq` + +The pipeline reuses two top-level helper modules via a `sys.path.insert` at the top of `pipeline/data/downloader.py`: `data/downloader.py` (network download) and `data/universe.py` (constituent lists). This path hack is load-bearing — keep it. + +## Alphas are weights, not predictors + +An **alpha** is a signed cross-sectional **position weight**: positive = long, negative = short. It is produced by applying a formula to the wide close matrix, then **cross-sectional z-scoring** per date (`compute_alpha` in `pipeline/alpha/compute.py`). Alphas are evaluated by **return / Sharpe / turnover / max-drawdown** in `evaluate_alpha` — interpreting the weight series as a portfolio. There is deliberately **no IC/IR** anywhere: those frame a signal as a return *predictor*, which this codebase does not do. Do not reintroduce IC-style evaluation. + +## Parquet schema contracts + +`pipeline/common/schema.py` defines the column contracts that are the *only* interface between phases. Any new phase or alpha must conform: +- `DATA_COLUMNS` (data output): `symbol_id, symbol_name, date, open, high, low, close, volume, amount` +- `ALPHA_COLUMNS` (alpha output): `symbol_id, date, alpha_name, weight` +- `COMBO_COLUMNS` (combo output): `symbol_id, date, combo_name, weight` + +Data is stored **long/tidy**, not wide, as a Hive-partitioned dataset keyed by `month=YYYY-MM` (so reads of the dataset directory carry an extra `month` partition column, which `_pivot_close` ignores). Compute code pivots to wide (date index × symbol_id columns) internally via `_pivot_close`, where all formulas are vectorized column-wise. + +## Alphas: factory + plugin pattern + +Each alpha is a class subclassing `BaseAlpha` (`pipeline/alpha/base.py`), living in its own module. It implements `signal(close) -> wide DataFrame` (the raw score); the base class's `to_weights` cross-sectionally z-scores that into position weights (override for custom normalization). Subclasses declare their own typed `__init__` params (e.g. `lookback`, `vol_window`, or anything custom). + +- Built-in alphas: one file each under `pipeline/alpha/library/` (`reversal.py`, `reversal_vol.py`, `momentum.py`). Each uses the `@register_alpha` decorator and is imported by `library/__init__.py` so it self-registers. **Add a built-in** by dropping a module there + adding it to that `__init__`. +- Factory: `pipeline/alpha/registry.py` — `register_alpha`, `get_alpha(name, **params)` (forwards only the params the alpha's `__init__` accepts, via signature inspection), `available_alphas()`, and `load_alpha_module(spec)`. +- **External alphas** (authored outside this repo) are the point of the design: write a `@register_alpha class MyAlpha(BaseAlpha)` in any `.py` file, then register it at runtime with `--alpha-module path/to/file.py` (or a dotted module path). See `examples/alphas/mean_reversion.py` for a working example, and `tests/test_alpha.py::test_load_external_alpha_module`. + +```bash +python3 cli.py alpha list # registered alpha types +python3 cli.py alpha list --alpha-module my_alpha.py # incl. an external one +python3 cli.py alpha compute --alpha-module my_alpha.py \ + --alpha-type my_alpha --alpha-name run1 --param decay=0.9 --data-path .pq +``` + +`compute_alpha(data, alpha_name, alpha_type, **params)` in `pipeline/alpha/compute.py` resolves the class via `get_alpha`, applies `.weights()`, and melts to `ALPHA_COLUMNS`. `--lookback`/`--vol-window` are passed as params for convenience; arbitrary params go through repeatable `--param name=value`. `evaluate_alpha` (return/Sharpe/turnover, no IC) is unchanged. + +Combo methods are still a plain dict registry: `COMBO_METHODS` in `pipeline/combo/combine.py`. + +## Data sources & symbol conventions + +- **baostock is the primary source, akshare the fallback** (`data/downloader.py`, `download_daily(source="auto")` tries baostock first). This ordering is intentional — akshare is less reliable on the deployment network. +- Internal symbol format is `sh600000` / `sz000001` (exchange prefix + code). baostock uses the dotted form `sh.600000`; akshare's `stock_zh_a_hist` wants the bare code (prefix stripped). Both download paths return the identical 8-column schema and map the `adjust` argument consistently (`qfq`/`hfq`/none → baostock `adjustflag` via `_BAOSTOCK_ADJUST`). +- baostock constituent queries (`get_hs300_stocks`, `get_zz500_stocks` in `data/universe.py`) return columns in an unreliable order, so `pipeline/data/downloader.py:_fix_baostock_columns` detects them by value pattern, not position. +- Universes accepted by `data download --universe`: `hs300`, `csi500`, `all`/`full` (every listed A-share, ~5000, via `get_all_stocks` → baostock `query_all_stock` filtered to SH 6xxxxx/68xxxx + SZ 0xxxxx/3xxxxx, excluding indices & B-shares), a file path (one symbol per line), or a comma-separated symbol list. Bulk downloads use `download_daily_batch` (one baostock login for the whole run) rather than per-symbol `download_daily`. + +## Code standards + +- Type hints on public functions; Google-style docstrings; 4-space indentation. diff --git a/README.md b/README.md index 70f6fd4..96fbb05 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,41 @@ # Chinese Equity Quant Research Framework -A modular Chinese A-share quant research framework built on -[backtrader](https://www.backtrader.com/) for backtesting, with -akshare (primary) and baostock (fallback) for daily bar data. +A modular Chinese A-share quant research framework. Daily frequency only (Phase 1). + +It is a **decoupled, file-based pipeline**: each phase reads parquet and writes +parquet, so phases run, cache, and inspect independently. + +``` + baostock (primary) one weight series + akshare (fallback) interpreted as a + │ portfolio + ▼ ▲ + ┌──────────────┐ ┌───────────────┐ ┌───────────────┐ │ + │ DATA │ │ ALPHA │ │ COMBO │ ┌────┴─────┐ + │ download │─────▶│ compute │─────▶│ combine │ │ EVAL │ + │ daily bars │ │ signal→weights│ │ merge alphas │ │ score it │ + └──────┬───────┘ └───────┬───────┘ └───────┬───────┘ └────┬─────┘ + │ │ │ │ + ▼ ▼ ▼ │ + data/daily_bars/ alphas/*.pq combos/*.pq │ + {universe}/ (ALPHA_COLUMNS) (COMBO_COLUMNS) │ + month=YYYY-MM/*.pq │ │ + (DATA_COLUMNS) │ │ + └──────── price ───────┴───────────────────────────────────────┘ + │ + ▼ (planned — not yet implemented) + ┌ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ ─ ─ ─ ─ ─ ┐ ┌ ─ ─ ─ ─ ─ ─ ─ ─ ┐ + PORTFOLIO BACKTEST PAPER TRADING + │ construct │ │ simulate │ │ forward / live │ TODO + positions fills + costs execution + └ ─ ─ ─ ─ ─ ─ ┘ └ ─ ─ ─ ─ ─ ─ ┘ └ ─ ─ ─ ─ ─ ─ ─ ─ ┘ + + Each phase reads parquet and writes parquet — run, cache, and inspect + independently. The only interface between phases is the parquet schema. + Solid boxes are implemented; dashed boxes are on the roadmap (see TODO below). +``` + +Data comes from **baostock (primary)** with **akshare (fallback)**. ## Install @@ -13,13 +46,218 @@ pip install -r requirements.txt ## Quick start ```bash -python3 run_example.py # end-to-end smoke test (SMA crossover on 浦发银行) -python3 -m pytest tests/ -v # run tests +# 1. Download daily bars for a few symbols (writes a month-partitioned dataset). +python3 cli.py data download \ + --universe sh600000,sz000001,sh600519 \ + --start-date 2024-01-01 --end-date 2024-03-31 \ + --output-dir data/daily_bars + +# 2. Compute an alpha (position weights) from that data. +# --data-path is the dataset DIRECTORY ({output-dir}/{universe}). +python3 cli.py alpha reversal \ + --data-path "data/daily_bars/sh600000,sz000001,sh600519" + +# 3. Evaluate it (return / Sharpe / turnover / drawdown). +python3 cli.py alpha eval \ + --alpha-path alphas/reversal_5d.pq \ + --data-path "data/daily_bars/sh600000,sz000001,sh600519" + +# Tests +python3 -m pytest tests/ -v # tests/test_alpha.py is network-free; test_downloader.py hits the network ``` +## CLI reference + +All commands are subcommands of `python3 cli.py`. Add `--help` to any of them. + +### `data download` — fetch daily bars → partitioned parquet dataset + +| Option | Default | Description | +| --- | --- | --- | +| `--universe` | `csi500` | `hs300`, `csi500`, `all` (~5000 A-shares), a file path (one symbol per line), or comma-separated symbols (`sh600000,sz000001`) | +| `--start-date` | `2017-01-01` | `YYYY-MM-DD` | +| `--end-date` | today | `YYYY-MM-DD` | +| `--output-dir` | `data/daily_bars` | Root for the dataset directory | +| `--symbols` | `0` | Max symbols to download (`0` = all) | +| `--chunk-size` | `300` | Symbols per durability flush (each flush appends `.pq` files) | +| `--adjust` | `qfq` | Price adjustment: `qfq` (forward), `hfq` (backward), `none` | + +Writes a **Hive-partitioned dataset** at `{output_dir}/{universe}/month=YYYY-MM/*.pq` +(one partition per calendar month). The `{universe}` directory is rebuilt from +scratch on each run. Downloads stream under a single baostock session and flush +every `--chunk-size` symbols, so memory stays bounded and a crash keeps the +partitions already written. Pass the **dataset directory** (`{output_dir}/{universe}`) +as `--data-path` to later phases — `pd.read_parquet` reads the whole partitioned +set. Symbols use the internal `sh600000` / `sz000001` form (exchange prefix + code). + +### `alpha list` — show registered alpha types + +```bash +python3 cli.py alpha list +python3 cli.py alpha list --alpha-module path/to/my_alpha.py # include an external alpha +``` + +### `alpha compute` — alpha class → weights parquet + +| Option | Default | Description | +| --- | --- | --- | +| `--data-path` | (required) | Data parquet from `data download` | +| `--alpha-name` | (required) | Label stored in the `alpha_name` column / output filename | +| `--alpha-type` | (required) | Registry key of the alpha class (see `alpha list`) | +| `--output-dir` | `alphas` | Output directory | +| `--lookback` | `5` | Lookback days (passed to alphas that accept it) | +| `--vol-window` | `20` | Volatility window (passed to alphas that accept it) | +| `--alpha-module` | — | External module(s) to import first; repeatable. Dotted path or `.py` file | +| `--param` | — | Extra constructor param as `name=value`; repeatable | + +Only the params an alpha's `__init__` accepts are forwarded, so passing extras +(e.g. `--vol-window` to a reversal alpha) is harmless. + +```bash +python3 cli.py alpha compute \ + --data-path .pq \ + --alpha-type reversal_vol --alpha-name rv_5_20 \ + --lookback 5 --vol-window 20 +``` + +Shortcuts for the two most common built-ins: + +```bash +python3 cli.py alpha reversal --data-path .pq --lookback 5 +python3 cli.py alpha reversal-vol --data-path .pq --lookback 5 --vol-window 20 +``` + +### `alpha eval` — score an alpha as a portfolio + +```bash +python3 cli.py alpha eval --alpha-path alphas/.pq --data-path .pq +``` + +Interprets the weights as a portfolio and reports cumulative return, annual +Sharpe, annual turnover, max drawdown, and hit rate; also dumps +`reports/_eval.json`. There is deliberately **no IC/IR** — alphas are +position weights, not return predictors. + +### `combo combine` — merge several alphas into one weight + +| Option | Default | Description | +| --- | --- | --- | +| `--alpha-paths` | (required) | Comma-separated alpha parquet paths (≥ 2) | +| `--combo-name` | (required) | Label stored in the `combo_name` column / output filename | +| `--method` | `equal_weight` | Combination method (see `COMBO_METHODS`) | +| `--output-dir` | `combos` | Output directory | + +```bash +python3 cli.py combo combine \ + --alpha-paths alphas/reversal_5d.pq,alphas/reversal_vol_5d_20d.pq \ + --combo-name eq --method equal_weight +``` + +## Alphas: the factory / plugin interface + +An **alpha** is a class that maps a wide close matrix (date index × `symbol_id` +columns) to **signed position weights** (positive = long, negative = short). +Every alpha subclasses `BaseAlpha` (`pipeline/alpha/base.py`) and is resolved by +name through the registry (`pipeline/alpha/registry.py`). + +### Minimal alpha + +```python +import pandas as pd + +from pipeline.alpha.base import BaseAlpha +from pipeline.alpha.registry import register_alpha + + +@register_alpha +class MyAlpha(BaseAlpha): + name = "my_alpha" # unique registry key (required) + + def __init__(self, lookback: int = 5): + self.lookback = lookback # declare whatever params you need + + def signal(self, close: pd.DataFrame) -> pd.DataFrame: + # Raw score: wide (date × symbol_id), higher = stronger long, NaN where undefined. + return -close.pct_change(self.lookback) +``` + +That is the whole contract: + +- `name` — the `--alpha-type` key; must be unique. +- `signal(close)` — the only required method; return a wide DataFrame. +- `to_weights(signal)` — provided by the base class: cross-sectionally z-scores + each date into weights (NaN → 0). **Override** it for a different scheme (rank, + dollar-neutral caps, etc.). + +### Built-in alphas + +One file per alpha under `pipeline/alpha/library/`: + +| `--alpha-type` | Params | Description | +| --- | --- | --- | +| `reversal` | `lookback` | Negative trailing return (oversold scores high) | +| `reversal_vol` | `lookback`, `vol_window` | Reversal scaled by trailing volatility | +| `momentum` | `lookback` | Positive trailing return | + +Add a built-in by dropping a module in `pipeline/alpha/library/` and importing it +from that package's `__init__.py`. + +### Using an alpha written outside this repo + +Write your `@register_alpha` class in any `.py` file, then register it at runtime +with `--alpha-module` (a `.py` path or an importable dotted module). See the +worked example in `examples/alphas/mean_reversion.py`: + +```bash +python3 cli.py alpha compute \ + --alpha-module examples/alphas/mean_reversion.py \ + --alpha-type mean_reversion --alpha-name mr20 \ + --param window=20 \ + --data-path .pq +``` + +`mean_reversion` declares a `window` param (not `lookback`); `--param window=20` +supplies it and the unrelated `--lookback`/`--vol-window` defaults are ignored. + +## Parquet schemas + +The column contracts in `pipeline/common/schema.py` are the only interface +between phases (data is stored long/tidy): + +- **data** (`DATA_COLUMNS`): `symbol_id, symbol_name, date, open, high, low, close, volume, amount` +- **alpha** (`ALPHA_COLUMNS`): `symbol_id, date, alpha_name, weight` +- **combo** (`COMBO_COLUMNS`): `symbol_id, date, combo_name, weight` + +The data phase writes a month-partitioned dataset, so reading the dataset +directory yields an extra `month` (`YYYY-MM`) partition column on top of +`DATA_COLUMNS`; the alpha phase pivots by name and ignores it. + ## Layout -- `data/` — unified downloader (akshare -> baostock fallback) and data schema -- `backtest/` — config, pandas->backtrader feed adapter, and `BacktestRunner` -- `strategies/` — example `SmaCross` strategy -- `analysis/` — performance reporting (sharpe, drawdown, returns, trades) +- `cli.py` — entry point wiring the three phases together +- `pipeline/data/` — universe resolution + download → `data/daily_bars/{universe}/month=YYYY-MM/*.pq` +- `pipeline/alpha/` — `base.py` (`BaseAlpha`), `registry.py` (factory + plugin loader), + `library/` (built-in alphas), `compute.py` (`compute_alpha` / `evaluate_alpha`) +- `pipeline/combo/` — alpha combination → `combos/*.pq` +- `pipeline/common/schema.py` — parquet column contracts +- `data/downloader.py`, `data/universe.py` — baostock/akshare download + constituents +- `examples/alphas/` — example external alpha(s) + +## Roadmap (not yet implemented) + +The pipeline currently ends at `combo`, and `alpha eval` only interprets a weight +series as a portfolio for quick scoring (return / Sharpe / turnover / drawdown). +It is **not** a true backtest — there is no transaction-cost, slippage, or +execution modeling. The following phases are planned but not built yet: + +- [ ] **Portfolio construction** — turn combo weights into target positions + (gross/net exposure caps, per-name and sector limits, capital allocation, + rebalance schedule). +- [ ] **Backtesting** — event-driven simulation over the constructed positions + with realistic fills, transaction costs, slippage, and borrow constraints; + richer P&L / risk attribution than `alpha eval`. +- [ ] **Forward / paper trading** — run the same construction logic on live + daily data, track simulated fills and a running P&L without real capital. + +Until these land, treat `alpha eval` as a fast sanity check on a weight series, +not a performance estimate. diff --git a/analysis/report.py b/analysis/report.py deleted file mode 100644 index ee1bfdf..0000000 --- a/analysis/report.py +++ /dev/null @@ -1,206 +0,0 @@ -"""Performance analysis and reporting for backtest results.""" -import os -from typing import Any - -import matplotlib - -matplotlib.use("Agg") -import matplotlib.pyplot as plt # noqa: E402 -import pandas as pd # noqa: E402 - - -def print_results(results: list, initial_cash: float = 1_000_000.0) -> dict[str, Any]: - """Print and return key performance metrics from a backtrader run result.""" - if not results: - print("No results to report.") - return {} - - result = results[0] - report = {} - - # Sharpe ratio - sharpe = result.analyzers.sharpe.get_analysis() - report["sharpe"] = sharpe.get("sharperatio", "N/A") - - # Drawdown - dd = result.analyzers.drawdown.get_analysis() - report["max_drawdown"] = dd.get("max", {}).get("drawdown", "N/A") - report["max_drawdown_len"] = dd.get("max", {}).get("len", "N/A") - - # Returns - rets = result.analyzers.returns.get_analysis() - report["total_return"] = rets.get("rtot", "N/A") - report["avg_return"] = rets.get("ravg", "N/A") - - # Trades - trades = result.analyzers.trades.get_analysis() - report["total_trades"] = trades.get("total", {}).get("total", 0) - report["won_trades"] = trades.get("won", {}).get("total", 0) - report["lost_trades"] = trades.get("lost", {}).get("total", 0) - - # Print - print("=" * 50) - print("BACKTEST RESULTS") - print("=" * 50) - print(f"Sharpe Ratio: {report['sharpe']}") - print(f"Total Return: {report['total_return']:.4%}" if isinstance(report['total_return'], float) else f"Total Return: {report['total_return']}") - print(f"Max Drawdown: {report['max_drawdown']:.2%}" if isinstance(report['max_drawdown'], float) else f"Max Drawdown: {report['max_drawdown']}") - print(f"Max DD Length: {report['max_drawdown_len']}") - print(f"Total Trades: {report['total_trades']}") - print(f"Won/Lost: {report['won_trades']}/{report['lost_trades']}") - print("=" * 50) - - return report - - -def plot_accumulated_pnl( - results: list, output_path: str = "reports/pnl.png", initial_cash: float = 1_000_000.0 -) -> str: - """Plot accumulated portfolio value from a backtest run. - - Reads the per-day TimeReturn analyzer attached by ``BacktestRunner`` and - compounds it into an equity curve. - - Args: - results: The list returned by ``cerebro.run()``. - output_path: Destination PNG path. - initial_cash: Starting portfolio value for scaling the curve. - - Returns: - The path the chart was written to. - """ - os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) - series = pd.Series(dtype=float) - if results: - tr = results[0].analyzers.timereturn.get_analysis() - series = pd.Series(tr).sort_index() - - fig, ax = plt.subplots(figsize=(10, 5)) - if len(series): - equity = (1.0 + series).cumprod() * initial_cash - ax.plot(equity.index, equity.values, color="C0") - ax.set_title("Accumulated Portfolio Value") - ax.set_xlabel("Date") - ax.set_ylabel("Value") - ax.grid(True, alpha=0.3) - fig.autofmt_xdate() - fig.tight_layout() - fig.savefig(output_path, dpi=100) - plt.close(fig) - return output_path - - -def plot_ic(signal_eval: dict, output_path: str = "reports/ic.png") -> str: - """Plot the per-period rank IC time series from a signal evaluation. - - Args: - signal_eval: Dict returned by ``evaluate_cross_sectional`` (expects a - ``rank_ic_series`` pandas Series). - output_path: Destination PNG path. - - Returns: - The path the chart was written to. - """ - os.makedirs(os.path.dirname(output_path) or ".", exist_ok=True) - rank_ic = signal_eval.get("rank_ic_series", pd.Series(dtype=float)) - - fig, ax = plt.subplots(figsize=(10, 5)) - if len(rank_ic): - ax.bar(rank_ic.index, rank_ic.values, width=1.0, color="C1", alpha=0.6, label="Rank IC") - ax.axhline(rank_ic.mean(), color="C7", linestyle="--", label=f"mean={rank_ic.mean():.3f}") - cum_mean = rank_ic.expanding().mean() - ax.plot(cum_mean.index, cum_mean.values, color="red", linewidth=1.5, label="Cumulative mean IC") - ax.legend() - ax.set_title("Cross-Sectional Rank IC") - ax.set_xlabel("Date") - ax.set_ylabel("Rank IC") - ax.grid(True, alpha=0.3) - fig.autofmt_xdate() - fig.tight_layout() - fig.savefig(output_path, dpi=100) - plt.close(fig) - 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( - results: list, - signal_eval: dict, - output_dir: str = "reports/", - initial_cash: float = 1_000_000.0, -) -> dict[str, str]: - """Generate the full report: PnL chart, IC chart, and a summary text file. - - Args: - results: The list returned by ``cerebro.run()``. - signal_eval: Dict returned by ``evaluate_cross_sectional``. - output_dir: Directory to write artifacts into. - initial_cash: Starting portfolio value. - - Returns: - Mapping of artifact name to file path. - """ - os.makedirs(output_dir, exist_ok=True) - pnl_path = plot_accumulated_pnl( - results, os.path.join(output_dir, "pnl.png"), initial_cash - ) - ic_path = plot_ic(signal_eval, os.path.join(output_dir, "ic.png")) - - metrics = print_results(results, initial_cash) - summary_path = os.path.join(output_dir, "summary.txt") - with open(summary_path, "w") as f: - f.write("BACKTEST SUMMARY\n") - f.write("=" * 40 + "\n") - for k, v in metrics.items(): - f.write(f"{k}: {v}\n") - f.write("\nSIGNAL IC\n") - f.write("=" * 40 + "\n") - for k in ("ic_mean", "ic_std", "ir", "rank_ic_mean", "rank_ic_std", "rank_ir", "hit_rate", "n_periods"): - if k in signal_eval: - f.write(f"{k}: {signal_eval[k]}\n") - - return {"pnl": pnl_path, "ic": ic_path, "summary": summary_path} diff --git a/backtest/config.py b/backtest/config.py deleted file mode 100644 index ccae95b..0000000 --- a/backtest/config.py +++ /dev/null @@ -1,14 +0,0 @@ -from dataclasses import dataclass, field -from datetime import date - - -@dataclass -class BacktestConfig: - symbols: list[str] = field(default_factory=lambda: ["sh600000"]) - start_date: str = "2023-01-01" - end_date: str = "2024-12-31" - initial_cash: float = 1_000_000.0 - commission: float = 0.0003 # 0.03% for Chinese A-shares - stamp_duty: float = 0.001 # 0.1% stamp duty on sells only (handled in strategy) - adjust: str = "qfq" - sizer_percent: float = 0.95 # fraction of portfolio per trade diff --git a/backtest/feed.py b/backtest/feed.py deleted file mode 100644 index 8cff07a..0000000 --- a/backtest/feed.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Convert pandas DataFrames to backtrader data feeds.""" -import backtrader as bt -import pandas as pd - - -def df_to_bt_feed(df: pd.DataFrame) -> bt.feeds.PandasData: - """Convert a standardized OHLCV DataFrame to a backtrader PandasData feed.""" - df = df.copy() - df["date"] = pd.to_datetime(df["date"]) - df = df.set_index("date") - df = df[["open", "high", "low", "close", "volume"]] - return bt.feeds.PandasData(dataname=df) - - -class SignalPandasData(bt.feeds.PandasData): - """PandasData feed carrying an extra ``signal`` line alongside OHLCV.""" - - lines = ("signal",) - params = (("signal", -1),) # -1 -> match by column name - - -def df_to_signal_feed(df: pd.DataFrame) -> "SignalPandasData": - """Convert an OHLCV+signal DataFrame to a SignalPandasData feed. - - Args: - df: DataFrame with ``date``, OHLCV columns, and a ``signal`` column. - - Returns: - A SignalPandasData feed (NaN signals are preserved for the strategy to skip). - """ - df = df.copy() - df["date"] = pd.to_datetime(df["date"]) - df = df.set_index("date") - df = df[["open", "high", "low", "close", "volume", "signal"]] - return SignalPandasData(dataname=df) diff --git a/backtest/runner.py b/backtest/runner.py deleted file mode 100644 index 07ba90f..0000000 --- a/backtest/runner.py +++ /dev/null @@ -1,84 +0,0 @@ -"""BacktestRunner: orchestrates data loading, cerebro setup, and execution.""" -import logging -import backtrader as bt -from typing import Optional - -from backtest.config import BacktestConfig -from backtest.feed import df_to_bt_feed, df_to_signal_feed -from data.downloader import download_daily - -logger = logging.getLogger(__name__) - - -class BacktestRunner: - """Run backtrader backtests with Chinese equity data.""" - - def __init__(self, config: BacktestConfig): - self.config = config - self.cerebro = bt.Cerebro() - self._results: Optional[list] = None - - def add_data(self, symbol: str) -> None: - """Download data for a symbol and add to cerebro as a feed.""" - df = download_daily( - symbol=symbol, - start=self.config.start_date, - end=self.config.end_date, - adjust=self.config.adjust, - ) - feed = df_to_bt_feed(df) - self.cerebro.adddata(feed, name=symbol) - logger.info(f"Added {symbol}: {len(df)} bars") - - def add_signal_data(self, df, name: str) -> None: - """Add a pre-built OHLCV+signal DataFrame as a SignalPandasData feed.""" - feed = df_to_signal_feed(df) - self.cerebro.adddata(feed, name=name) - logger.info(f"Added signal feed {name}: {len(df)} bars") - - def add_strategy(self, strategy_cls, **kwargs) -> None: - """Add a strategy class to cerebro.""" - self.cerebro.addstrategy(strategy_cls, **kwargs) - - def _configure(self) -> None: - """Configure broker, sizer, and analyzers (independent of data feeds).""" - self.cerebro.broker.setcash(self.config.initial_cash) - self.cerebro.broker.setcommission(commission=self.config.commission) - self.cerebro.addsizer(bt.sizers.PercentSizer, percents=self.config.sizer_percent * 100) - - self.cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name="sharpe", riskfreerate=0.02) - self.cerebro.addanalyzer(bt.analyzers.DrawDown, _name="drawdown") - self.cerebro.addanalyzer(bt.analyzers.Returns, _name="returns") - self.cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name="trades") - self.cerebro.addanalyzer( - bt.analyzers.TimeReturn, _name="timereturn", timeframe=bt.TimeFrame.Days - ) - - def setup(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> None: - """Full setup: load data for all symbols, configure cerebro, add strategy.""" - for sym in self.config.symbols: - self.add_data(sym) - self._configure() - self.cerebro.addstrategy(strategy_cls, **(strategy_kwargs or {})) - - def run(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> list: - """Setup (downloading all symbols) and run the backtest.""" - self.setup(strategy_cls, strategy_kwargs) - return self._execute() - - def run_prepared(self, strategy_cls, strategy_kwargs: Optional[dict] = None) -> list: - """Run a backtest using feeds already added via ``add_signal_data``.""" - self._configure() - self.cerebro.addstrategy(strategy_cls, **(strategy_kwargs or {})) - return self._execute() - - def _execute(self) -> list: - start_val = self.cerebro.broker.getvalue() - logger.info(f"Starting portfolio value: {start_val:,.2f}") - self._results = self.cerebro.run() - end_val = self.cerebro.broker.getvalue() - logger.info(f"Ending portfolio value: {end_val:,.2f}") - return self._results - - def get_results(self) -> Optional[list]: - return self._results diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..57a88e1 --- /dev/null +++ b/cli.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""Chinese Equity Quant Pipeline — decoupled phase CLI. + +Phases: + data — Download daily bars to parquet + alpha — Compute alpha weights from data + combo — Combine alphas into a single weight +""" + +import logging + +import click + +from pipeline.data.cli import data +from pipeline.alpha.cli import alpha +from pipeline.combo.cli import combo + + +@click.group() +@click.option( + "--log-level", default="INFO", + type=click.Choice(["DEBUG", "INFO", "WARNING", "ERROR"], case_sensitive=False), + help="Logging verbosity (default INFO shows download/compute progress)", +) +def cli(log_level): + """Chinese Equity Quant Pipeline. + + Each phase is independent: read from parquet, write to parquet. + """ + logging.basicConfig( + level=getattr(logging, log_level.upper()), + format="%(asctime)s %(levelname)s %(name)s: %(message)s", + datefmt="%H:%M:%S", + ) + + +cli.add_command(data) +cli.add_command(alpha) +cli.add_command(combo) + + +if __name__ == "__main__": + cli() diff --git a/data/downloader.py b/data/downloader.py index 9bfca3f..5b3d759 100644 --- a/data/downloader.py +++ b/data/downloader.py @@ -1,14 +1,16 @@ -"""Unified data downloader: akshare primary, baostock fallback.""" +"""Unified data downloader: baostock primary, akshare fallback.""" import logging -from datetime import date, datetime -from typing import Optional +from typing import Iterable, Iterator, Optional, Tuple import pandas as pd import akshare as ak import baostock as bs logger = logging.getLogger(__name__) -BAOSTOCK_FREQ_MAP = {"d": "d", "w": "w", "m": "m"} # baostock only supports daily +# Map the adjust argument to baostock's adjustflag codes. +_BAOSTOCK_ADJUST = {"qfq": "2", "hfq": "1", "": "3", "none": "3"} +_BAOSTOCK_FIELDS = "date,open,high,low,close,volume,amount" +_OHLCV = ["open", "high", "low", "close", "volume", "amount"] def _download_akshare(symbol: str, start: str, end: str, adjust: str = "qfq") -> Optional[pd.DataFrame]: @@ -44,8 +46,8 @@ def _download_akshare(symbol: str, start: str, end: str, adjust: str = "qfq") -> return None -def _download_baostock(symbol: str, start: str, end: str, frequency: str = "d") -> Optional[pd.DataFrame]: - """Download daily bars from baostock as fallback.""" +def _download_baostock(symbol: str, start: str, end: str, adjust: str = "qfq") -> Optional[pd.DataFrame]: + """Download daily bars from baostock (primary source).""" try: bs.login() # baostock format: sh.600000 @@ -55,8 +57,8 @@ def _download_baostock(symbol: str, start: str, end: str, frequency: str = "d") fields="date,open,high,low,close,volume,amount", start_date=start, end_date=end, - frequency=frequency, - adjustflag="2", # qfq + frequency="d", + adjustflag=_BAOSTOCK_ADJUST.get(adjust, "2"), ) if rs.error_code != "0": logger.warning(f"baostock error for {symbol}: {rs.error_msg}") @@ -64,22 +66,22 @@ def _download_baostock(symbol: str, start: str, end: str, frequency: str = "d") data_list = [] while rs.next(): data_list.append(rs.get_row_data()) - bs.logout() if not data_list: return None df = pd.DataFrame(data_list, columns=["date", "open", "high", "low", "close", "volume", "amount"]) df[["open", "high", "low", "close", "volume", "amount"]] = df[ ["open", "high", "low", "close", "volume", "amount"] - ].astype(float) + ].apply(pd.to_numeric, errors="coerce") df["symbol"] = symbol return df[["symbol", "date", "open", "high", "low", "close", "volume", "amount"]] except Exception as e: logger.warning(f"baostock download failed for {symbol}: {e}") + return None + finally: try: bs.logout() except Exception: pass - return None def download_daily( @@ -90,24 +92,24 @@ def download_daily( source: str = "auto", ) -> pd.DataFrame: """ - Download daily OHLCV data. Tries akshare first, falls back to baostock. + Download daily OHLCV data. Tries baostock first, falls back to akshare. Args: symbol: Stock symbol like 'sh600000' or 'sz000001' start: Start date 'YYYY-MM-DD' end: End date 'YYYY-MM-DD' adjust: 'qfq' (forward-adjusted), 'hfq' (backward), '' (none) - source: 'auto' (akshare then baostock fallback), 'akshare' only, - or 'baostock' only + source: 'auto' (baostock then akshare fallback), 'baostock' only, + or 'akshare' only Returns: DataFrame with columns: symbol, date, open, high, low, close, volume, amount """ df = None - if source in ("akshare", "auto"): + if source in ("baostock", "auto"): + df = _download_baostock(symbol, start, end, adjust) + if df is None and source in ("akshare", "auto"): df = _download_akshare(symbol, start, end, adjust) - if df is None and source in ("baostock", "auto"): - df = _download_baostock(symbol, start, end) if df is None or df.empty: raise RuntimeError(f"Failed to download data for {symbol} from {start} to {end}") @@ -117,18 +119,68 @@ def download_daily( return df -def download_batch( - symbols: list[str], +def download_daily_batch( + symbols: Iterable[str], start: str, end: str, adjust: str = "qfq", -) -> dict[str, pd.DataFrame]: - """Download daily data for multiple symbols. Returns {symbol: DataFrame}.""" - results = {} - for sym in symbols: + akshare_fallback: bool = True, +) -> Iterator[Tuple[str, Optional[pd.DataFrame]]]: + """Download many symbols under a single baostock session. + + Logging into baostock once per call (instead of per symbol) is the dominant + speed-up when fetching thousands of symbols. Yields ``(symbol, df)`` as each + symbol completes so callers can stream results to disk; ``df`` is ``None`` + when both sources fail. Each ``df`` has the same 8 columns as + :func:`download_daily`. + + Args: + symbols: Internal-form symbols (``sh600000`` / ``sz000001``). + start, end: ``YYYY-MM-DD`` bounds. + adjust: ``qfq`` / ``hfq`` / ``''``. + akshare_fallback: Retry a failed symbol through akshare before yielding + ``None``. + """ + flag = _BAOSTOCK_ADJUST.get(adjust, "2") + bs.login() + try: + for symbol in symbols: + df: Optional[pd.DataFrame] = None + try: + code = f"{symbol[:2]}.{symbol[2:]}" + rs = bs.query_history_k_data_plus( + code=code, fields=_BAOSTOCK_FIELDS, + start_date=start, end_date=end, + frequency="d", adjustflag=flag, + ) + if rs.error_code == "0": + rows = [] + while rs.next(): + rows.append(rs.get_row_data()) + if rows: + df = pd.DataFrame(rows, columns=["date", *_OHLCV]) + # Suspended-trading days come back as empty strings; + # coerce to NaN rather than crashing the whole symbol. + df[_OHLCV] = df[_OHLCV].apply(pd.to_numeric, errors="coerce") + df["symbol"] = symbol + df = df[["symbol", "date", *_OHLCV]] + else: + logger.warning("baostock error for %s: %s", symbol, rs.error_msg) + except Exception as e: + logger.warning("baostock download failed for %s: %s", symbol, e) + + if (df is None or df.empty) and akshare_fallback: + df = _download_akshare(symbol, start, end, adjust) + + if df is not None and not df.empty: + df["date"] = pd.to_datetime(df["date"]) + df = df.sort_values("date").reset_index(drop=True) + yield symbol, df + else: + yield symbol, None + finally: try: - results[sym] = download_daily(sym, start, end, adjust) - logger.info(f"Downloaded {sym}: {len(results[sym])} bars") - except Exception as e: - logger.error(f"Failed {sym}: {e}") - return results + bs.logout() + except Exception: + pass + diff --git a/data/schema.py b/data/schema.py deleted file mode 100644 index 6da34da..0000000 --- a/data/schema.py +++ /dev/null @@ -1,44 +0,0 @@ -from dataclasses import dataclass, field -from datetime import date -from typing import Optional -import pandas as pd - - -@dataclass -class DailyBar: - """Single daily bar for one stock.""" - symbol: str - date: date - open: float - high: float - low: float - close: float - volume: float - amount: float # turnover in yuan - - @classmethod - def from_dataframe(cls, df: pd.DataFrame, symbol_col: str = "symbol") -> list["DailyBar"]: - """Convert akshare/baostock DataFrame to list of DailyBar.""" - bars = [] - for _, row in df.iterrows(): - bars.append(cls( - symbol=row.get(symbol_col, ""), - date=pd.Timestamp(row["date"]).date(), - open=float(row["open"]), - high=float(row["high"]), - low=float(row["low"]), - close=float(row["close"]), - volume=float(row["volume"]), - amount=float(row.get("amount", 0)), - )) - return bars - - def to_series(self) -> dict: - return { - "date": self.date, - "open": self.open, - "high": self.high, - "low": self.low, - "close": self.close, - "volume": self.volume, - } diff --git a/data/universe.py b/data/universe.py index a026e9e..0ba793e 100644 --- a/data/universe.py +++ b/data/universe.py @@ -1,36 +1,16 @@ -"""CSI 300 (HS300) and CSI 500 (ZZ500) universe helpers.""" +"""CSI 300 (HS300), CSI 500 (ZZ500), and full A-share universe helpers.""" import logging +from datetime import date, timedelta import baostock as bs import pandas as pd logger = logging.getLogger(__name__) -# First 30 HS300 constituents (large caps) in 'shXXXXXX' / 'szXXXXXX' format. -# Hardcoded for fast, deterministic smoke tests. Use get_hs300_stocks() for the -# live, full list — downloading daily bars for all ~300 takes roughly 10 minutes. -SYMBOLS = [ - "sh600000", "sh600009", "sh600010", "sh600028", "sh600030", - "sh600036", "sh600048", "sh600050", "sh600104", "sh600276", - "sh600309", "sh600519", "sh600585", "sh600887", "sh600900", - "sh601012", "sh601166", "sh601288", "sh601318", "sh601398", - "sh601628", "sh601668", "sh601857", "sh601888", "sh601988", - "sz000001", "sz000002", "sz000333", "sz000651", "sz000858", -] - - -# First 30 CSI 500 (ZZ500) constituents (mid/small caps) in 'shXXXXXX' / -# 'szXXXXXX' format. Hardcoded for fast, deterministic smoke tests. Use -# get_zz500_stocks() for the live, full list. Mean reversion tends to be -# stronger in these smaller caps than in the HS300 large caps. -CSI500_SYMBOLS = [ - "sh600006", "sh600008", "sh600017", "sh600020", "sh600021", - "sh600026", "sh600037", "sh600039", "sh600053", "sh600056", - "sh600060", "sh600061", "sh600062", "sh600073", "sh600089", - "sh600095", "sh600118", "sh600125", "sh600126", "sh600143", - "sh600153", "sh600160", "sh600169", "sh600176", "sh600183", - "sz000009", "sz000012", "sz000021", "sz000025", "sz000027", -] +# A-share code patterns (baostock dotted form): SH main/STAR (sh.6xxxxx), +# SZ main/SME (sz.0xxxxx), ChiNext (sz.3xxxxx). Excludes indices and B-shares. +_ASHARE_RE = r"^sh\.6\d{5}$|^sz\.[03]\d{5}$" +_SZ_INDEX_RE = r"^sz\.399" def get_hs300_stocks() -> pd.DataFrame: @@ -69,3 +49,44 @@ def get_zz500_stocks() -> pd.DataFrame: df = pd.DataFrame(stocks, columns=["code", "name", "date"]) df["code"] = df["code"].str.replace(".", "", regex=False) return df + + +def get_all_stocks(day: str = "") -> pd.DataFrame: + """Fetch every listed A-share from baostock's all-stock snapshot. + + Queries ``query_all_stock`` for a single trading day and keeps only A-shares + (SH main/STAR, SZ main/SME/ChiNext), dropping indices and B-shares. If the + given day is a non-trading day baostock returns nothing, so we walk back up + to 10 days to land on the most recent trading day. + + Args: + day: ``YYYY-MM-DD`` snapshot day; defaults to today (walks back to the + last trading day). + + Returns: + DataFrame with columns ``code`` (e.g. ``sh600000``), ``name``. + """ + start = date.fromisoformat(day) if day else date.today() + bs.login() + try: + rows: list = [] + fields: list = [] + for back in range(11): + probe = (start - timedelta(days=back)).isoformat() + rs = bs.query_all_stock(day=probe) + fields = rs.fields + while rs.next(): + rows.append(rs.get_row_data()) + if rows: + logger.info("query_all_stock: %d rows on %s", len(rows), probe) + break + finally: + bs.logout() + + df = pd.DataFrame(rows, columns=fields) + code = df["code"] + keep = code.str.match(_ASHARE_RE) & ~code.str.match(_SZ_INDEX_RE) + df = df[keep].copy() + df["code"] = df["code"].str.replace(".", "", regex=False) + df = df.rename(columns={"code_name": "name"}) + return df[["code", "name"]].reset_index(drop=True) diff --git a/eval/__init__.py b/eval/__init__.py deleted file mode 100644 index 20cb146..0000000 --- a/eval/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Signal evaluation metrics.""" -from eval.metrics import evaluate_cross_sectional - -__all__ = ["evaluate_cross_sectional"] diff --git a/eval/metrics.py b/eval/metrics.py deleted file mode 100644 index 3dda218..0000000 --- a/eval/metrics.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Information-coefficient metrics for alpha signals.""" -from typing import Any - -import pandas as pd - - -def _summarize(ic: pd.Series, rank_ic: pd.Series) -> dict[str, Any]: - """Aggregate per-period IC series into summary statistics.""" - ic = ic.dropna() - rank_ic = rank_ic.dropna() - - ic_mean = float(ic.mean()) if len(ic) else float("nan") - ic_std = float(ic.std()) if len(ic) else float("nan") - rank_ic_mean = float(rank_ic.mean()) if len(rank_ic) else float("nan") - rank_ic_std = float(rank_ic.std()) if len(rank_ic) else float("nan") - - return { - "ic_mean": ic_mean, - "ic_std": ic_std, - "ir": ic_mean / ic_std if ic_std else float("nan"), - "rank_ic_mean": rank_ic_mean, - "rank_ic_std": rank_ic_std, - "rank_ir": rank_ic_mean / rank_ic_std if rank_ic_std else float("nan"), - "hit_rate": float((rank_ic > 0).mean()) if len(rank_ic) else float("nan"), - "n_periods": int(len(rank_ic)), - "ic_series": ic, - "rank_ic_series": rank_ic, - } - - -def _cross_sectional(signals_df: pd.DataFrame, returns_df: pd.DataFrame) -> dict[str, Any]: - """Per-date IC across stocks (requires >= 2 stocks).""" - dates = signals_df.index - ic_vals, rank_ic_vals, idx = [], [], [] - for dt in dates: - s = signals_df.loc[dt] - r = returns_df.loc[dt] - mask = s.notna() & r.notna() - if mask.sum() < 2: - continue - sv, rv = s[mask], r[mask] - # A degenerate (constant) vector makes correlation undefined. - if sv.nunique() < 2 or rv.nunique() < 2: - continue - ic_vals.append(sv.corr(rv)) - rank_ic_vals.append(sv.corr(rv, method="spearman")) - idx.append(dt) - ic = pd.Series(ic_vals, index=idx, dtype=float) - rank_ic = pd.Series(rank_ic_vals, index=idx, dtype=float) - return _summarize(ic, rank_ic) - - -def _rolling_single( - signals_df: pd.DataFrame, returns_df: pd.DataFrame, window: int = 20 -) -> dict[str, Any]: - """Rolling time-series IC for the single-stock case. - - With one stock there is no cross-section, so we measure how well the signal - tracks forward returns over a trailing window instead. - """ - col = signals_df.columns[0] - s = signals_df[col] - r = returns_df[col] - ic = s.rolling(window).corr(r) - rank_ic = s.rank().rolling(window).corr(r.rank()) - return _summarize(ic, rank_ic) - - -def evaluate_cross_sectional( - signals_df: pd.DataFrame, returns_df: pd.DataFrame -) -> dict[str, Any]: - """Evaluate a signal's predictive power against forward returns. - - Args: - signals_df: DataFrame indexed by date, one column per stock, signal values. - returns_df: DataFrame indexed by date, one column per stock, forward returns. - - Returns: - Dict with ``ic_mean``, ``ic_std``, ``ir``, ``rank_ic_mean``, - ``rank_ic_std``, ``rank_ir``, ``hit_rate``, ``n_periods`` and the - per-period ``ic_series`` / ``rank_ic_series`` (for plotting). - """ - cols = signals_df.columns.intersection(returns_df.columns) - idx = signals_df.index.intersection(returns_df.index) - signals_df = signals_df.loc[idx, cols] - returns_df = returns_df.loc[idx, cols] - - if len(cols) >= 2: - return _cross_sectional(signals_df, returns_df) - return _rolling_single(signals_df, returns_df) diff --git a/analysis/__init__.py b/pipeline/__init__.py similarity index 100% rename from analysis/__init__.py rename to pipeline/__init__.py diff --git a/backtest/__init__.py b/pipeline/alpha/__init__.py similarity index 100% rename from backtest/__init__.py rename to pipeline/alpha/__init__.py diff --git a/pipeline/alpha/base.py b/pipeline/alpha/base.py new file mode 100644 index 0000000..9115d40 --- /dev/null +++ b/pipeline/alpha/base.py @@ -0,0 +1,57 @@ +"""Base class for alphas. + +An alpha maps a wide close matrix (date index × symbol_id columns) to signed +position weights. Subclasses implement :meth:`signal` — the raw, unnormalized +score. The base class turns a signal into cross-sectionally z-scored weights +via :meth:`to_weights` (override it for a different normalization). +""" +from abc import ABC, abstractmethod + +import numpy as np +import pandas as pd + + +class BaseAlpha(ABC): + """A position-weight alpha over a cross-section of stocks. + + Concrete subclasses must set a unique class-level :attr:`name` (the registry + key) and implement :meth:`signal`. Construct subclasses with their own typed + parameters (e.g. ``lookback``); the factory passes only the parameters a + given ``__init__`` accepts. + """ + + #: Unique registry key. Every concrete alpha must set this to a non-empty str. + name: str = "" + + @abstractmethod + def signal(self, close: pd.DataFrame) -> pd.DataFrame: + """Compute the raw signal. + + Args: + close: Wide close prices, date index × ``symbol_id`` columns. + + Returns: + A wide DataFrame aligned to ``close`` where higher values indicate a + stronger long. Use NaN where the signal is undefined. + """ + + def to_weights(self, signal: pd.DataFrame) -> pd.DataFrame: + """Cross-sectionally z-score a signal into signed position weights. + + Each date is demeaned and scaled by its cross-sectional std; undefined + cells become a 0 weight. Override for a custom scheme (rank, neutralized, + capped, etc.). + """ + signal = signal.dropna(how="all") + demeaned = signal.subtract(signal.mean(axis=1), axis=0) + std = signal.std(axis=1).replace(0, np.nan) + weights = demeaned.divide(std, axis=0) + return weights.fillna(0.0) + + def weights(self, close: pd.DataFrame) -> pd.DataFrame: + """Full pipeline for one alpha: raw signal → normalized weights.""" + return self.to_weights(self.signal(close)) + + def __repr__(self) -> str: + params = ", ".join(f"{k}={v!r}" for k, v in vars(self).items()) + return f"{type(self).__name__}({params})" diff --git a/pipeline/alpha/cli.py b/pipeline/alpha/cli.py new file mode 100644 index 0000000..c1efad6 --- /dev/null +++ b/pipeline/alpha/cli.py @@ -0,0 +1,174 @@ +"""CLI for alpha computation and evaluation.""" + +import json +import os + +import click +import pandas as pd + +from pipeline.alpha.compute import compute_alpha, evaluate_alpha +from pipeline.alpha.registry import available_alphas, load_alpha_module + + +@click.group(name="alpha") +def alpha(): + """Compute and evaluate alpha weights.""" + + +def _coerce(value: str): + """Best-effort coercion of a CLI string to int, then float, else str.""" + for cast in (int, float): + try: + return cast(value) + except ValueError: + continue + return value + + +def _parse_params(pairs: tuple[str, ...]) -> dict: + """Parse repeated ``name=value`` options into a params dict.""" + params: dict = {} + for pair in pairs: + if "=" not in pair: + raise click.BadParameter(f"--param must be name=value, got '{pair}'") + key, value = pair.split("=", 1) + params[key.strip()] = _coerce(value.strip()) + return params + + +@alpha.command("list") +@click.option( + "--alpha-module", "alpha_modules", multiple=True, + help="External module(s) to import first (dotted path or .py file)", +) +def list_(alpha_modules): + """List the registered alpha types.""" + for spec in alpha_modules: + load_alpha_module(spec) + for name in available_alphas(): + click.echo(name) + + +@alpha.command("compute") +@click.option("--data-path", required=True, help="Path to data parquet file") +@click.option("--alpha-name", required=True, help="Name for this alpha") +@click.option("--alpha-type", required=True, help="Registry key of the alpha class") +@click.option("--output-dir", default="alphas", help="Directory to save alpha parquet") +@click.option("--lookback", default=5, type=int, help="Lookback days") +@click.option("--vol-window", default=20, type=int, help="Volatility window (reversal_vol only)") +@click.option( + "--alpha-module", "alpha_modules", multiple=True, + help="External module(s) to import so their alphas register (dotted path or .py file)", +) +@click.option( + "--param", "extra_params", multiple=True, + help="Extra alpha constructor param as name=value (repeatable)", +) +def compute(data_path, alpha_name, alpha_type, output_dir, lookback, vol_window, + alpha_modules, extra_params): + """Compute one alpha from raw data and save as parquet.""" + for spec in alpha_modules: + load_alpha_module(spec) + + options = available_alphas() + if alpha_type not in options: + raise click.BadParameter( + f"Unknown alpha-type '{alpha_type}'. Available: {options}. " + f"Use --alpha-module to register an external alpha.", + param_hint="--alpha-type", + ) + + params = {"lookback": lookback, "vol_window": vol_window} + params.update(_parse_params(extra_params)) + + data = pd.read_parquet(data_path) + click.echo(f"Loaded data: {len(data):,} rows from {data_path}") + + result = compute_alpha( + data=data, + alpha_name=alpha_name, + alpha_type=alpha_type, + **params, + ) + + os.makedirs(output_dir, exist_ok=True) + out_path = f"{output_dir}/{alpha_name}.pq" + result.to_parquet(out_path, index=False) + click.echo(f"Saved alpha: {out_path} ({len(result):,} rows)") + click.echo( + f"Weight stats — min: {result['weight'].min():.4f}, " + f"max: {result['weight'].max():.4f}, " + f"mean: {result['weight'].mean():.4f}" + ) + + +@alpha.command("reversal") +@click.option("--data-path", required=True, help="Path to data parquet file") +@click.option("--output-dir", default="alphas", help="Directory to save alpha parquet") +@click.option("--lookback", default=5, type=int, help="Lookback days") +def reversal(data_path, output_dir, lookback): + """Shortcut: compute a reversal alpha.""" + alpha_name = f"reversal_{lookback}d" + ctx = click.get_current_context() + ctx.invoke( + compute, + data_path=data_path, + alpha_name=alpha_name, + alpha_type="reversal", + output_dir=output_dir, + lookback=lookback, + ) + + +@alpha.command("reversal-vol") +@click.option("--data-path", required=True, help="Path to data parquet file") +@click.option("--output-dir", default="alphas", help="Directory to save alpha parquet") +@click.option("--lookback", default=5, type=int, help="Lookback days") +@click.option("--vol-window", default=20, type=int, help="Volatility window") +def reversal_vol(data_path, output_dir, lookback, vol_window): + """Shortcut: compute a volatility-scaled reversal alpha.""" + alpha_name = f"reversal_vol_{lookback}d_{vol_window}d" + ctx = click.get_current_context() + ctx.invoke( + compute, + data_path=data_path, + alpha_name=alpha_name, + alpha_type="reversal_vol", + output_dir=output_dir, + lookback=lookback, + vol_window=vol_window, + ) + + +@alpha.command("eval") +@click.option("--alpha-path", required=True, help="Path to alpha parquet file") +@click.option("--data-path", required=True, help="Path to data parquet (for price data)") +def eval_(alpha_path, data_path): + """Evaluate an alpha's performance (return, Sharpe, turnover). + + Alphas are interpreted as position WEIGHTS, not return predictors. + No IC/IR metrics — these are not predictors of future returns. + """ + alpha_df = pd.read_parquet(alpha_path) + data_df = pd.read_parquet(data_path) + + metrics = evaluate_alpha(alpha_df, data_df) + + click.echo("\n" + "=" * 50) + click.echo("ALPHA EVALUATION") + click.echo("=" * 50) + click.echo(f"Cumulative Return: {metrics['cumulative_return']:>10.4%}") + click.echo(f"Annual Sharpe: {metrics['sharpe_annual']:>10.4f}") + click.echo(f"Annual Turnover: {metrics['turnover_annual']:>10.2%}") + click.echo(f"Max Drawdown: {metrics['max_drawdown']:>10.4%}") + click.echo(f"Hit Rate: {metrics['hit_rate']:>10.2%}") + click.echo(f"Trading Days: {metrics['n_dates']:>10d}") + click.echo("=" * 50) + + # Also dump JSON + os.makedirs("reports", exist_ok=True) + alpha_name = alpha_df["alpha_name"].iloc[0] + json_path = f"reports/{alpha_name}_eval.json" + with open(json_path, "w") as f: + json.dump(metrics, f, indent=2) + click.echo(f"\nReport saved: {json_path}") diff --git a/pipeline/alpha/compute.py b/pipeline/alpha/compute.py new file mode 100644 index 0000000..18b3846 --- /dev/null +++ b/pipeline/alpha/compute.py @@ -0,0 +1,153 @@ +"""Alpha computation and evaluation. + +Alphas are position WEIGHTS — positive=long, negative=short. They are NOT +predictors of future returns. Concrete alphas are classes that live in +``pipeline/alpha/library/`` (or any external module) and are resolved by name +through :mod:`pipeline.alpha.registry`. +""" + +import logging + +import numpy as np +import pandas as pd + +from pipeline.alpha.registry import get_alpha +from pipeline.common.schema import ALPHA_COLUMNS + +logger = logging.getLogger(__name__) + + +def _pivot_close(df: pd.DataFrame) -> pd.DataFrame: + """Pivot data to wide format: date index, columns = symbol_id, values = close.""" + pivot = df.pivot_table( + index="date", columns="symbol_id", values="close", aggfunc="first" + ) + return pivot.sort_index() + + +def _daily_returns(close: pd.DataFrame) -> pd.DataFrame: + """Compute daily returns from wide close DataFrame.""" + return close.pct_change() + + +def compute_alpha( + data: pd.DataFrame, + alpha_name: str, + alpha_type: str, + **params, +) -> pd.DataFrame: + """Compute alpha weights from raw data. + + Args: + data: DataFrame with DATA_COLUMNS. + alpha_name: Label stored in the ``alpha_name`` output column. + alpha_type: Registry key of the alpha class (e.g. ``reversal``). + **params: Constructor parameters for the alpha (e.g. ``lookback``, + ``vol_window``). Only the params the alpha's ``__init__`` accepts are + used; extras are ignored. + + Returns: + DataFrame with ALPHA_COLUMNS. + + Raises: + KeyError: If ``alpha_type`` is not registered. + """ + alpha = get_alpha(alpha_type, **params) + close = _pivot_close(data) + weights = alpha.weights(close) + + # Melt to long format + weights_melted = weights.reset_index().melt( + id_vars="date", var_name="symbol_id", value_name="weight" + ) + weights_melted["alpha_name"] = alpha_name + weights_melted = weights_melted[ALPHA_COLUMNS] + weights_melted = weights_melted.dropna(subset=["weight"]) + weights_melted = weights_melted.sort_values(["symbol_id", "date"]).reset_index(drop=True) + + logger.info( + "Alpha '%s' (%r): %d symbols × %d dates, weight range [%.4f, %.4f]", + alpha_name, + alpha, + weights_melted["symbol_id"].nunique(), + weights_melted["date"].nunique(), + weights_melted["weight"].min(), + weights_melted["weight"].max(), + ) + return weights_melted + + +def evaluate_alpha(alpha_df: pd.DataFrame, data_df: pd.DataFrame) -> dict: + """Evaluate an alpha's performance as position weights. + + Computes return, annualized Sharpe, annualized turnover, max drawdown. + + Alpha is interpreted as POSITION WEIGHTS, not predictions. + Return on date t = sum(weight[s,t] * realized_return[s,t]) / sum(abs(weight[s,t])) + + Args: + alpha_df: DataFrame with ALPHA_COLUMNS. + data_df: DataFrame with DATA_COLUMNS (for price data). + + Returns: + Dict with metrics: cumulative_return, sharpe_annual, turnover_annual, + max_drawdown, hit_rate, n_dates. + """ + close = _pivot_close(data_df) + returns = _daily_returns(close) + + # Pivot alpha weights to wide format + weights = alpha_df.pivot_table( + index="date", columns="symbol_id", values="weight", aggfunc="first" + ).sort_index() + + # Align dates + common_dates = weights.index.intersection(returns.index) + weights = weights.loc[common_dates] + returns = returns.loc[common_dates] + + if len(common_dates) < 2: + return { + "cumulative_return": 0.0, + "sharpe_annual": 0.0, + "turnover_annual": 0.0, + "max_drawdown": 0.0, + "hit_rate": 0.0, + "n_dates": len(common_dates), + } + + # Daily portfolio return = sum(w * r) / sum(|w|) — normalized by gross exposure + daily_returns = (weights * returns).sum(axis=1) / weights.abs().sum(axis=1) + + # Cumulative return + cumulative_return = float((1.0 + daily_returns).prod() - 1.0) + + # Annualized Sharpe (sqrt(252) * mean / std) + mu = daily_returns.mean() + sigma = daily_returns.std() + sharpe_annual = float(np.sqrt(252) * mu / sigma) if sigma > 0 else 0.0 + + # Annualized turnover: avg daily turnover * 252 + # Daily turnover = sum(|w_t - w_{t-1}|) / sum(|w_{t-1}|) + weight_change = weights.diff().abs().sum(axis=1) + gross_exposure = weights.abs().sum(axis=1).shift(1) + daily_turnover = weight_change / gross_exposure + turnover_annual = float(daily_turnover.mean() * 252) + + # Max drawdown + equity = (1.0 + daily_returns).cumprod() + peak = equity.cummax() + drawdown = (equity - peak) / peak + max_drawdown = float(drawdown.min()) + + # Hit rate + hit_rate = float((daily_returns > 0).mean()) + + return { + "cumulative_return": cumulative_return, + "sharpe_annual": sharpe_annual, + "turnover_annual": turnover_annual, + "max_drawdown": max_drawdown, + "hit_rate": hit_rate, + "n_dates": len(common_dates), + } diff --git a/pipeline/alpha/library/__init__.py b/pipeline/alpha/library/__init__.py new file mode 100644 index 0000000..91bb0b6 --- /dev/null +++ b/pipeline/alpha/library/__init__.py @@ -0,0 +1,7 @@ +"""Built-in alpha library. + +Importing this package imports each alpha module, which registers the alpha via +the ``@register_alpha`` decorator. Add a new built-in by dropping a module here +and importing it below. +""" +from pipeline.alpha.library import momentum, reversal, reversal_vol # noqa: F401 diff --git a/pipeline/alpha/library/momentum.py b/pipeline/alpha/library/momentum.py new file mode 100644 index 0000000..b72ef67 --- /dev/null +++ b/pipeline/alpha/library/momentum.py @@ -0,0 +1,18 @@ +"""Short-horizon momentum alpha.""" +import pandas as pd + +from pipeline.alpha.base import BaseAlpha +from pipeline.alpha.registry import register_alpha + + +@register_alpha +class MomentumAlpha(BaseAlpha): + """Positive trailing return: stocks that rose score high.""" + + name = "momentum" + + def __init__(self, lookback: int = 5): + self.lookback = lookback + + def signal(self, close: pd.DataFrame) -> pd.DataFrame: + return close.pct_change(self.lookback) diff --git a/pipeline/alpha/library/reversal.py b/pipeline/alpha/library/reversal.py new file mode 100644 index 0000000..08be14b --- /dev/null +++ b/pipeline/alpha/library/reversal.py @@ -0,0 +1,18 @@ +"""Short-horizon reversal alpha.""" +import pandas as pd + +from pipeline.alpha.base import BaseAlpha +from pipeline.alpha.registry import register_alpha + + +@register_alpha +class ReversalAlpha(BaseAlpha): + """Negative trailing return: oversold stocks score high.""" + + name = "reversal" + + def __init__(self, lookback: int = 5): + self.lookback = lookback + + def signal(self, close: pd.DataFrame) -> pd.DataFrame: + return -close.pct_change(self.lookback) diff --git a/pipeline/alpha/library/reversal_vol.py b/pipeline/alpha/library/reversal_vol.py new file mode 100644 index 0000000..8cef7ba --- /dev/null +++ b/pipeline/alpha/library/reversal_vol.py @@ -0,0 +1,26 @@ +"""Volatility-scaled short-horizon reversal alpha.""" +import pandas as pd + +from pipeline.alpha.base import BaseAlpha +from pipeline.alpha.registry import register_alpha + + +@register_alpha +class ReversalVolAlpha(BaseAlpha): + """Reversal scaled by trailing volatility. + + The raw reversal ``-close.pct_change(lookback)`` is divided by the rolling + standard deviation of daily returns over ``vol_window``, so the score favors + oversold names whose move is large *relative* to their own volatility. + """ + + name = "reversal_vol" + + def __init__(self, lookback: int = 5, vol_window: int = 20): + self.lookback = lookback + self.vol_window = vol_window + + def signal(self, close: pd.DataFrame) -> pd.DataFrame: + reversal = -close.pct_change(self.lookback) + vol = close.pct_change().rolling(self.vol_window).std() + return reversal / vol diff --git a/pipeline/alpha/registry.py b/pipeline/alpha/registry.py new file mode 100644 index 0000000..a6b3900 --- /dev/null +++ b/pipeline/alpha/registry.py @@ -0,0 +1,102 @@ +"""Registry and factory for alphas. + +Built-in alphas live in :mod:`pipeline.alpha.library` and self-register via the +:func:`register_alpha` decorator. External alphas authored anywhere can be made +available with :func:`load_alpha_module` (a dotted module path or a ``.py`` file), +which is how you test an alpha written outside this repo. +""" +import importlib +import importlib.util +import inspect +from pathlib import Path +from typing import Optional, Type + +from pipeline.alpha.base import BaseAlpha + +_REGISTRY: dict[str, Type[BaseAlpha]] = {} +_builtins_loaded = False + + +def register_alpha(cls: Type[BaseAlpha]) -> Type[BaseAlpha]: + """Class decorator that registers an alpha under its :attr:`~BaseAlpha.name`. + + Raises: + TypeError: If ``cls`` is not a ``BaseAlpha`` subclass. + ValueError: If ``name`` is empty or already used by a different class. + """ + if not (isinstance(cls, type) and issubclass(cls, BaseAlpha)): + raise TypeError(f"{cls!r} is not a BaseAlpha subclass") + key = getattr(cls, "name", "") + if not key: + raise ValueError(f"{cls.__name__} must set a non-empty class attribute `name`") + existing = _REGISTRY.get(key) + if existing is not None and existing is not cls: + raise ValueError( + f"Alpha name '{key}' already registered by {existing.__name__}" + ) + _REGISTRY[key] = cls + return cls + + +def available_alphas() -> list[str]: + """Sorted names of all registered alphas (built-ins are loaded lazily).""" + _ensure_builtins() + return sorted(_REGISTRY) + + +def get_alpha(name: str, **params) -> BaseAlpha: + """Instantiate a registered alpha by name. + + Only the parameters accepted by the alpha's ``__init__`` are forwarded, so a + caller may pass a superset (e.g. both ``lookback`` and ``vol_window``) and + each alpha picks what it needs. + + Raises: + KeyError: If ``name`` is not registered. + """ + _ensure_builtins() + if name not in _REGISTRY: + raise KeyError(f"Unknown alpha '{name}'. Available: {sorted(_REGISTRY)}") + cls = _REGISTRY[name] + accepted = _accepted_params(cls) + kwargs = params if accepted is None else {k: v for k, v in params.items() if k in accepted} + return cls(**kwargs) + + +def load_alpha_module(spec: str) -> None: + """Import an external module so its ``@register_alpha`` classes register. + + Args: + spec: A dotted module path (``my_pkg.my_alpha``) on ``sys.path``, or a + filesystem path to a ``.py`` file (``/path/to/my_alpha.py``). + + Raises: + FileNotFoundError: If a ``.py`` path is given but does not exist. + """ + looks_like_file = spec.endswith(".py") or Path(spec).expanduser().exists() + if looks_like_file: + path = Path(spec).expanduser().resolve() + if not path.exists(): + raise FileNotFoundError(f"Alpha module not found: {path}") + module_spec = importlib.util.spec_from_file_location(path.stem, path) + if module_spec is None or module_spec.loader is None: + raise ImportError(f"Cannot load alpha module from {path}") + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + else: + importlib.import_module(spec) + + +def _accepted_params(cls: Type[BaseAlpha]) -> Optional[set[str]]: + """Param names ``cls.__init__`` accepts, or None if it takes ``**kwargs``.""" + sig = inspect.signature(cls.__init__) + if any(p.kind is p.VAR_KEYWORD for p in sig.parameters.values()): + return None + return {name for name in sig.parameters if name != "self"} + + +def _ensure_builtins() -> None: + global _builtins_loaded + if not _builtins_loaded: + import pipeline.alpha.library # noqa: F401 (importing registers built-ins) + _builtins_loaded = True diff --git a/strategies/__init__.py b/pipeline/combo/__init__.py similarity index 100% rename from strategies/__init__.py rename to pipeline/combo/__init__.py diff --git a/pipeline/combo/cli.py b/pipeline/combo/cli.py new file mode 100644 index 0000000..b3a18f0 --- /dev/null +++ b/pipeline/combo/cli.py @@ -0,0 +1,47 @@ +"""CLI for alpha combination.""" + +import os +import click + +from pipeline.combo.combine import combine_alphas, COMBO_METHODS + + +@click.group(name="combo") +def combo(): + """Combine multiple alphas into a single combined weight.""" + + +@combo.command("combine") +@click.option( + "--alpha-paths", required=True, + help="Comma-separated paths to alpha parquet files", +) +@click.option("--combo-name", required=True, help="Name for this combo") +@click.option( + "--method", default="equal_weight", + type=click.Choice(list(COMBO_METHODS.keys())), + help="Combination method", +) +@click.option("--output-dir", default="combos", help="Directory to save combo parquet") +def combine(alpha_paths, combo_name, method, output_dir): + """Combine multiple alphas and save as parquet.""" + paths = [p.strip() for p in alpha_paths.split(",") if p.strip()] + if len(paths) < 2: + click.echo("Error: --alpha-paths requires at least 2 comma-separated paths", err=True) + return + + result = combine_alphas( + alpha_paths=paths, + combo_name=combo_name, + method=method, + ) + + os.makedirs(output_dir, exist_ok=True) + out_path = f"{output_dir}/{combo_name}.pq" + result.to_parquet(out_path, index=False) + click.echo(f"Saved combo: {out_path} ({len(result):,} rows)") + click.echo( + f"Weight stats — min: {result['weight'].min():.4f}, " + f"max: {result['weight'].max():.4f}, " + f"mean: {result['weight'].mean():.4f}" + ) diff --git a/pipeline/combo/combine.py b/pipeline/combo/combine.py new file mode 100644 index 0000000..8157a1a --- /dev/null +++ b/pipeline/combo/combine.py @@ -0,0 +1,85 @@ +"""Combine multiple alphas into a single combined weight. + +Future combination methods can be registered below. +""" + +import logging +from typing import Callable + +import pandas as pd + +from pipeline.common.schema import COMBO_COLUMNS + +logger = logging.getLogger(__name__) + + +def _equal_weight(alpha_dfs: list[pd.DataFrame]) -> pd.DataFrame: + """Equal-weight combination: mean of all alpha weights per (symbol_id, date). + + If any alpha has NaN for a symbol/date, that alpha is skipped for that row. + """ + # Stack all alphas with (symbol_id, date, alpha_name) as key + combined = pd.concat(alpha_dfs, ignore_index=True) + # Group by symbol_id + date, take mean of weights + result = combined.groupby(["symbol_id", "date"])["weight"].mean().reset_index() + return result + + +# Registry of combo methods — add new functions + register them here +COMBO_METHODS: dict[str, Callable] = { + "equal_weight": _equal_weight, +} + + +def combine_alphas( + alpha_paths: list[str], + combo_name: str, + method: str = "equal_weight", +) -> pd.DataFrame: + """Load alphas from parquet, combine, and return combo weights. + + Args: + alpha_paths: List of paths to alpha parquet files. + combo_name: Name identifier for this combo. + method: Combination method ('equal_weight'). + + Returns: + DataFrame with COMBO_COLUMNS. + + Raises: + ValueError: If method is unknown or alpha grids don't align. + """ + if method not in COMBO_METHODS: + raise ValueError( + f"Unknown combo method: {method}. Options: {list(COMBO_METHODS)}" + ) + + alpha_dfs = [] + for path in alpha_paths: + df = pd.read_parquet(path) + alpha_dfs.append(df) + logger.info("Loaded alpha: %s (%d rows)", path, len(df)) + + # Verify alignment: all alphas must share the same (symbol_id, date) pairs + keys = [set(zip(df["symbol_id"], pd.to_datetime(df["date"]).astype(str))) for df in alpha_dfs] + common = keys[0] + for i, k in enumerate(keys[1:], 1): + if k != common: + logger.warning("Alpha %d has different (symbol_id, date) grid — intersection used", i) + common = common.intersection(k) + + combine_fn = COMBO_METHODS[method] + result = combine_fn(alpha_dfs) + result["combo_name"] = combo_name + result = result[COMBO_COLUMNS] + result = result.sort_values(["symbol_id", "date"]).reset_index(drop=True) + + logger.info( + "Combo '%s': %d symbols × %d dates, weight range [%.4f, %.4f]", + combo_name, + result["symbol_id"].nunique(), + result["date"].nunique(), + result["weight"].min(), + result["weight"].max(), + ) + return result diff --git a/pipeline/common/__init__.py b/pipeline/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pipeline/common/schema.py b/pipeline/common/schema.py new file mode 100644 index 0000000..f13d90a --- /dev/null +++ b/pipeline/common/schema.py @@ -0,0 +1,33 @@ +"""Column contracts for pipeline parquet files.""" + +from typing import Final + +# Required columns for data parquet files (daily bars, alternative data, etc.) +DATA_COLUMNS: Final[list[str]] = [ + "symbol_id", # str: internal code like 'sh600000' + "symbol_name", # str: stock name like '浦发银行' + "date", # date + "open", # float64 + "high", # float64 + "low", # float64 + "close", # float64 + "volume", # float64 (shares) + "amount", # float64 (turnover in yuan) +] + +# Required columns for alpha parquet files. +# Alphas are position WEIGHTS: positive=long, negative=short. +ALPHA_COLUMNS: Final[list[str]] = [ + "symbol_id", # str: matches DATA_COLUMNS symbol_id + "date", # date: aligned with data dates + "alpha_name", # str: identifies which alpha (e.g. 'reversal_5d') + "weight", # float64: position weight, signed +] + +# Required columns for combo parquet files. +COMBO_COLUMNS: Final[list[str]] = [ + "symbol_id", # str + "date", # date + "combo_name", # str: identifies which combo (e.g. 'equal_weight') + "weight", # float64: combined weight, signed +] diff --git a/pipeline/data/__init__.py b/pipeline/data/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pipeline/data/cli.py b/pipeline/data/cli.py new file mode 100644 index 0000000..4d9e41f --- /dev/null +++ b/pipeline/data/cli.py @@ -0,0 +1,44 @@ +"""CLI for data download phase.""" + +import click +from datetime import date + +from pipeline.data.downloader import download_universe + + +@click.group(name="data") +def data(): + """Download and manage market data.""" + + +@data.command("download") +@click.option( + "--universe", default="csi500", + help="Which universe: hs300, csi500, all (~5000 A-shares), file path, or comma-separated symbols", +) +@click.option("--start-date", default="2017-01-01", help="Start date YYYY-MM-DD") +@click.option("--end-date", default=str(date.today()), help="End date YYYY-MM-DD") +@click.option("--output-dir", default="data/daily_bars", help="Root for the partitioned dataset") +@click.option("--symbols", default=0, type=int, help="Max symbols (0=all)") +@click.option("--chunk-size", default=300, type=int, help="Symbols per durability flush") +@click.option("--adjust", default="qfq", help="Price adjust: qfq, hfq, or none") +def download(universe, start_date, end_date, output_dir, symbols, chunk_size, adjust): + """Download daily bars into a month-partitioned parquet dataset. + + Writes ``{output_dir}/{universe}/month=YYYY-MM/*.pq``. Point ``alpha + compute --data-path`` at that dataset directory. + """ + stats = download_universe( + universe=universe, + start_date=start_date, + end_date=end_date, + output_dir=output_dir, + max_symbols=symbols, + chunk_size=chunk_size, + adjust=adjust, + ) + click.echo( + f"\nSummary: {stats['n_symbols']}/{stats['n_requested']} symbols, " + f"{stats['n_rows']:,} bars, {stats['date_min']} → {stats['date_max']}" + ) + click.echo(f"Dataset: {stats['dataset_path']}") diff --git a/pipeline/data/downloader.py b/pipeline/data/downloader.py new file mode 100644 index 0000000..9eff224 --- /dev/null +++ b/pipeline/data/downloader.py @@ -0,0 +1,179 @@ +"""Download daily bar data for a universe and save as a partitioned parquet dataset.""" + +import logging +import shutil +import sys +from pathlib import Path + +import pandas as pd +import pyarrow as pa +import pyarrow.dataset as pads + +# Reuse existing downloader and universe modules +sys.path.insert(0, str(Path(__file__).resolve().parents[2])) +from data.downloader import download_daily_batch +from data.universe import get_all_stocks, get_hs300_stocks, get_zz500_stocks +from pipeline.common.schema import DATA_COLUMNS + +logger = logging.getLogger(__name__) + + +def _fix_baostock_columns(df: pd.DataFrame) -> pd.DataFrame: + """baostock constituent queries return (update_date, code, name) — + detect columns by value patterns rather than assuming column order.""" + cols = df.columns.tolist() + result = {} + for col in cols: + vals = df[col].astype(str) + # Stock code: matches sh.NNNNNN or sz.NNNNNN (possibly with dot) + if vals.str.match(r"^(sh|sz)\.?\d{6}$").all(): + result["symbol_id"] = df[col].str.replace(".", "", regex=False) + # Stock name: Chinese characters (detected by byte length > str length) + elif vals.apply(lambda x: len(x.encode("utf-8")) > len(x)).any() and vals.str.len().max() < 10: + result["symbol_name"] = df[col] + # Skip date column + return pd.DataFrame(result) + + +def _resolve_universe(universe: str, max_symbols: int = 0) -> pd.DataFrame: + """Resolve a universe name or file path to symbol list with names. + + Returns DataFrame with columns: code (symbol_id), name (symbol_name). + """ + name = universe.lower() + if name == "hs300": + df = get_hs300_stocks() + # baostock returns (date, code, name) — detect columns by value patterns + df = _fix_baostock_columns(df) + elif name == "csi500": + df = get_zz500_stocks() + df = _fix_baostock_columns(df) + elif name in ("all", "full"): + # Every listed A-share (~5000); already (code, name) with prefixed codes. + all_df = get_all_stocks() + df = all_df.rename(columns={"code": "symbol_id", "name": "symbol_name"}) + elif Path(universe).exists(): + # File with one symbol_id per line + with open(universe) as f: + symbols = [line.strip() for line in f if line.strip()] + df = pd.DataFrame({"symbol_id": symbols, "symbol_name": symbols}) + else: + # Assume comma-separated list + symbols = [s.strip() for s in universe.split(",") if s.strip()] + df = pd.DataFrame({"symbol_id": symbols, "symbol_name": symbols}) + + if max_symbols and max_symbols > 0 and len(df) > max_symbols: + df = df.head(max_symbols).copy() + + return df + + +def _write_month_partitions(df: pd.DataFrame, base_dir: Path, basename_prefix: str) -> None: + """Append rows to a Hive-partitioned (month=YYYY-MM) parquet dataset. + + ``existing_data_behavior='overwrite_or_ignore'`` plus a per-chunk + ``basename_prefix`` means each flush adds new ``.pq`` files into the month + directories without deleting earlier chunks' files. + """ + out = df.copy() + out["month"] = pd.to_datetime(out["date"]).dt.strftime("%Y-%m") + table = pa.Table.from_pandas(out, preserve_index=False) + pads.write_dataset( + table, + str(base_dir), + format="parquet", + partitioning=["month"], + partitioning_flavor="hive", + basename_template=f"{basename_prefix}-{{i}}.pq", + existing_data_behavior="overwrite_or_ignore", + ) + + +def download_universe( + universe: str = "csi500", + start_date: str = "2017-01-01", + end_date: str = "2026-12-31", + output_dir: str = "data/daily_bars", + max_symbols: int = 0, + chunk_size: int = 300, + adjust: str = "qfq", +) -> dict: + """Download a universe's daily bars into a month-partitioned parquet dataset. + + Streams downloads under a single baostock session and flushes every + ``chunk_size`` symbols, so memory stays bounded and a crash keeps the + partitions already written. The dataset is rebuilt from scratch: any + existing ``output_dir/{universe}`` directory is removed first. + + Args: + universe: ``hs300``, ``csi500``, ``all``/``full``, a file path, or a + comma-separated symbol list. + start_date, end_date: ``YYYY-MM-DD`` bounds. + output_dir: Root under which ``{universe}/month=YYYY-MM/*.pq`` is written. + max_symbols: Cap on symbols (0 = all). + chunk_size: Symbols per durability flush. + adjust: ``qfq`` / ``hfq`` / ``''``. + + Returns: + Stats dict: ``dataset_path``, ``n_symbols`` (succeeded), ``n_requested``, + ``n_rows``, ``date_min``, ``date_max``. + """ + constituents = _resolve_universe(universe, max_symbols) + symbols = constituents["symbol_id"].tolist() + names = dict(zip(constituents["symbol_id"], constituents["symbol_name"])) + n_requested = len(symbols) + logger.info("Universe %s: %d symbols, %s → %s", universe, n_requested, start_date, end_date) + + base_dir = Path(output_dir) / universe + if base_dir.exists(): + shutil.rmtree(base_dir) + base_dir.mkdir(parents=True, exist_ok=True) + + buffer: list[pd.DataFrame] = [] + chunk_idx = 0 + succeeded = 0 + n_rows = 0 + date_min = None + date_max = None + + def flush() -> None: + nonlocal buffer, chunk_idx, n_rows, date_min, date_max + if not buffer: + return + chunk = pd.concat(buffer, ignore_index=True) + _write_month_partitions(chunk, base_dir, basename_prefix=f"chunk{chunk_idx:04d}") + n_rows += len(chunk) + cmin, cmax = chunk["date"].min(), chunk["date"].max() + date_min = cmin if date_min is None else min(date_min, cmin) + date_max = cmax if date_max is None else max(date_max, cmax) + logger.info("Flushed chunk %d: %d rows (%d symbols done)", chunk_idx, len(chunk), succeeded) + buffer = [] + chunk_idx += 1 + + for i, (symbol, df) in enumerate( + download_daily_batch(symbols, start_date, end_date, adjust=adjust), start=1 + ): + if df is None: + logger.warning(" %s: no data", symbol) + else: + df["symbol_id"] = symbol + df["symbol_name"] = names.get(symbol, symbol) + buffer.append(df[DATA_COLUMNS]) + succeeded += 1 + if len(buffer) >= chunk_size: + flush() + if i % 100 == 0: + logger.info("Progress: %d/%d symbols", i, n_requested) + flush() + + if succeeded == 0: + raise RuntimeError("No data downloaded for any symbol") + + return { + "dataset_path": str(base_dir), + "n_symbols": succeeded, + "n_requested": n_requested, + "n_rows": n_rows, + "date_min": None if date_min is None else str(pd.Timestamp(date_min).date()), + "date_max": None if date_max is None else str(pd.Timestamp(date_max).date()), + } diff --git a/portfolio/__init__.py b/portfolio/__init__.py deleted file mode 100644 index 2b063cb..0000000 --- a/portfolio/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -"""Translate signal values into position actions.""" -from portfolio.builder import PositionAction, ThresholdBuilder - -__all__ = ["PositionAction", "ThresholdBuilder"] diff --git a/portfolio/builder.py b/portfolio/builder.py deleted file mode 100644 index 92edd3a..0000000 --- a/portfolio/builder.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Map signal values to discrete position actions.""" -from dataclasses import dataclass -from typing import Optional - - -@dataclass -class PositionAction: - """A target action for a single stock on a single bar.""" - - action: str # "buy", "sell", or "hold" - size_pct: float = 0.0 # target portfolio fraction for buys - - -class ThresholdBuilder: - """Open on strong positive signal, close on strong negative signal.""" - - def __init__( - self, - buy_threshold: float = 0.02, - sell_threshold: float = -0.02, - size_pct: float = 0.95, - ): - self.buy_threshold = buy_threshold - self.sell_threshold = sell_threshold - self.size_pct = size_pct - - def build(self, signal_value: float, in_position: bool) -> PositionAction: - if not in_position and signal_value >= self.buy_threshold: - return PositionAction("buy", self.size_pct) - if in_position and signal_value <= self.sell_threshold: - return PositionAction("sell", 0.0) - return PositionAction("hold", 0.0) - - -class RankEqualWeightBuilder: - """Rank all stocks by signal. Buy top N% at equal weight. Sell if drops out. - - Called once per bar with ALL stock signals. Returns per-stock actions. - """ - - def __init__(self, top_n: Optional[int] = None, top_pct: float = 0.2, min_signal: Optional[float] = None): - self.top_n = top_n - self.top_pct = top_pct - self.min_signal = min_signal - - def build(self, signals: dict[str, float]) -> dict[str, PositionAction]: - # Filter by min_signal if set - if self.min_signal is not None: - signals = {s: v for s, v in signals.items() if v >= self.min_signal} - - # Sort by signal descending - ranked = sorted(signals.items(), key=lambda x: x[1], reverse=True) - - # Determine top N: explicit count or percentage of available stocks - if self.top_n is not None: - n = self.top_n - else: - n = max(1, int(len(signals) * self.top_pct)) - - top_symbols = set(sym for sym, _ in ranked[:n]) - size_pct = 1.0 / n if n > 0 else 0.0 - - actions = {} - for sym in signals: - if sym in top_symbols: - actions[sym] = PositionAction("buy", size_pct) - else: - actions[sym] = PositionAction("sell", 0.0) - return actions diff --git a/reports/ic.png b/reports/ic.png deleted file mode 100644 index c6761184065ebad932274f5ddb9cbc951b28d27e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41067 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 diff --git a/reports/pnl.png b/reports/pnl.png deleted file mode 100644 index c30056abddeeb11d60a43745f68bab31a361e100..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 60056 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

=>3pXQoO?Cdy`cuitF z=lpf-d=5Zbmd_eIOkKU_cgV+6v&zrP&VG}b84Db#rssjnzQ4)e?bAohl4$2r3fT0u zJ`$N>%@2+I&fqy6tS)^<5qLbD!3SLRYk7HQS((r`1C=Yx-*$m9Wfc?<(+N8?0^`Ll zFlh*o1aq(bBxUpIq0c8=!|k%>1;f5>+Vls!B~ zDb3Sm)_X~rBY1LZsu;ek$;!$)@7Zv|&>q7;0Jgu*_W%`mz7*)=I8D92!cA331kiN+ zd#H{{J5M*5l1n?JcGjt4vHoPeh0eJ)cz@sHVTv%3y2Mt9V9g$SFbk7{Fnhlo)0w-@4dN=SB?Gyz9!|QOadC=g(H_+V3^oXbG6zU_A<4k$ zJ)s!0r51X5x#{Dh6qkiiYV(mVPq8j$0#4S@zkk1KmokF2Q62(hy#<0Ut^`%Lz6tn& zt&>wbSP$K{aA;^K$adr9jw$_nfhSXQnP*%oX{|r|tpMx-0de2kBR(LK}$=^`A}-(pc$uRo{NpZi^wd+So5FX5*IG8 zNdv--YCMisadJmqV(`N)PR^_Ly&(CuS|uBWVFFga^o2x^wE$1m53=QB)03G6myo?< z!hJD6*u4?B2b5P>5zptn~Ty!rJYtjJ~UPQzg8~a@5kA z4V>#j$Bcc6oGR!%XWXeV7#{QR_698G5W<3kxj-h6dn!SyLl`%>EXaXqVsUo7S9$z9 z7j`h?xd`m69RnXzoT~BNmiivp+jGqZUif1$*PDB8>;(Qx@6<28AwfYwG5;&Yxg8ifQJH=; zzzj4XYJAGeTivUtHuhrvQ)lqh5OV1h+y}wa*lXeP!?*mGsUja#r-A)?ef!BK_;vH< z5PT(N<)*LChWQ*P1u@88bTLW*6HcamR`Rb4SQF9$l;pOgrlq5^c(T!e2c7;BX=uU} zX{mwdN0o~);@NlYfwhRb%*$w5?>#bsHXem%aG(ACI~o@$}>{9vivAahp+mRxpcSzv!hvb{IrK13b zudG#9S0C1_S4Z}DbuE$%0Km+_%*fqvObbrls95iX8&;JM#9fHt720F9Ro8~jf^0v4J>F(B8?)%9==nBXZ#h94Q99QU|A^YobTiBa<(m_g%1-yaUM zOA2X)(a-uH7#aF3e}IC(JjgLM4>6Mz{eTj{O2pt0yf&Ngw#F@Do0j1!V|%;1jh6fi z!|Kv)fPxTzjX7?B+ri6a5Pff@fJFc-NlQmpY}xe+S#b~40t>EHkF~Yg6Zg|ZVc0TW z(bj)SpeTvgvKx4jZO;M5h2&(;!2y{~lvn{TlLhBhP`9t|Fjvo@E_K9e-Jc%)@gqB? zloPP-Y5bPR1quL6ZQ9z_w$LAVE+D~35^%ng!2%FLM!@m2s6g?*#8a^hmXEZ=9@~MKoYV*T3`B5UXX3w) z2wm`I1hKKRt1DQA(pxGOdzWuybo8dQwDf2Sz}bIx_Wr_`FJI!4lc~4PZpn~5@$-{_ z7D&?K;z$?sw|;JU4SfF|vb(z*koJKYf!L2M89dhzo|OKkc%oEgJ9mq2jmawOEXz-2 zPBASlt+T&B8P%br5yZ%@J(?aDhwOxAtV@}hnPua7hX!B%|NeH171N%l-dEQ^mcPrj zqvJ$jE}C^zk(eEKXDj1-MNBy*BH!!VSPV zJ}v?aoM#892Pn>>IsP$JxnpG*R`rNo8{mhwKmi`=?prexv%3qQ4!k%6Vc2?LYIVFA z2SYgYip)j%pX@JSupX%HU&@CQKX4oV%Us3<47d8=oaO`T;L^^!afZ*3rm z7nJ$~Qxbqx5WP6v!DKlc2t$}T%+}KMOP$;SIVLpNS@5qJ@XNgHMv{c3iF={|rM6Y& zaJ2n%yvofcs{F-&d`m8Ksdt03xF%Bw*afZf zhOEZ9*y)pn8p9hz84^m#&1)YZ6qiqIkyApP*t_Rmr&}Fcn=K@GhO^(lUrOnvLU3ON zVHBB_g(J}~|AChB!L=6bHEN8ZIeZO#9lIazI#~b53WW_1`U}W=hZ>x{ys6g>-^DUL zREk-kB4Yfo0U&n))=Z$*B{T>zfN|KY$^*3u_?mgcZKLkMd~LF9sQ*7Us|d(2GP2k1 z3g5t57DWMm;M#s5rau_)XAFfC#Uop$+th1xe!=3o9&86W`0LRs^ycY3I~^4 z-b_UB6bfpj!5ymCNl7ZwfK0HsI6wO*uC3P{7-MLfvN7WXr_#WSfaQ9S+{POMrQu=> zA0NKOhA>c}2vhWM3nXB^^0nmc|tmj(~sNNCoE2Hm0g#)-rse`wYzU8|clD_-%09h-7WC_@8 z2m)ZBeWkD=2H+N^2Ea|<1<9%wIM^Eij^;1q{uz>ADbIu2ZqgTm7Wq-8L!Ysr8RDx0 z;Vi&40YgsZl}_S)1}JiDo|U!z9t@-d;M?^^io!pU?4@go@-1g|p_MrE(WYCtXh{kr ziRw45$-jzOTX$;;JjG)WtQkf2w zntv_Jl>R@@`;=Kl$H@3%%s%c~A>KSUEJq04LjK>!5-KxH3oHWCH)vC3I(F3Ee0cVE zQTx+`_a7?n{T*BGSbJ!G>IQLKw4Pg~+^ln(fC(1Fk0KJa@SD8h#2i6k^*2*kUgvb& zW+=vm+OM?dB}T4{MYulx?|L79ek~iD%(If)I>TNg(e;U;HJ{~77EA1(s6i;eQ-Sxh zR}RBpA+ru zE1~i;n7x|+uEna<&A&jPgwO|k5=2`LS*9o<^i#(f*^J1L@c*t*;alt867|D=;>s9z7~bJ7jUtlQ8~?cx|Ogv?p<=(gwgqxQ$=asW#^60NdY{L*-#o` zexSdv%5jA+DS!Wh`OmfU-i06IV(<&HgFZ$MuZfR}rI9r)KbtOU{h<%hvkBC|sOkT4 zC{}iQ9uneFI0R|YZ$4EPNI%JZSZ$n7Mm02P|pk z!ZWW*yEaI&q5_hzv(sgsIl{DtHSENhn4m2KYOZxzz2dJ0EFwWB632%jDjSt?8g<2iNG3if+VWDFsDH z(aUYmtf8Pp?A;gtU4)jZ2xMxmEkQ=8KkdGLDPyH(mB&F!lO%i!5o)1XlK zsxMq`0gwD!%;3amdIWBptrIxo0BD%VeXblw43TZfIL}73=VO z>U{vfnzc&dXD4%GsN7e-n5WJ8Y0sYYq?_<_;dNPa2aO9xtiUR_D)MI;iB>J2@+o|d ztZ@#Sw%+STDi%l9^s-4L$>u)SlaQ79-vi5hbf@AGKR#BqLVmNoNN)#kARl8;(Z@3R zoonS>N4>xI%J0bR^TIYr z=iF8$se13BH6v;ih)NF+1+BSV{~gDaZT$|Sua%}2OUytZ4wfiS)~>74Q-ROgKepW16e zYm!R*&tUI$ix+y4B>n@P&mBvzpYDg{%WmLnpwg*woMQr#M5>iUAPx(HXYsFeXCYYj?MneH+*%Ni+2 zO~FIFiI1_uo#CuE>A7Y(aJGajB|CuMMKF%=tn=ta)HsLQ=i05ckCH!vf|$5(z2=Es zqlG8QZivf>gmxkU4uIW}iM7T}bBF1uS{LYH1MC?1_5D{@78m>$l5-FWWl<#fEHD0O+*JI}8f z-`gZV+7GnchLJGdC~c3*3zvtFAxnAIIh}FqGGyIzpRP)peZR@o6a<0*2%m#!hfmFa zojI%rq`cKHwuu4z{ZTk2>@ahgOl_Fv*t2#6l$2PBoh4e-kg>L`=4w#IJ^HMt9ejvB zmFbpFBpw&c)7R@WDnuy4jkuW>?u}d5U_YSu^P%-~Ms;H$a;WHhO2yA;h(472sqnuG z^fsGP&d4A?`!mB@1dMRO-TE)T)?u z(R~eO`;~{N(aTAyt|b|zhaU;=+c1c6wb^8lgnnj+g6)4R-u!nx<9egmv%F^1d96nI zsBFR;q?PCNn@N=h4C@*q9B&EJK{gkCY#*x7wh+FvgHsUulNO#UxlJh6ZHF@(K!mTr zEr{6kRWh0`vqKAVKD#FqIQqWgCCl|Etq-NSCMy1(krLd`h%=ALNU}kux`***r8SK! z@_q05u^Aq=cPV|GQml=0Q9rV^iOsk@y&Rzx6G;Q=^^#>N%-*R^_WO z9xXwGtdcrQc=5QAm$L~GH2?1MWm#wPZXtA@)^s4g65lMD=L9Xroty_`xRTZt-HEI7 zl`JV6PHFC#S)J^IMsD97Vkzr`3K1=QLNn}FIg(xP9Sw2 zFmH*aUf|8OvFJr*A}f+bV!I#lSF5MOdvt)mn-iH9#?@5gvO|=6kJ2YA##8F%rgOfC79*b)iPCDXyCV z(j{aE6jaESkdADje}MV@rm+v)P|$G42P|l4aj(+Lni&3WK0K>I)R(?MjGH9ZP>g7d zo0HIo?&!`gRU`vl+r$Xu^6J~Pa>%!~u^tqvHLhaIdcb0Wl_!eK^HCf$lcSuv=}VX$ zdBVa(-^3(+FNTEpJNBj{86MQlZfzO>y9yR`emW8Hf-&VXy30Dh-(raNAJC24xwud) zZx|R5b{%UAvBJjs>zrc4&>)ui|<6jYqhAy*9)Ux*Sm`kZSJSr zrP`veo)k~z|B+Py?A?hJ)t(Eb%Ee(%qXh?(xnFq)VLUP!J?5beLG9W2_EX)rWjLB!e2YXQRQdb#53)n_u^Ap48@e~HR)J?^elM^DjB7FaAJiXlb+pc37N*kqYXAJr+vH1-*Frb{S5I+` zIP>OS)t6YH0J_;iTLhy=j$JYdB)(-u$RGu zpaK23+m}bxee*h7t_Mft@3movtF|m2i52^2VW;I0nx~r;QsX)7;h)n-OK2tLoCv;;ZuO-oK=kcASN}V!~mIc~l4P8qw^QOYj zvGY3m2&q=LfuC$Ye(|YJKMJr~xFf&-{5@+wEcom*uIbOUo}6$x2nr*m!O<3@9$m7J>vL zrRn7I=4?3nRA}!Uc1e3K78bw=M7fHs#C3uwKaY zux24oaJ}Fq5``!PWdd(u5P|;vO?anpjkug_{Pv;1MUnm zSevaId5}eme9(hb?|#%e4ljj!e}d|6w6Quw4o9mKi_CZRzV9d~hBeu|ugD9fA@)-(Etktn34JzxF4DR?j3UR%4j@cmFq`9Ox*@1L z#Tg@8IzI76h+B)3*x|F+C&KF9Wd$o$&w&;FRw48=m{_XNUHNJWEswcRPgEy7ju!FA z?;wby(lzY=9dz)S8|nlUQV2TaNyB6FgY265ls9E_+Iq?ilChdI124m~nz*B7JwSuda1#O(Q z8JfL5&;0L^iXn(0kS;Sa&c=!b_HO`xsUh~yj@ADodjoz)_|o~C{`^8H@}@|asAYdQ zKU&dF9M-~Bv)PkGcDg@a)dJ)$(fks!^ih#*0<+vRt{98ac0?JpJ8IxzuMiQXLPB=8i#?vQN7atw(`j?} zMowI_kV8PXZm`JckoZ%3=1d{JX}{Mq2*id*>xbF|3gYE1QJbz+k=G{Uy>CQ z53B*n%8pV>awsUdLI>K!9lLX*!N(YjvC5Kj-B{}G4-Z37cc~lRWf*Q_yY>E9u87db zG@_=cPEyc1nv_fx)Sfc#;wp4C*`-M!{=XP<usWhzf~YbM%Lz5-!st?OUZnSGTu6uzWVx^K*MaD8^+*(urKLB@$!4 zkF~E;u6i-6F&b4B8xSUteu%v`)6_49DYc5fI}~T-b&-|sExAT0a3`tvHdjV@K{C%+ zE~8mRbB*r6NZfiRP4i^u+`do(js}OvCXp#VN|ykSYs|A+Qgp(yX+qtp?W4btRE08G zu8E)>Wr30I73i>I>?ahI`F&twdp5frU)CRe*PeM4X%BV~%;$^cgsb;$(v@GzK+SqM zWiVm=-gV#sPh*>)C$Hn#P5vhNVHtiHnk>#zFYL5rqDpFv^5edL}-i+09@ z7NcxAzabMISAV!`zhE_Bq+yIOzUk{2pfmf+ZN~I6Ixq4>+B3b`lhRSvIe~SBDL&Cw z#_5&Daa9A&E=B2M6l1kAeZH*DYsabbh0I7EIW;w6(U=zBjk>WVf1tm8dC%>aOyQWt zbR`V5^V0hFNF;YjMPqi3oQJZN5jzB=WL8g^M^mQ4x3K5|*;p9Em_tEdTfIY}eK99u zi~ic?m3wTX;~m-GuhN;019s{`BqfiCf8Z+_Gn;`C#1j-xVbruw?On8ce4oq)K?>sh z=(U&0;8}2gDl;RQo?GfR$6$9g=IrHCFlU; zYXcP=Iq1Q?ab@^*mEWwFBq|LR-=6X?!!I92_m^4sp>kg7-#bLRvyknD?k&~R=V z_mj)~_H$oJ;MUoM;^Blmki>S?7ut2CDhgXHdOk&$;0Fh5_cG5So6?^+>K-K*YK(}T z(moIHEujqBtm)}gZH=X(X!VM`bT2~TdYPT*j0C4a&DNAB393AB{r&Q-`(*31@XxLO zd^o%0kA@m*7R>9QMV6dHt>{^)-?V7#@P*e@d^w}7Y+8)8>aj{^E{S|Gg=1X3=Z z8MUmz8(dsm-&;sCl|WRjl$3zeQu3qrJ~{0q)x_?SaA4Ny6tE}$RiELoR~&Eid@Tou zlvv-S!JQfN<5Y(dK)BOl*of=S&n!I>39ya{#`@Yw*lpUq>}x@uwixOaHJpk-2r>L& zRT{gjxd1wA^ICa97Bbal#9c7uq3BIiEg{3}_!Sdi>{1yidz&MwSre$beszj}JP6}X z{{H3Oso4nOXTJ=0J?iwagL^hV*ib6GbN1s!a!be(CBXpg~h-W>f9l9g#zmL8un=b&|$K`AE4=T9>ibnXQC zdP{yOF`%?!yy|d6Zz~uIve5NA+ibAFxP*ZDrjXSwSSTBYcqZ*W4P+z#`|EWu_BUzm6etFx68g^!U6*1 zpvMQqSHVDO^ZYVFk<*OWL!i`jcX#(XTnGmu0y26r_?4}#riMz!z*CsS>1GR7vWSpS zll7C)YCh0bkBK4Plpsk|f#p&uT`Lp@uxfJ z_civou{#uL*K?d|PBCkH=G#Je>{oH~BtkMFl&% zypa-Tw(Rf7p)p&rL*(biiW3$)l*Tcf{th{C);FFCA#~hSc!~&}75}!ly{W0^?rB3; z%`h5wl2g~sJlio`cwjTI*;gb;cT{PQ(0z7>T{RPJzh0$g~NYf{My~u|2x-=vNA2b_u9R+a}BG zKV*oDi^peV#Db1D&>9jR3oC*f1OL-kePGc5ubjk!EcnR>2Ny5R`?uIfTU%NZ&IFGs z$^UmBoe!GCGBTzNn-_FdYVm=f@Om-|I2g6Y}MT$zP+|y=%Up5x`8pskfE3WeW*-pA3S7)!Bje%f33`M~FUek&@~E z_DxP+9?@?_L`0;dq!cu0OX!o4oz4324IkEr4<3Wm`E0_|4EC}lI>~- zFOkvyi;Hr~Xl>E9vSQ(-#(EAPdM5|2XU!!MGy~6$1L%c6K3f>qdQi{aZiCCNE9PT( zcS74|<5pzv?OGd9uHoj>8r7{b4&hTZeb<2k!M&wG@+q=tK^2a{69|~n<4?cfRv4fOwem`zn@FkyPeoIdXp^<}s zG_4Xq0@~KVg6cuA^4lF$luC94a)n4L?yKHRK%@g?ScX8}hp6)oA-u9OpOWDqXA|qJ zob4DQ(s?5!F8*a=34cdx)xs9;LeU)o+0J@FgH@L*$)Q`>E}b@U1`IeS>ADqJ*y?_M zgpnN|QF{i|`#w|Jx$;+8e^GuQM0?|@YP?hCWU|)Jj~Vd1krnoI^hOM5hB@aZUH-8o zamIFDX@~oZ>SNN2>+xY~&ST0^)O(vXU?*j69w=fDr{>PXFn1~nYSv#l?W~ww(3%7sv4IA95xB}^ z=rLghgrML;$R4IifB%b$!&RL)bGghx&z{iU&pqwe0g(04VYBYmW%>9@ff;ajqycI| zD@bkFy!V{7>p~nJdS*ZPGWufH1P2<|7=4~D_55suGh}ZGbld&JWVj-XvlL`sIu{L& zKEe_U-aQsRxQoNE$?(Xdr0b;+Zy+9I`@5uNZJ}ugy>o^ttkpXtyrm|<(nsn?_i_lA z<1w+7p#pu9niA93j(&A2oZWIpuTzE5Vhclr2Yg>&(=e#s_rR$qU6QWUrXLr|GpeBi zU$leU1Q7HoH#gzAJA@I>1C7)Ra67{S+_&)p$Yl#C+FAgfbaxj5(!}Tpat;(mm=DC; z^+&(xvU5mgBV*!%OYY6s{w5o}_cP}@;u89`EJsM(LfDeGRvA7`R;Y#Fn0pCfHRy@3 z7+-Ov6JxP|=zE_LDUP#5peUD)f3fkt+SqA1EwCP``*y<3+AwTGsjS?|=7qtl!B#s! zMIG1U$a&*3KzC67DcrNx2x3o5WeQ=Hd{J)VEm{5znk}C<&7$6Xsg@kHNDY`vrR1lB zlT-pBJpZqcr_-fY@=6kFeLo}Cd)(OuK0WPJ70%53Ku6iD{e#upH+-Va!tfiApHYM+%KZB9 z1}GB9{@lq3lN>c*6_AZB4wx|1M)eiog-oCp?0%P=SZ?7~kV~@Ir%*LptkSp4S zBF$xCyET%E2^VvzU@A9E5VM}^L(+(Xc%b^R8gNt-v1|R<*!B!2t2!OcBKX*flKaCW zs*G+$UtE9q`KYCq3&~DtblxR?X+6;_CAM`?8k~AdPd&nVlz}?qM?(VxH0M$47JKz? zD;PT(Ri@KQZe&$xo^0e!E-Zuw+}4Z0q2m=CZ~u6587-eGTXApZ@Jvy$xKQ6r9;I02 z^b9b6!F(MsDcNdm2h{j98^HieCo;qCniM&RsH`bpv3h?3h$7+rl+|6Z1&MPH*UM%d>7bc*c!a15k7LOQV5Y=Ldnu`~!j3jb=^++sZN)y9Tx2Ttm zp9=6!yhy#D)sOW_0?~(UL^TkO5PD&OcQ}L7gA}*osMf>DGaVOOpU$3NbWF6gQ^RJr zY&TOW=W})ZG={{mfxP}k!8Q#AG($^lbt_4u$nc8eF%cOiUEDOG7hEa=2yiQu+#S%Z zCyb)JNRu|DyTVGKMdgAAPA%@*&)iqm01X?@=v`ZRgl_=4-I3{MwAAN&_MjaE7zL|= zQgR{;CTF+2%497}jcGpfPj{-|5V zm4#p`)lG+bis_Je|0p5A^r^2)kUO8>zfL!{2q#&_-ylcbs(zARZtTkw|K2M427>so zKt7oteP)Q@iPj~TCg>?ixTG8~lE4zJh2pUuP#4zY`x&gBy>c4?wzz4h7ETKL^pV`1 zLGtcw=H}~f-+U))7-spE?jrhN^o-SBoB{5$_o4XM%^M-4WWEl5>~wbp*=OjG-2&)g zS28Tf>QPdW^oAa;f5CMm$=BAIhPzKcx%yH~hope%#w?QNj48eCe{UEbkSTjuP$4>P z`ySW*i}&j)%z8d2Il(7G za2P#EJFd67GI7E%p|ovr0rX{7f7n-hxm<-{0TVTVY;0m$!@+MG zPAD!8s|~AHP0Jo*#Zl!D+^R6glM&tu`c#mGS5aS1C;shSxNRiRQSK@xv=N7QV%Hdqq^U$ri5BeOcDTm}}1H(GUbdT-|& z*<@VazH1-R9&dyqK8(>{LBF?Rg7|8sl<^}T*BT>uY6wgc1uc%g=jl4DJqBLQ=<;#z z9TE3my^|sQ%gNOa) zqlF*r*7#UHdKPoR^ag=aY~on#dP|E=H3nDjv$Xv}F0s=MIuwVeb~#a|v=UViR?Bj& z;`m;g=nFO_LS5&C>BK0hPX7$OO`MF!tv#2ZvtT`-NBO{~)>z+{#{?loPs>aE+VA^2 zwNZ+7&>t+G%zl#vbS8s5>4`Yd;pz+i?rHJrv_rJ1QR7cMp!T>pc7ym{;u%X4i-5kA zl;~pusA#j zpBo!ePe}%?@^xeXidY<$%a@I{4Ey@3h5l%z zGk0KHX+JPwFC*0}3#@K$jfJKgnJlC?$P*)Zx-(L`dD8u{!?n~Lh)(#;+g5~h?@7`@ zn@7d%aXSVU%QOBXMKuxwNA903tu&9if(5Mh^fzdCdDR7#1q^H2%EHF#cDZ>@ridS$ zW0Yb%dygo@h2CIRaSx9$lU2 z)5p=5(K-5g0MeYtL}Zz+`8z9|8*^lZb!;G+h19~m+it%Qu^ljXHw}F1>mYpRQu=+DKm*tYTuMRs?w|57)y6z6I>9?+M(;;EQ4`eeJ@uEW-gkwWkf|G8tSi>Ng1 zPJV7ilsV~}TBofbq_+xWo^owiV%5*lRfyeg05V~;J9vw!B_HdDMGSqXv$1PMDkPI3tl2pQ#oc=jg9Q=$EO8W&PgY)NGtxb1^fqs)5(^rO&F~1QCmn+c}8Q7{@|ToH#qp^#IM)qY>lKCUh(%J3LjX^1^=LHcj@U|&neXk(XcfLVg>azCYwrU9Y$~rAKb@j?$EaM|t@-;t8|#XO@ayZ$8Xo?<&A% ztQDSDJWNVVF~EzH^^ADynx-G>g`le7xmJSiKeQVBKr_@Lh;Sl_mkm5xahT^_mJ=7< z6@azebINL1QAA2FvaLED+z3fFfncnf8I1`CyQ0`On%MQx?6@UVv7dqHC$PSd;c?FH z!NFMO)I$3kxWF&!9Hr!L)l_z?a2|W}-ayE7&(DnTLF{lei~H=JOPVkSB)`!7?l}q4 z;K1jnolzw9vx09dpE3BLHGj7QKiKkZzNooNrbgY3B#14uhFyF05YS#FDCs%hQZ#5Z zv2Y-a=ge9*q(q;Kcsw=BFd<3t(F-!3w2k*6*!_5abzX{D{z^|m)GQQ2+t!u@j62C~ z*M>+8`u?IYILd25a$EBR)AG#e{V(I0GNo2w*<&1#tn>%!z_DHP_A0{!98)#{x{vyd8fs1 z%XcC*{j8V1_0v3#5N8g#g-2JMl;RM=`Mh0SGfT9Au)3o~HFo$$(wKE8c3=VkS)Y_s z6|eB?zO9RUz=V(vaHS8N(0m?g{Yxevbu*ngpQ0{bHc)jmq^!-^A0{8}Og`$O_9FQu z5vtwh>f9P<48l0;d0p%|w&iA5veN#6P!B(PB0@nBKIz+wES#xiO4%QkX~W7BWtotu z9y5$hv>H(K#q$i}M_XPp!@F|%HoKqS5?RI&O!x^uZS=6N-kc7{af4G{+f7S3cFW`B zT`%d^C1iV7*o^O-Rlz-Yc4;Y}Gpx94{#lzsS>?IJ^tK1PZp6ujqj~w@ zTZ1itcDSvjPVuhwnbkxdAVYxIWF9Cy@qm=f#XDQ`@Z6fof=w6qOe>Nya7zEBPHO}A zjn`j2JJo!~P97;2hZXl-gov6yGXB>6yeU}gURjY;`X_fi;!YEu<5< zBN0ndUx+hL)Q9yAxx_*DA-*N6PL^H$y^hcHPO@ngbg;0A#Yub``ZDl^; z46z8K-vu8qMrYg^8=`8LqN%w6Rz?!uw74WZZkP|zRn3POsyl|!Myn*YxS1cFuZ?GX z`2Vr>)=^b&U%2(y-Q6G{hzJNscS<)1(kUs8q;!LHgCH#>4GNpCO-R0L zpWk@LxMSSwe`n}XzO~j|b3V`Ksl}$DEa}fn@0zs6d)UT6{1u3Gqf+-(xOZo$kzaC& zh(5Je_*vxwsi>rt;_U1}5&SvnC7L8NUbwD-^ja}P91iCx}OWW zT<+`HG9}nmt``bkqXV`7f5(2Et5aD*{|09X>Q+19u?!Yp?YB+x{3bER=w(zU&^z$N zMv0HS;Z%j}E7&byx@>s6vggS6gw+u}gyZl-Ia%h+VXX26mo|8k({wmj@bDKqazNJv zHbL#NA$hO5CjqPOnx*)ju)E&0ltj{38!K}^9$vF+g9#=OIMfp2XyQ>M@_zb+4A-f> z$p5j96BSjhrfrUdfwNId;60w(q=W3E1kRTEMP&Mp1Ll1|it6n8mJkA-kvu-LB;F|! zD-g)m>@WV^c@G7TYA5iA1Q@=bMZI?FUmBWi%s3C~G{RQwc;$IP-ti>Q#r~G zEumR46gGt0_5sf2liSzVY7}~XY7@Ylaw*6~F~53*{7=a$>;2lOu)({7$`T8ju=JIq zkozYtr`3B+5?!4Rx`8uiST9zp>guOQHxX0ty%?-MXD`33TTAe5nh)Hdnm`R6wyG0h zCW*MuewavjP7?V(ta&|udESxp`lYaSj<8I*)a8*S>id zv}#azuKb_`r-)Mx)-itzAZT~^X7{3-G^;81R)xW^N-A|(4k6*8mse}R`1>=NeZ^~G z3L73imwUq>aifntK@z9hA6rXIS+R#Yjzazuh4Fjq~1dL87GM7 z7am8xK-SnhBUg@^mSNJ)2pP_;+a<};V_0VVrMNt~)gt%lh1E>6mAreo0K*nw49Q35 zJ$Fv^&^rJ_D{~%R77keBMOYgftGZ~uy1^3S@u)=vc@^r+0{L|kdDN4_bUJUg6a(8M z=dUAMeU+!$kRBp@Nq!VD!=vdz+2P#Fv(~_@kS)Qy{dZ&UHV!|3oL2luQTXD@4}I|8 z9quUv2m-=Lsxq*$NaM*GY|J9|Vr2LtZ1-#PS}NWR=f+*yVvn%gx1di9#bIXx*W(}; zSw<-e#QH>d=lrqFPx1^OaaIYX7B;=XkG%#TrJ^#%pI_`0a_Iq0qC7Tp@XEW08wiT) zKi$I>c`JBm1&;a)dB@R&w)%K;%m1}~vX}qL+lR%Q+wa%r53Ua!zOGpFGJ2U#_T#a| z$RFE@P7dqPk=}g4(dg+&UkV^6zNY>)fLGm@g+{8E%(B)RX+E(!|K^kJ{Rw;E`rK1o zjGwuKWVa};j{IT*3nA9uBRr44GDAX)GO|!>pqSNO*@L&Mj@@MHzCncN($K8rKA!T8 zxPkJ1&}>M%sd*{0ohoJNN4oEq)EuXDnOzcPfnv(djomq&6%sPBtO8vIYS(H`DR4da zDLCFD%?d{cOyjvEie#?WUEn~Qr#EiZT>&B z=P$YPpZ13UStA64+UN%gZ;F)q`J$yA8&!;M6#Fc!TEZ0hFOwG#)O>p{Q-UqLM?xF$ z02S$$krzN5%DdCcQAMI?m~5?h@PUq8L1F~zv$wYLUVh;ylM@Ov_-}Ee9h}YKNfm81 z7>*xfjRiwSwWwgF;?S9OMBRc0$rNKo0S){UXpC#^yoAW!!NyAG5pturHemsePxXh3 z#L?Nnk)$z+VCti}<~N2yT^|KvH=PEqcp)p%w$;%3?vICBox7M9%c`~$re*I$`S;Zk zTZ4BSuHMP^t2Zg5CQzWp36}5g{=3e_!^ym{%|J&1lE?odEMO?3U+^*%CXcht7uv$Ktvo{QM2$R{Dul`-P&wJ~a_#2zfYHqq-!D1P% zc63coGX5q)>+j6|koo5nnQ49S@x1+4M_?y38(uM(@o}YSklB~?!f=DS9lK>_9ap#K z7X>&K!k$zQ)scDqV60BO|Mdgta6+}sRgPbtMz{3^vUxQB!6_Ug&0(zYQH{EU`XcRR zxRoOLkq8?2r8e_`6-&ZUh;Cy$ydjVeCZ#6Y=C^3l=G$xCY~-!o<~XeLJ6O2SK}b=On?N2FQx;P08TjKm7o*M;wULe!^LvhVz{RwlE#1#`WbwB6mT+ zNT=mF?0U;6vEB>kmPyWZWv#mHKc88wehjLA6L ze!r1zGK;n!z#M!Cdq1s9KsBis3`_xG3U-p+MNg|smGJPE>UN&aONRUsB(IQ{M)Vqp zf%ygKWDF*W;rwFll~QqP(DuO{pNFv*wFKguW~iX@uK16V`m&xdG1{B{D52hMdT^8WUQ zVseHUV$r%nR0q5x9t(E8KRgTPE(PFRJF!nD z({O@ALi;az4j5O&k-4=LchN6;HM02{g8;_ILvy*s13LwggpH`54;F1nh&B>biau_J zC=dQIzRsqV2?l2)w*4}j)cb?=pu5^=;pt77NU-<>xQx#YPPGK5YS{{V!szBd$yp0c zv^k$cAimCRj~i;a1i=Ujs&3zk!-0nf&h9_2*32jG+zxuA^}X~ufO4Vu(cagn>7U() z&)(R`eU@^p5%%q9`UqCh7^N8&4kOV8C))AcJ>CQwf;!WvA`TJie{13DuMSt!IQuC{ zb2C7*8yTK&Ac0y2k>Q84gkG(ziOt*fO!bE7%BY z%Gy9HMu96hd%daA$VbsX^h6JL1lQ(>JU`GMntNnu6q)gQ4@o1WPXyW5IWY@2xonG4zej4j8lp~tc?``rw zAsKEqFUQm%Pcthn<_P5@T4&Zn2Yb==X*eG0mN*?)drfJm`Uj3m3l>uDOZD36Jj8`{ z!b-K*FC(_d;XnYou{(Ww0xV3*sdrXVw2(Dm{a)WyXabEC+63dhaje(Dmi76nW!mc- zUxdWXk3Q_nI?)>}*^2m);c2<#)yyFQx2$P#x zk#BA;nGm*;={{-{w*14><6}=x>w#-UublWisVd^H7^jSuU0&(cLXP@J6e_3(ws>=d`j= zJ#yok4vZX_wEx96>x%OcezTw=7e2VU)u9{9AQyZF76CyerVtep(J`!9C zcbaQ-gNumpsphil)u>hu>bQ!tem%m1d8@MYIykAB;#;4=<8n=WDlbq`kBul1seS&j ziig{zFiR_bI>?w0h9k0%cW=k$9mS^t!MPT4Y>^AR2~%ll7T`2Ia*!|K@*C+ z;gRX|Xj($*uPS?tRKxrMp!+`FNM))4_8Wvv#VkIH00rKIZ`%`=UJn)fOmK<2N^ogZ zxGG+_PG`n$|KH`LUZjqstfE-}t*F7b7W5Yu2R&p$zze#$q1(e%)|ML7dqDOo%#+(2 zRDVQ4$$mWNwJ4}5Qse4eN({31WUgN5GoWt~yBVKsB&q6y$D&t0q`d$BjQWC>+H%6k zP~#C8sDnP8*is-kW#G=aoA^6Ny52yeP{0Le$$#4R=e1Ovr5~=yej7;ADH>tVpju*c zAnzZ^T`PB`?r(7&T2X4c+tIDNM*PRIEe z;*{I6ybbVkt@Q^oRGBD}8tG(~(JCn8H)9P9V6bFBjTiU20@#Ac$lxx;W|H|E)i+x2 zx-MToM`AeiORBys=GBk5o~GwWOH}PqBG=yVzbL>KJ?qug#G?}O8qc(BOjy|u17RmJ-RA3LY@eBm4x|=eO1#Kq?z0uQ zy>eepH5zY&OSnF5xVT^-q+P*=euE-+{3j5>`My86nQzF#g-9!`>ikO({9wO%X!EV? zIoL*E3m@u{j`k};d;bQ>_tP>lw}4qNtH|VYc9NUUs^Q+2xPS+F6n%!5(u(Y=h>#95*# zNp6k^11LJYdEFLH77{|!?7_wtElb>rCD7SI;nC>_39P16Q8FKHVq=Crefjv?$w=&t zi;w3ok89v3WDTY+{OMAD-qh!}P=E~-!No=rFIoA~HOwtHDx64lI)33xz=TIt#gh z2_%B?A(tiP$?NWyqe^`nHQ$_kJljs)j&$Kg9IfM8PoBD3d`UjxpiU*v!ih;>-NIOn zYvQDk07oHU5s@U$jT*!nFr4MP;q)sz*hGC4Ar6ilL?~7%2ub|Qf9rzls1VxquWzw5 zLX^QD4&q=HDzzrzh?}!5&>+@i^5Akp59DZdO5xT)_h4jKi_4Tn@ub#y82uLBG`YDB z<{_mg4?U_}UW3K1!??3auQqTY5}9uyu_zrJ_VRLd4M!qCS&kVMfbLKjt&`a5_Jb7C z6?#a(Z#^N+-|j0Jd|HmT7K&`PkmwbUVDDUixsn!?;6O?`|WwfsrI z;|&tzLc4K&8?-o{*g;;+aU?a7j12UhbYQwEIj55eWNBcxTX@s%b(w7wXCC7$*6 z3u=CR`w@QKzIH2?y;zSsvvC`lE%D`rLtesT@IeRFefLFiW$<0OdHqdr=~;HJzqyYP zaLOVaroNulxS%5!?kbYfO_j4FhG}1X6jl9)p4@n%bj|gw#AYu+Z3(Uoo;h$@4?2$J zph{W=+ImdBgy+$lPuy^suqO$&LtU^E3v+IGI%1+7h;kFtZs1O0khQjNy5WQ>(K-=6 z4duZiJRCC5g9ARCIoomCL)Yy4Cf@;rUnVoB8X>M|dc}pIBdeuLOU+*$I?yV={g~UN z#Em&VI=r^f$VA?t$a~RYAhV91$PI=P3Q;pb>V2I;hzqOvXFf4lz{&|)U8!SZa(bD# z4OINTuGMNSZF>A$_1ZtzhRZta)E<%c=4m>U(ow%p;?n~TX2GEMwMtm+^E&0(!r$?j zFRpP@Jjuc{>5rUQM+uu=3l~s{=$rDi#cZ@Kci-UN3dYJ*l1|KMez}RSjjB3=p~F@#;~%UHrLU!tgd@JO zD^PTuAylwt4CDoPTq>MoaPXwYZ=Hy-^`6Aex^1*Z^6Lh!coYfGu%Uu3Ev@83X+2rh zf#@gDeP{dQS6<7foJa0g4IBv0sk7s(^pfaLD_m~2%*x<5fG@QpLEHsY_1)lbXWGWs z-s*0Q1e)T*>20IZe^s*QDG&E$k1mOm^}`&ao82GG65p^m;|chrvqN2f{qVC|=>H*- zSM&t_mcxAF2L$M1Om<&8-~IdkIwY8!UwU-?v}^rz2-$U&&rgYwiX$r-3*zaffYwq# zaOuPi>mk1&j(W9mzmAmmYkUiyySV=7_j*e5(xp2w@8omT`vZS6xQ>TY>WC7za*W-@ z+Xki75rtlW`9Asmb|G{)#vCJJkcA=Vr=hu9cX*~ z4}z1uk6A={YKJnwWXR-~G?;5Xf(&0jUXV=sgzQSd1HQEPQMN9|A#iR|nGRWi>Y(+`y$=l@E3tSG_kKkja6TBY|zeaO@)^d2)^vdf>vw zRa|@VZ!irw9zs_@h**xtvUf=x$-vNLS`RL7&KA1(rBCnF5H>Sx!Kpf67EtqgA^C0AXAJZ%9(Us zl`S) zjNy#USw^uLu|4z-lx2wsO#_+MMx^3C1 z`Z_M_B$}yicxI;#pCuFLj&mp?Lnupk`tD2JVSR+p&`U*NY+|$_Lqm06VzRoK0zyR8 zRFr@r7V5qlfD7sB#hO;w?HjHsmqh^*|C}W+#=}-4ZXhfQ(Q(+VHu?3E8GkL9QWj z!&Q$!|COYdISzH}s4$Bxz2T`kzT^8sRh#6a@nsV)O?^&Ka)ZY*6XXy^K^dsOpeN3d z7Ei``mu0cVu#x{h8!0AIrmmlZDi^%@NU^`%lpk=DyUiHZCdayP7{MY`FW3%mpE0RH zcXi@?$+tPnGYHocl1KhAxL-IjoO$z^us|uZP2wdWw*ZOKvq$d~|LjlQcpOVV`{&F; zyPgXc$-cwa_Ar?9GvKuY1z*nIyzr5!NraF82N-reAZ74jwYmK0-Pss(&eYmEyf_|u zIe~5`)3oYM8#Uj*{OesHM@FzAL&{R|p!E}vi6UT<9ob!I4)|-<=C)`OJrX_`^3u5R zHfT4N)R78&4TA}KUV+ps!5pX>z0AR5)!A&L#9_QED&n1AWYxqi9RI@itB(d}?>rXT zM{U3wa7NOU{Ew{_0{$5uE0=c!)HhF)n^C$%Bn+ZKF}@MOY9V`9^J7RJ-1>;#4XJV9 z{mO9@4ir%F19+0$BSu@`cY6v(45hkE6(~notS;z*SVflTOX?jQRFIYfHqibi@c@1N ziimL~?1%k-uk`r2P@34t=#pJWhg47@lqC*v>)MZ{!$0u4|DLcrf&M+G<%X5NYmzc% zGVM*UAnwRV*nT`WOpSj%H@|_(XY+NMI2dv^!OdrF3pO#dx5ln1AYHrv{;hefRSI0@ ze_e=62db$|K9s)$^H{upYd8 zBaNo7n~2uwzvn|)nitvZgsn~DAr1X#7(>sf7D2Z4^;g&0VnF*lLDktuuScI_oa*Ei zgfkrKVnTm1E6RVYslxUqHCXB&bZYTxyZWmH86M5OF}2;&*~-C}mTzrz2pb>2-xT<= z&WpbMzPiX%2r#wc!OY-l{6Ko@sph`p_-vjE-7PN*shiEM`MDkJ7t1&2ilvodeyt|A z&Gd&GVn7obXrX#?_rM^Z&18Cn1Ppg$fcN-n7-C4mw#p0gHMkcv=u>j?Eog^3i#y@#n zlIe~|j8j7yRko)yv6Uz{s;c6fP{h>%S|S9jr4ejDXE$S-?+Xp4_I{Dmhcg9H9(KPA zR#ObnuvO#!vqWu}{+PPFFDp3`>tXidw@`bhgJ?Sj=miM+e4db6k&wKUcu^P0aM*4> zAr#wNHJ%Yz%gE~Qd?8pO*tJ$G>otO|p#2d^xZx3Q6FMMaMN;5^Tnd>OKRlv>v{wti zV+O=$bMXeTuQ|u4@o>Aq!syG%k2$I_|0iLRUIdI7IiPX1`-eEiB%9GEJI@ul)`27q z)L2gAMZs50xzySM3R!6n zI|ykit(NqR$M3GQpeOQT8%Xy~*b~#6=bl%et#t(X-?aP$Y&z_$OxaaF*abcg2I!1R z0ay=cMsQ?hVO6A~Knl{`u4caXEL~HhqruimBe`rtWFrk@%!X+84yIjbxh~vE2`6gim?Hq=kv*-82)gR z8c20@D)t!)iCVWV;WQVkgtcPhwWR-5FPy_O7t`ZT|lW&b$FR3+4LX2>?p5_x|q28Nkdp08+afAZMQ~ zZvlE{A21x9fLH>Pqm_09;TS=52S%&zYd%Fz+_^_FRzyn22?oO1N$(LX7CT(1vic;v#5L5bC>Q49{!@ zrQoBa*!6kv3dVU*dB?E@Q{S%(`%q1h!;e9oLLXo%4PC5`<99z?iU8?4gWI)#M^ZNd zjvf^vNQt|@P=}7dp1QUx7xoBkX9fdgXsEA#>oRWZGRNK00yAndbsRc%TEOD2@hDnWdT$zUqRD*x4eW=3Mg)gLD@Fy8vNWLUgKbk;<=b7RrGXa8WvL|TmpT9r-eD0NPkg@$xVEti7J zPOZzo;8l2bI4$yj{T`+O#xtoS4%%V>ELl}mRSFgsMdRimTmrVgFRQMO2XYD4+*^ZJ zwtx|bmge$gkY*4>5>PsMfhY|-esgnDF={+vVc}P+HVMK3ECw2pNCy?8H7nROXSKf| zznTXR#rDeIx&T|vvpYL`$4~97ZJZ*F?>)1_lnZZp>Hx>Icx%n+Oi<4-DpS_4T)_Gj zPSYLF?}>Yjj)aal;Z`aLq_}}FCPvK)>DEdOYPj$wPT|3Yw8Gz1lKr5!N8IIP|I`EoJz9D34rI3x5pTatdoJmv((M5&O<|rvFBUZrR&WB z@`S%7h6O%fJ{~`-j@%2ta^X&(b)PeAeFoM#dl|Pdu{Ck8g)f@YevvRw;I>y$!+1zz{_z*-SPb%1ocReR@0>V&>LN_}Wq^38T~%9KduVV51Ul`Z@PxA@D<^QGf-ZN4 zowR@HX`|}l@OLjyTXjC|tdy#u&io}g9v1{w0J!G{EEOmo7thJIjZzLWU+@tEDi(gO zfcrw&C_i4G)1pK^4q)C=e7h-GT>tpa4w7m$rPTSpO(SeIQvwqdMK^&rRz!_#Rt1L{ z3D17rKbFr%POcCedWnyF8uR-&P6DonYx4xXDJ*Qc;^+QX&1U@-ZnJ!~y|bpTmvN`e z|IuEPE-B=!Ccnzf*P2I-n_}dA=?*W)bd3FxXo+^dwzEN&AdH-W+d9K-~Y84wsr+Z`m z|MvA?j=xb$e9c`OJqqBLnRbo2i;um+oMSi5vX;JS=sWJKIR*mUPWb2N9KjAXI9#J;3jKGsy@{Z0mus^+w4DslKHSNRG(`3$QJS+qK5lY)$pQv($^L3F z)25s1@`FDz*|;PfVE_?{&+T0Ub$0evl}uz$j_jqW$_!c!@FXHTfwt8(2@_{1%9q@l z(be1F@)rZ5sWxPNR4SFnYNmSLfETMB&s{fIut7Sd7$5ohi~j>{I~!C55~}uQD`X4| zs6bFb;a(Ilo`c9NggvhZkzdr-CJGXo9=bp#Q{kdMNWrnPvFQPRw!rIaZ$xMk4KNf- zZVG}D_9e6AWo4D9=3)esgjg)%wIHn9Fm12~i@4c5HoO@>pO49$VJ%01GjRGKC~TXn z$;s?;|EY{ZzINX8ndr~*ShzFpkZ@nrG!3SuY-IS#B)02Utw^j1M3C08D9!0LK=7iq zk9YXpc>66OBDb0V0$)-fF8(q5Yr7CF&XL^Jx*Z6vmdD!SsWzpzr&mcUf%H(gae_te zrGHw%o3dm0OkJo^)Q}Z7z&n5u^?Vu~OH?sm=^n{c zDN2XatIX2JZhIy1>iJea%n1~m)aZiSP%uvp2VmF`?9duAi<}&s$007o9BwL!g3 zfGiEAXb0lx&(WyH^|{M`r7YvHL`J~>Z@B>s&#K{tp%jZ4GRkia6^gb<=4pm-K>g) z(79!KUPOJ?aeM@@7#*UMs1O}dj)u5*1O$Gcm_a}4zEX-NXmxIqKDik{V2BuPKm^pt zCji~XMkH+jCO-ty(9lq=!{)b?i2s$o{AYV(KKg2=9U4TRpNT|?kzJpfx-f=rPmJAB zd0}BQavfog$OZac0GC@6T#I(Lu34eve#DSouNco>72X^LfSF;RMy2L#OOz-zQc6%^ zh)4|h&b0FnhSarfi3g$d^bnqL5Xge+f4BEuVUrou{UI@WH>aM2!rqzT)3z_@1Rf{W zh5S2L*cCSY?xisYG@17mc|s*2(cX*7A;WprLJ^9f5+b1c^lgRB8z7;;f2eCUSr_DS z*~)(VdK%dVfYDCBt&8(2DsbfUHCk?KM>u-Hdh_*!2t4-B%#|_^<&`ymrdOIoK!Gqm8(_R93@P+HasS-( zjBko>s+_)KQOfU)_vXO{=>JF5$}&hw9JQStgGZrz{bf^7hmBYiWPLTSeZ>0T{Q=Th z2;lY^)usuJKfxm=ZvnPtg|U{#VSdNJ6>FDnn_?7lX?a%dl@3okmU^4v(uDaR(U%tz z;jU^;V8Hdq_bnb2-FzZKTJG-?KAHvO+pDOU>8VZlM5z#P{gJos*ZgiPDYH2rO`p^FB0`%#+epxOFDeFVcllP#FDs9J zDb6WQQ^Jo0`^`|YqUn?gllfpEun7@LQ)AoAlZ;|H|Ifvl?O+<&4 zw^2br!%Q~0AsO9y<=~GucZy>05vMeF26{kDx!|Lr#cOoWAS8hROaj@g>JBrXk`)Q44`W#O|+2j zD3>Ckt*NzfFzh^308q_M*E5ic3T|c(_;&&h8s@y)ffc(dZeSX`6@khi?BVUWG$=1b|Yufe(T+`KQ$cu-};l&?LB*i4SM-}cs z#Tlx+6rm}{AlV7N7+-&P;Vh896{=Po{KE{)BI1p=z$itEb6kmFc-3|>EFk53#bQvE znmYu)mI%E}^OWSFaYvse;c{DIv=-rZs4~n)m?C6vuS-N zM0Wqv1iJip-PmK24gN}>Ew~b>mBA95$>{%Zcj@Q9y-}!1`E;qJce%AX*Or(q_XY0g?$)M-9+RqCSfF zd%#+JRmqo?lm&qZP8vyN%mg*LV+Hvd);^xG7yIQDhMzFHGjQ$5TA1_TQ-bS}C1U(4 z=M1rx2JPauwQLemH(M$HqW18Rqp(xM0}P!s-XSJ346;WyPncp<8h$S;#zdVHGZWep zD1cIMO7<^cU3>c8yR62D zrlSb`Dl^iUDKk?A-kl6ze1Hosr+v-}1WjG!+}P%j7e@zWQnpQmJI;DmGnBx4c{$ZNoCpHsVp6Y)@RH)Nr{dQh1KP#7H|A$H*bxAHP97wb3Wz0rKhET z7GXl%o^R>tzf-5S|JsEBS-8o95td*y0mdRHJ&lO~^P3|CJ(+r`3nVrG+RN$43Br|} z+!;at9SIaoc!q@i)L|4c2Ee2V^f0*cHO8C#=>cl;Pt!lG2!v&%_dPb>^@z9uy@=k9 zgV*X}Gpog*&HeJDM$`a@h;W#7;EIVe1fuz~C2$WE;eF+vzHcX?VI#r&vM9h+Ffj5S zs~ZDo`jJ3$Vju#F1&jn7Us6J~!ydJ&H>`X1uJJ{dol-boo#c7{#8=10mIW*8AVo`; zbJu5Q#Nt3{M=YGBgBxRxfWtjk?L>itOp#A+A}l0yW@g4@aJ9u3I1?4pfgFywOain# zhPxa7n>%t7rEkRPY%$0V3<=bA41@&moW1!5EHSY3RiIpwmVXZ@(U05kb64AaHv;3h zbS^K*zDFqHewd1lUk76DoxTrNYFxpUK%}XW=8p~oS)p>gO~LrwFR?EP0K)MVBL!d; zO76;170<1OonaLOpmc#%p!yG9&ZY;6dT*@~nM`#<$a58IQxZgx6^k;<#(}z|{c?y1 z8Q%Rf_if`Nr0dToJ6A>JXn-#C+`8UKdaEnF#a@Iq_HF*DJ9p2kz!iaFK?_wG@cQ*k zuztoTNYMGO%X70L7CvWARX{h%Vlgo-jS6EeGT*H4F+*_UR19_(f?~taLF3wKv*qae zd?^Im19)(eAc7trig+rD($?--ce>JV0+EPTVmP$)1140t zN0nCn;DZAUQ5|k^oPW$k;fJesIPE~@0HBbbH^otY9H!{PptlUps{98}l^u`UCbH(YpHP5JqQ5uU+N1ah^$ zFywjg7jBr}2QOd|Tpxs(O9wJ7r3r1YQ6ow0*aymPj z=4vz$jwkd7KMV(TxV*|^f^VumWyT&}FW3c;fN{mQk|KvTDLs`Jd#t3P#hn836tECn zm@K6$Hk|4!35)y2gYS)&7u9JpP_}e7=6Lq4(xDJjv%AU^K_cf(`}m>s2He^T?nS$gX$OwGE!`Y#%l@Y&Bze)mS;W^ zp(BR`@^WNuHuM>8I)d_oQpYEG_o?6Opw9L8nbESqLj;xu{!G$7>c_oLDC&v;s#wXjK$^4DoPozW2w3s?Z-2(0eD3@Jck=5DR@L5p1OWr!2YeCb zFyO3^Z;}z5P>*>k2^iU9ACrTw_7(HtN3VDa>^Yi}gaQ=UI2q}QS<*k*xjDB8t+l{C zjaj4<1ud5btxI|Xt1P68WROB;rC0zc752-Biuf{xf(FVG5)=$K{O?p+t4}%K z=h5~M{-&cE$+ZQT1tLb4s-ZuB%v{|tEw?_nxFFITA(B2mA}y`0KCP^@W@%(rfh!hn zYmEU1-Fy#M#E$!sZIcwN)_0T0dBWRZ&yeMtj@{C}zDK z0WUrl6+Cu}y4;tE))ifmwHr ze){}$o^`zPk}eNZj83G@#G~#ogZ&;#X7Va|`4&M?-Yw4_#IG_vq!Yk=;ZfxG*!vIOYqoQMl zq6RmSNpnKv*&Z!I{|-A!h|zLvWVP_jh2@=4V4%@G-1!@*&^-V2(mfW7UD~3>j~ zFACuFrqM4mj|d-)p8-|T<8d~N zi9L}vrcvp0VX}((?Z}iZuvL>yu%3b*cpnG~KLN@2rXamlr_KVW(Q!4_=kPl^NFx^q zMUN6a6O)4NJ51U~UC3~?jJmn8&Aj>I015kFdeWJqei7zuXOd%2tCSk}8^^SRD;7(Hd8=2>n zEjZii7x2P^9XPM;`$JZl(*6(y7QgH7ll%Z*y7MQ^meW#{Wk)>+RYy@UuFGtK6#;Y^ zwuEqE>pQP_1V!|)Yb~|ewhzU+z371qJ|&N3BVspUt)-)rN!}hqFDo_AT`3G~zH?GY zEty&yl^`V)yvt}Xl@zvaa2KLmT7+-Us{%mc7g`F3K_n(2<5~n$wxplmLu(mG6HYh#44k8Pcv*nZd@#LV4pfk!+Z>A#95TR>k#r*JXhY0RAdN1dUGvSr z;qzlaLLHlF9ljAKlS=9ZVDZjUs<8gKtE#cAK-TT8Jeut$-+EoDDW&4!+3thO54CX+ zF;&r>naRy0;Yx+X0OnF)NmB?MQ!8bEt`?+G96Lo!@RjWZYTsqvZ~&%dpypERBn$|7 zE~jOfU{OaVu;NMAaeJzg2?G5Rc5hr>X_s6y41Q1xIcP`y+qns%eWW%ISk-|&*^ zBJ{9M^F6b)Sh*hZ`D52!pj zfWA!QWdQ;q`)Iz(*B|pbX)=N3^Q`1SAVP z(=;T6xL!G=82v|X7|&{Qj4p-Z><4ZC&+wx+z{TqH@=-aifP?Sf)@t&ZbHSx6<)3R= zJPd@8cs2)hm3T;2g_OX~FU`OeHKgn6byuG?_!Tt%lp3S_yI#N*DvW(rO6=HyDA?h` zAIqYey#EJ}LHU&lRtiwgR#!Q_j54tIV4&E9@*j)Dj`87k(nB& zz?s2lP^9VXx&Hh6kn)S%%3k8x-^W1G1xl{?RJ*>upPtS>A&_?VAyJPD5565;|qx9LRsN8LXxTGEx$CO%dPH9huA=j;O?j%W%cVBEih0-FlcRYI1OLFeMRE&l{ctP z^I5e?|07>;YDQ$TgAlklMDQ*llUnp_<74SiB>rJJ@v2q<%|rkX@P2Qrgf~2!Q>L|o zVhe1RN4dG?5QamZv&0v#Q^YJkzZCxYQ(gAXIAdJ@?3GT_0ndOJ!l{5qY~;4sZj0zs z4hLdWtY(AmY9@~m3aIo=gWd;bQ>$2!qsc$Cu&RLnQU* zkdX3+2cIK+e&4rfK)!HrnaTd9xth?VyPDtJ;bVZRG=&s6JaDf3y z7_h1aHH-J~!O+V-LHxY4kAl#!NZH&;TI|Mhvuu%Q>4tz;<~l*@NL4Cw;*EHwyg_-i zL3YGTu1~lV%)#Pq_MA^rc-Y7@LRq)A(DZYRen}Q<{CkNEs}~A}!roKb_7!h#nUm4n%JkSAxMCs6|<@LKYHjmL!E2ML?AL8WGzeRElOdK z6#?;Wi0NzrA51tU=;-e0O<H4S(pQo0CrUo%*kilKRM5s01f_y-Ra1kjH! znC&_9>CgE)6UTkxppDu|n;~{2`Ew;kHDMAq2W14bKEU&VsU$^i^_Y8Vh@#(_vg=IJ zo^+&J9wkMp_|*NYEWDIH1Up=JoEx?dg>|TIylh!fSt?s>IRRN0l;55p!^ymY3V}gg z7(P+hz;-obtMx*`>w*hwq70lj0l5bR$!ye7o&JTm{kTjQy#1_yH3q>W3DTf{Yikagnkfn6&k&nt`PAlY7KK0j;0eb9YyFNR?ap4K;!{j^quh5s;0g z9flwkMw4Vb#U)~Op1U${ndklj6vbdYdtdi&8&sJZ(j8hBCKjTA@d~t9I)RmiWH8wv z*c&AbvUoudY;%#uR}6$VzsOzV!UliG-1sR6L{0sa65$Nvu~FS!qyEIGcmE+VdQEVh z{!;+4zW=i|kV2MA<~rNoK62ReIzxS}t+;9ZJEt-dDoq^}l2wmuFbO(u@57O<7ZT|J zVzI+M!A-{FyVp*a@Subw;7H!F8!Q{p^47K@gptmB(mSYm`bH4=W0-3RJQ`688*`XW<;mSQ5ca_T zy{s6l8cp)^{aG_SNhA%$@Q4HBL&CX5&{pf!khji z-&^qq-gGNiU}iMIDfTrzV%S?N=Y~1#^mjD(H3PW@HG)oujWLqDm`U9XP@8!E3*Gfn zdyDjAjI7S9y0ag_uoV|_wcP)jtL!P}UgO&}5TQo`=i=$r30^V5u^S^+1-ux5?m-r| z78t3`4YLvHBG^6M5@UIS{sjFhO#SGnDmne=OP_@=Aas`FHW9%H&984u;>9FfOuTc* z(s>4ojrW#Ejwf!yY8(_k0fQ}d?oowZVmhV8O^mQS& zR6xQ&x|}(CXV5a?^;V@7DLr|Lpc+Ryp4lh06l^}F%-e?c> zebxUSn8D*a!=k1~L4O`FMge1-`@Gc#_Q`kYaDs`l&-mO=*_==V4__#&+WV_+ z-15Kxip~GKF^#CG)4xo8eUP%3SW9pW1Ok%szQ>RtbLu@2nW_rYx-WNiA18Z|AKW{k z4s0%X5a1n0!vARZkgIy%T9KKJ1JP;9m(~;R8dzY63=%V+NM>IG=>(WiV;Ya&h)%e# zORZc>EZVlU^L3zxC1Ul^)nK)lln{JH@`CBkvz=j0C@$iQ1F_Bs5Mr6~NDH)>GU7-x zdGMnfhy*e^o11*OU+M{p$)ht9RO0tiZ($&!45afPf_mc z(G3F$V|iNu-?t|TI{U!#N!{+MH^H}kuVYcwgnf<4^;#!$)+2tB=ETHl#{3K?^Dmhz z@e4E&C8tGQQ?5=yG@AQU?A!ZV>lapU1U2WFlWFRl-IX?k;TI0%4Mkc&JSnI*hZ&19 zmQE@a$Z@#%+nvJUW{w)svVBMmaL`;+Vgbhs_DYH>lJ}fPhoJlaatPkBV*4AV7@P75 z&vIfd3Lt6hX%%bdy-*C64=C?zFKQnwrnr*~8FsB1$9+f2aJYZQ@QYMlTK+4l?&@d3 zVEObbNni$Hy8kl*!_au-{f0mq8twMMl4{UrQ^G=(#o5Dq?c}(`iuhxAgzm}I=EbMv z+eV!OCsbySzh2Z~Ho@Z>am4OxtA9N{OH=3abkBCb2f!-?EC+PE6U?U8|I1#e>qN69 zQUJ-UdoEl);>AyezGkDB_ubaz1I&#g7^yzFueV{`BBEDnI&s7wG-LAq|44hwu&BTI zZE%p1?k+_s>244uL|QsUKsu!x=|(`MVWg367+OiCQ|V9`Y6$7vGy3~K&+cBk`)23z zg-m=;ea<q6zs-%UXq!K#gXq;A1@{pU=SUI*z;UoS*6rBz@T%TFqYNRtd;acNbKxwP=|w z@H{XUq#qLuxDaFYUz8 z*>O74{pOEZcyAdIQ=TrD{TaB^;fql z4G?M$b#DOWMFk-j*5*TpcTkV^`QSIqubE88Qnfa%KFYilVnagS08qRpP<*nsQMo7g zTFG>iFDed%CFYdEyyv9h=B?=+fQJ*f7*aoG1O+Yy!0JpctgPa`f{fn?&JKfUa9E+Y zIR!*JFZ<_y!a+ue%CH|u_lRS>6I8ltXhjF5m!eS^ejYR9K|i;GJO`;Ev5Gji*KoYi zyorH@O)No7<3Z@D#_I65Inm+E=BNGEpl)o=0pLHs z(jSy|l=!~sGF~6ls%=7>LCpr6i?Y538kO7QX;!X&v7@hM-j{XroqUVduYM1Z-wMLb zTw(Q~n3to zXn{hwX!mpVGUP*Zpf+QstezMzD*F9JJH^A;0KcUN)2RdF#ez$?7hpk6L9x$r^oHm1 zNKRJg$2`2I!!SA|9hFkrwds8JGL>5h@NQhY_N@~loF+ei-LjGCf5!AOgzJ`mYxi3C zh3FHlb;4XUWNtAAhpN5lG@TB{0+`-=FC!2iL2gG*HEL^KVK6|afBWUvP8ZH7$Sbdn z{SBZ8=R4l;2|U6F)eC6z(y6Y@R;$xYJ!iYoic%>Qh3N+y48y&JIb0Z!_lTgb zb`y)XMh91*u-u!SG9R&<%zr_K+e=e-4O=R^dWfVXH2!^Pqd$I$9Yh)mA}o8Al|2xD z#mHC*NKP93PmFKQZ!Q*YmlTGxj+RP{EFp@MOxv$H((GMBm55%zqyn-pQOn{-Wju=O zb3xIB?q4oZq>B0bO>!#}qHGH%NMP1Q0O`u&`*((Ycp32xR~S_If{G~hg=x-m%nvE) zwWu2~{0xD@vU{=dV{br$YMD6aT*WhL54q>I#1y&Rl7gxUh96_Q#uSG8HrJkD%Dn;S z&!vuP7syV>YzqKPsYleWcKxfy1NfSz9k1wpRqIzyW=^|)pDgzPy$1x*A&HgJ46GqK zyA#TY!=K(#GqtA^0N8fY^9MlYMCn5T`0%;H8GB+R+fMp~bx}sFAYTc9t-@=X^V!fK z&ULD+-nRqeya1YoeEhY(cMPXn(C8`c(IP*b<8jgPhDDwE1ihF8SI*vN(>6nSF&n=J zAfw<;*bHMDKw$40?)Hpeb?G<(+%;ZLDdJ~5y#dN9zya($cP@8^GT^%0?=7nqh1-~YEw|1t-w?rXR+I;sce#b2)Eh%Sd>Ytv0M#+1@75PL zXy3;~r0}%UlZOLTmpZ97AhXZq0^SOjJ}a@`1sI=CHbAWIOLx#B(zkbZSLxWDnhcPX zMD3lc{$nSCe$o`tKaQ#GQ_7*@XSg8+ylr|rs*tx2eg%zUjwcF5<>4bN>S8ABK-LO$ zV*QDCtv0N+`aAu>rC*b|wiPlHK=$truf|^k$yG=nLJ!q1fVun1^Fk;JwKc8$mJBtM z>Y6gFclz?8(igoI6Mi8h@fbMUUk8SSt7aStD<+uYIX!%1!4zWw3W7O+Hx*@bDX{g8 zymC#wZq~;HfI!n^)$_3U`dgPiMq5n*&C3{-ohO2sF&IL+Sws3(KmUBuN>;Ps^j&fb zPn6NU6pS)Q@qCE_^d@9T^8SwaTKfxTvi&`d3|4^_gW@{c*2eB*?g~^`WUxwm<~mSx1I=J*M(!_2#PZYxwFdW-mXB`F;B`7j_z8$*NbFyL`nLI5w`=ysfI(&GmY^H8jcv?LvH)~UbWlH{2w(U#)>m=L`% zhax-QX3V9PCvr(h2lZ7b7d?2Xvna)Br=FwPj$th2nvTmQbSWX-6B_G z!1k^U(>EiKp0nesO9W6#P1c=4RcLck_b8xBLu9@H&D9s9ZA1{s?I24`@k`u8>tj=d zp4ih!VphJ#fm?_m zTh4h?Ta=R{vvS=?4*r?;H*BNa`B({SymRq>&^FFIt5Uh#IfyNGDadOV&9<= z*2EidJ3Lqjtcm1HYA_mHC9hgB^QbwuUp+GRO|-AJ*uN?bBo&lf+W)T7z-UWYaE$0$ zZYk`w7QnFRS9<>ZU2<{=uX*osx?P3L>({T30#TmGe*(UB5!HUTYR4gvw)ZfjjsIg` zzBj>SeZbp82A!eKbh$9S!0j(64t;=6u~-CJKF3yGlAO|kAAn&jvhVGy1~@#y#&a1( z6#<#Ftrqv6S#A3bEU$W-*b=AYNO*mx$aQ!tl5q`?gO-~<4N%9aoMztz)DjpH+;+q> zbJT7iYOAt(m#V7i9uvaYBZagoo_xpEoX6LNaQtO7P3`pjJZ|G$+~A;c&Ub!(eix7r z3O&_t_Y=+b*<#D|UQeSG_uy!-{~4^3A=I^d^0AE*>|T(g{k1!a1PE*dU=N{0hR{Ae zjUZ%TVAxOfXrc3HIgK^5Dk60A0$f-U-!2IMcu6dKo4Cvg&c+p9=dK5V1j8Y6OhCzp z0^wUbmg=}{*5nKF3=M2FpaYC``IHCCa+(m+$AR2g=}$F2Q-ORGkw+DQdoErCSMHDY z)(CoUqA2)D#Cvwm&nNgIEN&CNM?M9`wyKk!t94)UWdhqjL4Td9)%F0AI2u|^3zjXi z=D02ns+8#R4wL8|*{50!qM80$nd_1=A2KX(o67YEZ9RV)-Dil1dpce$k~3FT7%NoN zGUH&n#(+od_+zK^?Tf?9h;EPD)F+w6TRSfIe&*x&EIgaECyGDN9Gnfhe!R!{DAD%%^QanXmJStT9RJMehy=*@er#Yr(808X`cH&GAv)_|lcBI3HeU5GMF4Y*aq@BMm02!k9612C96~AQ92(!nFu%24HY^mE zHWat+C^B-)th&Zj6i*MmwTwUvXdu} zbt>y!lTF|yy^sH@V@x~TDNEN1d`umI z&+JbVhGyxG4x`gCL`aUG!Z@q7W|;lB;PVYZBj`T2tU2SSKz4MOgtx+0?;O?D2^3G+ z3J3_C87+>E29=d94}Bb;m^c8uJzbWyp9%`lMMXuMHnM$rEC*BHjU>N+9}1$gQ1CPO zF{pdso~|%v2c=R!qpq&}@{5Wd+?Uah3j&h|P#%1IV2m&G^QNrlxV=k0h>T5!n8!WY zbb2LE%*V&)=lJKxX93dulvf@y;7^Pq%STx(Pxeb4K9{&d}&E4S{0y%YRiEL>NqzAWD92#MJ(XrFDo9I)H%HDd!dU1 zDtlvS#yc9vLu(T(TW}%YP#`34UZCEXhz>~vVT*Wa_jeR5vSy-1CGcO)hYSCJ(1tGz zox&Kr-0>jyI7MA1ceufH_TM_~4S&rm8@_oPn4@3ILRj-y?@N2QvAV2_jK=$D9hn0A z%JJOQQuR7pjeM@j21eUC3O~d7tzg6Yj*Dn*QbM`MLNX_U_c?y@>sv{7NLyxGStbp9 zd+2Jpe%Z`b88f(^HqY;!ec3$HtY*EYSx|b%8M?Szc=j;Ah^28`?@39S2LlAEHgn{& zm$AffK0u!bQv$@mFth~=zE_jQ8hRZ8l7*^S*qwBt$J;aJUBT#aQo_v482bA9Rdc&% zK&W{gX6rB*4E6P+z*YYW5DlU131Pm*`$Acl>OasdHS;RFovK!7Dc!nt#^5t1vfe#QtS zjuk4W1=CCVECcOG59Y>pfoa&oMylm}>KBN{;q7gyCX;^4}4GFro;;a%i(wMMUSU_#_rF3^D zAX^&V3~8CH94?6?V=piNfw}POx^?PD79KNPaNaWR8Hsg#F3?&U^~Y$NYeu~NpgIAeD-peNG?BC49nXxa=PXiPKlW; z6G7cV;$1LhP^9CEmXpcSOF35mltVRT+I0KC%t*Y+;nYO;+6zV-)YCM0k@i;KOH`Fy`;}MYd!N&ifhXp3Wuo=|qt_&E$tIX`D51`}15@$)?o&S& z0#Q?;B+VU|Tc;S1agrg{;wqsFx>$iQgAftT5Q>w?CyL+D=pf`AdZWwlPw;4j&aNe1 zeL(}HV;y5NzNZVGA%X&o*rv`Dw~M&$Bqt|kquZmxk|@$y)yt#J*ba6DfDT)7vVtKg z>h^ja?WUQ%!1pRaBlbr92ji^K!CuKS8TCnpOz<&H8QsQLDoT;r zgsvYhMK*Ld4` zx<#!(#YOnweZ*bJs{hFoH8nSu5)Ew`U6LbNX81!E`tnFtR#LgHOqZXtfB-0=tm3J( zTl)*c5c*BBBN^i)VEkex<1feiqqZ zgeA%w2QCUSk;5Ql#8t7P{FEwerd6dt1gv3l z_&5;^f0z;47WI`RJ5mf;7!qeM$Ja}D2|m3X!C!@a$WS*S$z}<$z4P)1|qBs+q@9IL}<>raMyKL15-_mG7dE=%e{ zDmOO$snjknFE<#WII8s7Ku1Q}a9t(ZPi?$?K9D|)Os8xi8}oiE-AcfbK z3OsWso6);){!;9N4(3hswm`t;NZXH;J>wh`gI)T$#jAaz+F{P=+3<@?2}6^-UEhi~ zuseGp_+H6~@WQ4UvDQj5Jc`Z8^rw5S)k+JB_tOiTO8wbZmB$?|oc`RXH1dQy2G@}!s;+8Z)Yece`L-&Tz(Od*&w%bc)&S^TPuTE`ZcWf zbv--ySut_-~lD%_5-u$~OfXjCamFHh-5IwIUvP$1>Oz%@=2L`ZqrkSg=Q8kKpA>qa$a`@a#v?3!7h7TtCe2wqyA~vBe;* z&A&+xzuS)TE`|m!Nz-o!`lCZmr}Yc8g*se5XwSL8>TZ0@)26_j%*v^{5J?Jh+4kS78{djG{`Wuo`S$O+yu4q8n!o^q^%~^r$ft$vv!3Zh4-CO*8OfZGmii z+?FP@VqLfBy0FRj5xftZz1}7WvcEgVI60feS~a0<@9hUVs|%swi3b zw|%n{`a+iM?fbp#kBbik@tNE3F-NpbPtqL>nA+5Q?yL?WzA0^3I4QU$n?$@cntyuf z{kRau;xE_68)lf-L?VZvLYJH%ej<_7|9r&o{W*(gm0y&7rx z5y~Stq|QO{LDtmDjc*sW*g!OLk@T+p3!4?5qAx&aY!rQzPM(}D(5sX~OSeZVcXHx& zE#rbUDowYei2WM&K<;WW$XbQCDMpRmTz7W22Vk{`C9f&QoiV?Z_fd!vPuCVbOJ(($ z&XonRM)#p{1U(*^Tc@Ne#QMerUhlyY?LQrEXgg2#{@Q9D>jKK9*pwS6=HvcGH}1BX z9k6)~*51%MSN3I#@Sx7%4R>>M@BKc#u*JpCu7yr&QqfI{Vg>m|x6HC4v|Pxd*$o>4 zK{mApTy@l!PsWLatr{`nc#RFazM8^!8^Y5e=4Ix@P+n^IIa z6R-$(rLmjx3AVGjoiRN@SFJW#*1lTR>mq*g%*H16V~W$~VKPO~y4m=v8tP1r&tF2N z9Xn$?930kLaN|hilt<^1#CqCVdv9yE0dyeVUWmzrY;x1%5vFL%cK#QW9X4A_P0!Zd zuy%79*x?V^1Nfp?&5pmvwqyHzE=u-wEgkzPX1J|qJFn>K9Y=?e72KZP-I_20vjZv< zN}A%3&vkazY9h5$@X*Sa^*oSy8vC$TM}H~hWqjbR7e%gpm|9zf#REf7h7e6of1kav=Pre+Seuv&3Ga4o_b!RUUHFQX5NASsAx}C z@hi|J&)*-+bRDS{FK*s97%6(R2eO0PzU*w^VQwUulaMtF{Xq3tH77_Oc>z@Hcnaa| z1MTdJwk;v8kJP?xo4Y|Rb}hDEjrg3@6L9<6zM^g_-tOA)>%NKK0p^lio21`Kvg^~R z6fMhrCDBv%NIjR5uX|7U5_knnWM;%VU8BV{LTV)Y29i0iE`4F$ZK%&a3}Gpy?&MYI zjJji6O}}gP`Y0nyw42a~--Br7!~L@wd4@`a@gfrw z21`uotCD&H}Wm|EjDbZ3naIyXm}QiXqv=OXOL7Eu5)62 zXD^?=@h>c6=HLpgm7xtorLT@;&5avIM7D5mqZCaD!nbSZ7{=7hTus50b?Wn0rT#ip9AD>Z={V4@~$tH^;I4u zypIs;$--NGW_jgCOeB}8Br!4HA*~a8Xe(af{feaGYyzXn=y>MtXDlC|B>lPG=f-xD zRkeW+nshDWzceNog-xQl#`KzxZdV%((N1`0y)&dv=*Bl}nJZqZp(UjqSh*bj_>`FHF%B%PEb3$^>1V}cM`*PuKtr0{4G|M|qN)E#368rJsmr3JK<$^1 zP5`+>0&I-2T1^StU9NeUEl$AkI({arO@N%Gl_QnOPdD0xz(dWd2)xb~ogA{Gh5}%2 zlDN5V!Ayz-U<5v2vBX-MNCXwYO$`okM4%Og)Z&Mid)3_j?r4T;XD^@OrC`Hm9}ahS zU_Iph+e_s##RQ^k)$P5#{s~8d_fXx7hj7>4*LIDkr~3ShI?NjutI{~!edfuSoy9Y+ zcDcEG!TtQSQggrO?VUj{S84jo4XMmV0pm1LU4(z^S1K>qpRj#Z7c2BOPNq__BkEJq zQ);zUI;+X28h6TMbWdd(#ZdwJk6wr<xZ_63;8TPrstfZXIEX{U3Q_kO=$aw6A9(BwHc)9{0fUF;DZ=R=k;Fa)FjR)$NSyb zp!h$kT)YQV+c*`M)}=#RLOsP=xSi0mH0PtJH5-FXRyR>mAhpx>Uv~uO>S79pA=k=Z zm}F)i!Hq&xiPwsfBU$g1#I5?FKX>*oYGRKLmYx^G_07o)y{|vuAk`awq^Q>U`DJYP zmd!QqXJ2fvs#m|a%~SUyDi2PFSn9Z1+n@WZGP9$W)pw|q83Yew`xqV~!VuCuZJ*F2 zr;Avgc&l8GG&W#lk-6o{1G{te5zRNd9TXA-XWzVd$k|O>o3Ec5q*GLhvt80aYfrRB zBZ0x5Sb^_f&*I?OFDjpm!bv@IzB${`bfM2xabbDw=~AqHNXr`)=cF%~@{l;*yKcHT zk2~|yZGWkI@rS(h#A(Uar>ZK(_wrekYo!c#`C-l~{amNjxW(uv&vfx>;K4E23#gD` zvCRh3ycva{69Z=aec{Ih@Al%GNu1rozq9iztTYPac6Yx;P^lr+T=&c2h9#(Bm#uC_ z_kc_01ME-*4#wQOq~??6TBrB{jrw<(;RrO=MGmXg5?1UAy3D8%hF02+4kk=+-~&6Q zE5y!otv5y3JlV?l*Eu%eA$d%}$o0M;9LvXd6xK9z=IN4P2MxwQ+c8)!L7cyF2Dd@& zxvHyfqk4u24_#wCs4zPl{zIqPD6>Q86r9U?_C1!U$!Z>%C%9O@Wady%xsns>#6% zmnm9yv%x!j{<$8<+IHk_txSUSlIWcY1Mrkak+CKBs`DBMYGu^DLc;MwLw#%T4gu!7~O zW(u1q`!(bF-xbiADc;3=2b@On%0VaghHpB|aC9=A*6QD+iwoHiQS3sipH5HjRpwKB z6e%f@iuSZQFwJbQE*_Dds$yAM6m;K#sCpOv{=Abp{Cv{hHn{ydU%Ywvc~qFmMthUs zF#iL)g?3kUPXR6N;u2sWr4QAg>^U1wOhbVR8FetBC`;N^0d#1h>~9JS;No2wZ^%@{ zO~!^*b{Qu@XV`!PDR&yNaM@bV5>B4OwXB-bc`2^CwnavBU8u^!Bjcn=x4J;qTw^O9 zN?3g0_REPw2D6nS)z;yL?q)YzJ{W^e%`-ARml^)qk#4OnuvDpVQgs*0@@Z$mgQ)BP z=C@pTzXx4HHAR+*Vj@|;luXoYGsD|8yS|nO<3n7SQ7;NvCR^I-HRq?8eHO%eS94C| zzm8T60QcY7AD3me2>m=I_L6*6;*wYBXt)PWFq{pmYIaq zWoC%Sc~m8)FkbP>(}i0wq3nAUIuV-{i+*}z^eZO}O9dEe1%0Y`$=E(3pJl#=AELc7uynPt|1-S9RW>pn`^|_Y0 z>bV^uMSIl7NcihlLz!bgj`lyC{jB1Gv}SD@ew!;5M7{0Gp9WPNY7VD^y%jw*|H*YK(EuIj`RNz4Kc#HzkK_!E>>!V6utu z6~uR+Tu+1R_ybbE8_oFzUK^k276X~ei5_Fakl|#PT+_3u-)-vVu4ad$!zg>YzFu0> zV)sIWas~WdIKbXV?wFW6U6$@uinId>IjZ17pfI1bn+om-@W;h30)Ye;{LFetWG32m za8Z-twwep(eB5^oPen&Q#r$RUqT!FabM#m4Mhly24Sk;#oK?u2YK>+{_8@QMchUS?^=*^v z>DBm>9c36o5jYAsj}l+%kW5zZeCO_y8}W;h4s@4o8QNMV_563OXe?Lq%daMxAn;qy zUklD&{8?FekuHeNOY22)wCv_mG^*b6G)2c3#PEb%8IGt87tj`rCCx!q(aEO?w%MiC z^|GgxN8W=K^EJ@;-`gSRte@*v)NVy%r)UyBO(Tv@nz|r>x>6sXK!x3X+?D3VzQj{G%aSO`vpAQ!C}B^wHs|ySvAYhGa$D0X3^5K)Q<`a?JSELp5@tpfmbZsT@k7V^ELQQo@GVqCT1hTf<$9oy0BoB<}{#Vb}!Tu9OrYrfIS# z?suOfyPfgWcnXt-0Y+SE75}Aav4nC~kP4NLB;@4wKDJPH7$K2d)RC+w`gMdR69m8+ z9XyMV%e1WqN(MaJ1>7}e>KhN?ZrDH%66GSN2#XA#qFQv45P&)=y^&!pYn{7Jhs%q^@)EO5r@nWe=kO zc4=g8sDX?w-d;9w`rEp^_-u>Hrq3&AZH5ZB4(m-r2G%_pM*(Des#xpt^t;nq$s#(V zQL}1Fu%=S5hKvS0GoMQ1q?66O#y)((A#p=?SBFK0fk*~#l{%N%vtUZ7X|e>>U2FR1 zN0ku^uKd5QXH1p8%v{d_~T2lOuv*#OvkJux(Ip06B&i}O( zn9O2*I1hMf+~5r8DsRNRP9{BIXy5GaKi%KYKQaCKd4AGjMom$GPP-b(hREoT@P%xiSF8Tg}>hWFSrSMU{lFvt!F2Hpe0j` z8GufF7VXh~OVe|%`nAO6Hl1q3 zE6cjllpX;uia{_CpkxS|Q+f-mT{2N~o^4R}<|nvSY{n(yR>Kv#jy$wKTc!Ixja!|x zNmVMs{c?L{rqvP`Lp|N)8SRS$9E$3@CH*CMtB_7Ybc(!43tp%z;~)kdunt5_w5H(Z z{^I2cJBfGhiNvjz;TJUwF%~sxHmx8Pl4xak3I=dQZM)`Lm8MNHQ}+PFVt9bpD3B}a z*N>0dqd>MS)sorjH8Np0v=C)wu#9owq5vuiBwe*(O5X9nGDOGwWs4+%lGI{MaP+KB zLYJqdR%;ZN*ne`;-}>G6)oqO;8eRs)6UoQ`ih#;sxmO}q-6zoKPD zvVN1Db9)IUQL&&UxrOk?j*;E3P%nx0g;J%a&IuGJ=Yp-9-`K`q@MiQmVM>* zYAtT{vmf!%&ZJ1kB~ab#E^bFUwn>L`w>jc~V8;V*U-5W!#_fi<^7Z#d=L-R@!|eX* zV6GUE+S7cPsNE>ayqj zX>*#@C~!01Tg;zFJg7X%cEuyZ0{qV()0b686G*j8)l8^PGP!0NZe3uZLOn?z$h}4U zJ}=SviNS>B=WvXTLlDd4cCw$6))hx_*tb}I)@Sb(PpwQ^!io()N&33tFJ=pqL!4`h zzg4ElDrxQSiG}gf*GB@cQHkAX0+~+>7V^UGY*8gzj>=Rv^KVd@5OnqNDH{vKgDq&v zY#e#yo)j^|KN?^Hx$wBGxjiMp;J&8yJ^WU;CA#eJi`;y2Ey<)Ll7Dl>87uQqh2zxi zQVXV-mAuB;t6){2Yn5w0xqd#J-O2}9?*AcA;YvnbrNZJ--`Bn7oeXlAU2_4cV;ITd+vgmg4pA8_!v|TVI^5Xv9QDGNv2IoEGSeHs1g^EN0Zj>)%Xgzkj!7T+<55=%#QK z8_(m3Wp5GI3(INL#pKD6>HNszlDIjL+P%3GGvyRh5A!MNL50K%j$5eSeOpODW=&z> zUosF#OyY+9RKk{sNJCb026`z5E|PkWBAwSOibiKj_RwGupo)C*)$?RLeTc;w?ic2< z6hHq$Ibf3p@|LFg_`5P1#9mTke0e+2y$XV~aJO{>@^*(^a*MIqJ0xN~8-5F&&C1rS z6@3IFFn}_fgF_q1QPW9vf$Ok%9EYL zpA3@xojTDo1e>waW#XYu@uZAET>_@Ul<(kaUknNas19Bi6R2k^7FbA8LL>-oxxBtT zGHqwd^|-&QvCSQLj#E=xOGH8vQf=O620*a!q^2Dk*E~Be>3mKRajB`4xHLkzASa*& zI`fb*x8HPf`yH`vAoe9#RtMXMMNJVMFuTzyHiVB*)C24&0T3uHsEp6;MRcd`#5Y+5 z-`0EpT4Z*+_p`Q*lO588?-w4ygq?HQwqC~gIlFwf?dNEqV-Y4rrodz^OBKSMOHuA_ zlFAA`i;tX*c#m}faH6WM#~3+`VWi@oi0-OaUwLfC9|C{_G`}m5no#9)Y}}6Uk<+R#Zs3SH*~HJad13bh97` zCzbI3Lum0Z?V7&!SNL0lr%kZJI0OddhjTH{@-@0toV0?68qx;uJxwr)o4K{ab)-Uu zaM|n?)1Z}l)pr1l%Gdd3S6=JUdu0ao*e)(E0ER=Qv#$f>lX~q`_wjfiS{!Xn;Q-KZ z0%o=BSb!bWK2@nGE1URWibY13{SnwrKQ@JfK|H&;(Q%d3Gk5d_h%ocmI5Pu36=U15 z+X;Qi0iIN#>?tXv6I>TRXpc{ya@RGH93g!G3=6(&mI>dd3#UlJ_&4JXgfj5NJ$5=l zeqa5Zn;0FlYGx#-VFQ|9{FJSna_lXIl#mcPz{7R?H#^VbPP6M&z!?t_(Km`~J4YNx zrwvr!r=mbtdlCy=T--3Ecf^|Ml7kcG;HC>f6gspo4bY=Ze=MzeVU|d09<;!A$FWgI zT>}U5Os%XVJc~6`QI`*ijy|y|D=#0uv`a-rCE~Sj+PR^eE`S9x=sH1`Y5ZcFule=G z@fkcXVcAX0@H$dl>BNm+XhYx+BEA=w~zG2vq|%uj!t>*@e|Fdel)vHdxP75l!N^ep0=7z?S#9v z$7irgGHj7B78b7rLu(T>0f=3L8Nvjp7`Q>Xtnh^v;q~DxUzv%S6abg8>#-IUUQ6>m%L*1}^02nHCX4~cF z%DZE3Bu6@7aBwhaL=gZF^f3g+&dwT;Qc&b6rEq!uN>msv)veU&SkFt4ik6EZw~;pF zAa~oI#|GHR=72zTXK-Wd zAvp|3h)VI#FNM2(()%?2^q|6vFD@BJK9v)4d$x-fqS|%!LA>sAO~uE;bC>pg_2*nRsg>s^lvBn&3opi* z;LOQbdGc{zE{?Z(-M6$Lhj=jW7Cej4f`3|z5p>SIa}O$EHK z5lT>9gRjP+Y<}Gt3;g$hgFwXiiQ&fQl>J|Etm%^A%rlbYq0=Q8v_7l`afE}29D*L# zZTwXW7eO6AmmhlpSznmnk$UGVj~1!r#DDqnMB<>EM8p$8i_CNb=*c!JivauTx*B2O^*C@r(V}<0;Uc zc1068K(A0tL8c;}s`OITtB|D;5@zL2KzP6n7mSKzee=4Ydo|>I?Oiq;_~$WB)>G`} zDm1+KX^wcd$bMwq`B0DVz~F`o{2SQ52bQShMpG2Ey|44?zJ;fBh(!~4WcGq|qfhkI z8l#FCz}zQ7p8&ShRY3bjv{b4PjAlH!DZ(*7QkX_v$3fx4)TiAL$7Lx%==gxGH!`2{ z!N;YfkSJ7sXZ1T-%SYkxrt7w8Ul{I3^Zf~N+iHO1&kosCAD`PR1&+Bqj4~n8D5~BaKD_J6$ zEQA3#?Mgrtg~F4O5&HqqzyE&Ou>4#oivQmei2i%R!_-zXw-y$e84&&i4<-Wb%gT}x zO=9~LOqGDtFFvH|$@8TR>u-BrGk{)flL)gdzWy$FG6Pcm0s7Cg$p(GbFDtNUy82=v-_rqihEr!rRPS&@-n2$0`p zjJ|uo3=%6UD*k)-Zi2zTZm}WAboQv-P(fxKvRgoz2inn-;Gb4_uypAEZ3t*>v+JQl zfbJ2l|8Efgz8wr3qK3Sm5rIJDCDq))#Q5zO%Ymm8bpdE8q|)erZRe3A zS25NLn6#0svAp0(VB{Jd!62;*G==%EDay=MpdYWqDsUnjrgSfAq5+|C@cG^UKF8v{ z!GdLj&=PnC(kKAWNHm?kytbeGqZNUC&cAu$B_5RT$N^qK@D|p8KRs}N1poK=(0>vY z+bw1{8n=UN@m1)5Eglq#0dw>JED5|@9xfX|kpK5`C7N3PT`7wH9(+vf22{NNy&CeZ zYQYsVZ0e*)Q6iN>`o@Sfx_M~a45l)tT*2fB|2H|T(Yb=rz|RXt9R9CMr0%7HA=yQu z;CCiozy1nG61)UT^Ro*%65M|cvfAc}Nd6W0)W7D0ERo!T?GSX$1|P^T1${G8P;;%Z z#QrZ|=`obC`rA_2|Fnb$OEgB#F(lu> zo80w^BV=S{C(SZ1kxhfwLzKV!4*6eVQ)EApum9g~+K?O}QIqt`0@jLS`rDH^r?o@{`mS_AJ08b*5n3rfN+d&0E~{R( zS=dQaR|w`BKxCT?5TeZYyZkpk(y;up|4(H(5MZhHOUPNHe~rVo=%*{WmR@pC`&aWiiE`s5Z|EW@Pl4~LYSvY z{Rsf*`S!Bo_Uo^K?xUZe=LeM`ttYc~@TCsv z;cUqmparfVk=_BTz`N8`y*5}_ zfc!&+RCL_<>>fOgz(;;OmZw0_3Mwj^wkuk;+SWmKdbRz}8lT1-fC`6&h2?N+Y0w4q zVzcT_v+Bo1$&N38SpL;_KqnZ?A9DA~BXw-bza;|SzCDKo5$0--q;SdXO_q>=y!GK6 z={?U&F!;o@G^%~k1nClV z#K?j!9}NJ7t{PnSK_ev<6%{F=+s8f-yqC?zWo6yKUgZIz7j&92WSKf-xw{b>iuUXG z@B6&GMA5l*M-KIDF+IV6vgtlIH-Vy};wO51Zyv5U=6*;;HGwyY!u)}7uL2nKl|@1# z;TnmUkaK+9-Nc^r?O5P_Kt2am>#&7T0407AO4$PF(_|PSNjylu$t*YEMU%-!j=3V;ehNzQO zi=NI@7|*TaVJg0Ok@TKn{Qpf9MCHp%GE4v8b+EWKqz?9I9h7h(_g{?#7SIh7KYk2H z@+75MWslLjUL4sVTV>_qk~c7*?oD7w0LYN^K#AYIx%nshky|fh?Q|=X1pqR7QgX6} zi3!)PcD{A@|D$xq98nI;oWvUp+6$R)ju!;jt+O3bQUJL{PHP|KKuk4Kpuw~TB+@@X z-hh8!4?D4usttH0qW!%kkpEQ-`g=hCKfEbd_U+p@b%M9loj3Wiqyt`8i4+Oa55<|` zNx-lI#6J^MtBLO_0i6io6y%!x3dP72bH`3*R{4UYOo1%!5e{ z+eg^}7n|w?5fVCOdT-yqC*4_Wqd~emYDc7S^2Q?nY>Y(sSpTX9as_rZ`d<4b7m~l? zt7Yv9lHm)G4UlYoBJ$e7!j!n_kLqJM2P5XD_uT?R{j@vR_yE`um2D@~h}J)<944)l zL6^3~MXRBU*^<7dd};dtIuS6loM8&t{~+ADFW2@!A$~boO{yz~yFIepu#u641rt

K{gMfsu{uz;u0A;|RL8&x2eANl~w+?O`*`4F&eE?{x4g;AJ5I_s4`tzP;HQ^$dl8%(bD zGczPb46%Ag$lgXncxt1ATN{m@DLJmTBQAUU*Q8Njxp^FLTNgl zHmoPP{}Sovy4>qX0HV=p$^&g^AP;JB8G*TP*M$hy)6x6>>iM_MGzrpg)~btMr$Qt8 zi*f>@0aEhLd@~7BU{Cgz62`}0lmV|L%ofA*MW(pe&nV89?6lAxZbZHpnk5Ts=EjyHD@`%WlgT_3?JP zUG2}&%9qll+P6?yZqrZcbCj<-@%5>}oe93L!>GVqAvWvfE9xty{>+EtiUh)TVNf z+`{N08D>gMWpbXEa`tmR^~atwfBE>#<@fu&-`n$i-}n2tlr!EbYdEdFZrmGuDJ%E7 z+QFf|x}5tPTkq7=+$7YgPv`Ra+S=TVwDXn8qa!Cv||Q07HRXtF9baESr#GB2ob$)VxW}690*IVWKD{`}V>I(xR+mXW@lmi64Hub>~Lq+dRGoD2@Yz{b+n{ZaPO6OS55 z&GL0uUYb8PMIUCdWN##gj_ydGTXXuf8VMH$zFbZ)`(=A+k+6M$nI>}Hym>x6ueON^c1CgkNE>me97-D%s)!F?&n_Q3erTp_EJZL?S-A8uxPooV(G+M&-y>^w(a|K z@z&CS#S!v|RLDn%5NW{P1_hZAlZ;b0c5lpdX!#``uFJW7`yxOArEL>^uF-o2re-54fQ63B>g@?|(-+8wFxeRHjT z`R{ag_zUEi6%WqSeUV)!@tM*M84>0~5w=%6Zr$*zQ0f!%g$mBK(bL(MDQ;JreSi@> zdLE_-d$V#!`n&U9h#%!?sN&Q}+gZAv2Y_ z7F|s*`fl>*tWoxYTTL)`SdY z>1{8G!Wu^5;OIs?6OJDb2TZ+4v;g~E zuj~**;*X=PjO-)_T6(-=uPryL%;sQg)<=B+0sx+ zj2-JbLtR~$i6hGLai*qAj6MOP#T!B}IeR57LVin@1JrVKNIZU>b$oAY;d*EPMh9X* zQyQ;miYp9yBA~iMuED+Y=3y@I-We1&ToMJZde%~#T*Jz7Xt;5Xj+H3e@YFZoYpjY@ zr2&_;hV~`K`hJE7wQ=@L=5^F6B_#1%+1keUG-RA87F4TO9TJ#M>h6)G4B1)L# z$~1|eWfG1726D~K&7@4E_)KOb7<_(i+1Fl3M@L87*x2w7di(ksELybaVd;T^4O8jv zsth{i``K9Z(D#GDXEwIB)$n>2U;`rf9y+09HaIv)UaU)}+tH)5Gcs1JSu>do?I#6O z)|PDXL%}*F4d2Gbe4+-t*2FdxB(<0L-y+o?qL%x;UkxaS02T;V#;b*SHMZ`k+in7I zVFBB`xZPPO)KE(iY#1R=YPpqsJq;V1-XZF?(tvCbM#|8kRKb{+8NlrwBmKkMPXu8> zSSI_QBci1}CaRO%8z51v(akI7vUE9#nyXuO@@M>KDYN4eOP(d@`KCAM6?&ke3GG=Arc;v?YBu4+A;FnIIt(>7#`T(5oG!b`fwYnkfXp8 z8W=&qN?k4^met6DJWm%HDuhjGo}>Wrwtj5Wg)^V@{A}H3Ng}iL#~LVW=l?(i^^ry#m>d#e_G>ViI59#CcVpB zGpo$Yk<`2Bbl&=D>8@!6-u(>Cm@!xd>&$-<^6ptu&!cR0(*ymnq#Q@UV|aV(^#zm` zYDel#9gpoGC&HC3Zc4K?ShZpzq!^!{8*w;cyP8bOD4rUbd#|Ej#t(m7;-3}pK{vNd zgQ}oV;IGuHbO~wz)`PE_?8fRLncB&-Y30xsV16OjA z__8&4jw(<@t^EU<|N0{LLViaHhvxACK&?U&5YS304)}m(Sbltja*VaPXs}+x$Y{&c z=g(znWv`ZwdHwYd-n<}7#4EFSi+x|c0cAt4huvK64x=*$z>I@^%?fhICQ(R_Y8Ev5 z7&4^lgqAa>CQ*~*Izl6$p=oTa0#VjqP5m>6g3(cwtuu@MX2J(OVxLxhj!4GUI%NwP zRuAqnOWSkyw07>hcA@^|5)}R@le$kjy41}{N=|0$<`j$LXGwz(v-^*Ma_xk`^(ws5 zBj@H#n9^U#6Yp3%AuCgYo&eI(<4IW%0igf`l;vslT|mbOUebl$?JT!jk#0kM&9pf` z`?-CI$wQv(qGc)!cHd@uAQB2u`xIoTV~2qD2@K|n@&4LPg;>?9`MLHQM%SJ()}+x^ zd`QsSRycLKIiDgcE6Xq`ORp;a0&|HxQpu(@Op=kbwx>{tm^!p+_lKaY5kAuoiw0#d z))<6#v_R&f!i0INY6eZj(m287mAs}UE_#NBhN&1#!LV0!GdfcR9iR^zrbkP3YnYAm zb^rAlefx4L5gFSAtdj>a4sI3^yzFVk*RFE)W}O}=IR%$nfIKq&>Iy{lj&c8YF2Jyf7&(yZFCi^cekX5ukT=x?;N=Ol)4&=K+-M}j!q?O0_opX%Vx!In9fE^W6@YkLj!E+ z51yVHAbkMWIRq8jS-g`-4jr25a*$_8xye$}C7i8L7)V{; zORW*u8mV3djgGFQOD#o-RX3I+Eb7LMR6>>m(Ygz`RhU^mMR^D4lRy;Yf^N1Yx3Jaw z7U0$~TzEuZIoQEFHb7w;ribH!Gj`rTYk~rq>{f6_RDp1>_!Ump{qcH4g%1!RzVcSYZM#dfsCi-(e z+o2jM&eNk|zZmeT?t*6(L6}gu$N4Vqy&zoUOhNyq7 zjm=N&B3y@}(Re3qxohZKrJ?@z7?f@9cpDik1KKd9{@S+3gy?6ojelE*OJNqpsTzpG zpox@p-#`Cs+0%!c>53YoS1WcEaIvI#+p@5a=8v+s9V2%}2EW1DuI%lwc+3kRyQhdk z>UGi3Hne^24j5uXm_6qGkbywJ8&NSaD?#Ry$VN&c0>@F)9R?^MTP#TXVVZH9eCmO9 z=w!_e@I|vy1J$YACq9YTEy+KDK#Tl4I?15X$d{s9BuR+1sWZQo*jxTq`INqE|FBwj zb=)oBMgyof{eJ0E*Yq&qBuXc2s0vrhf8{!=hHB+DrtRec5%>)}p)2k^372#l>ur*D q!hEs6kk0?JcmAKw{Wm|(9~G0G*W?iOqxen=UTdsvt#TMU!v76BSQ)DT diff --git a/reports/summary.txt b/reports/summary.txt deleted file mode 100644 index 9dab488..0000000 --- a/reports/summary.txt +++ /dev/null @@ -1,21 +0,0 @@ -BACKTEST SUMMARY -======================================== -sharpe: 0.12450603119200966 -max_drawdown: 18.40327026827532 -max_drawdown_len: 251 -total_return: 0.04945463098329521 -avg_return: 0.00010217898963490745 -total_trades: 695 -won_trades: 357 -lost_trades: 333 - -SIGNAL IC -======================================== -ic_mean: 0.03559005065789478 -ic_std: 0.2671528697174321 -ir: 0.13321979545096566 -rank_ic_mean: 0.022676047691591646 -rank_ic_std: 0.24733432522614088 -rank_ir: 0.09168176584814361 -hit_rate: 0.5315904139433552 -n_periods: 459 diff --git a/requirements.txt b/requirements.txt index b018091..08552bf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,3 +4,5 @@ baostock>=0.8.8 pandas>=2.0.0 matplotlib>=3.7.0 pytest>=7.0.0 +click>=8.0.0 +pyarrow>=14.0.0 diff --git a/results/daily_pnl.parquet b/results/daily_pnl.parquet deleted file mode 100644 index 5f55398e59bb4655ffbc0998f48ab99ab358d033..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10443 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 YxWBg_ZQei*2mj$*!qd_f|2M|}1A-*q%K!iX diff --git a/results/signals.parquet b/results/signals.parquet deleted file mode 100644 index ed380662c9d1f3265390b003aead583b46de3a6f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 148034 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 signal -> cross-sectional IC --> multi-stock backtest (AlphaStrategy + RankEqualWeightBuilder) -> reports. - -Usage: - python3 run_example.py --universe hs300 --signal reversal - python3 run_example.py --universe csi500 --signal reversal_vol -""" -import argparse -import logging - -import pandas as pd - -from analysis.report import dump_daily_pnl, dump_signals, generate_report -from backtest.config import BacktestConfig -from backtest.runner import BacktestRunner -from data.downloader import download_batch -from data.universe import SYMBOLS, CSI500_SYMBOLS -from eval.metrics import evaluate_cross_sectional -from portfolio.builder import RankEqualWeightBuilder -from signals.reversal import ReversalSignal -from signals.reversal_vol import ReversalVolSignal -from strategies.alpha_strategy import AlphaStrategy - -logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") -logger = logging.getLogger(__name__) - - -def _forward_returns(data: dict[str, pd.DataFrame], horizon: int) -> pd.DataFrame: - """Build a date-indexed DataFrame of ``horizon``-day forward returns per stock.""" - forward_returns: dict[str, pd.Series] = {} - for sym, df in data.items(): - fwd = df["close"].pct_change(horizon).shift(-horizon) - fwd.index = pd.to_datetime(df["date"]) - forward_returns[sym] = fwd - return pd.DataFrame(forward_returns) - - -def main(forward_horizon: int = 5, universe: str = "csi500", signal_name: str = "reversal_vol", - dump_dir: str = "results/"): - universes = {"hs300": SYMBOLS, "csi500": CSI500_SYMBOLS} - symbols = universes.get(universe, CSI500_SYMBOLS)[:30] - - signals = { - "reversal": ReversalSignal(lookback=5), - "reversal_vol": ReversalVolSignal(lookback=5, vol_window=20), - } - signal = signals.get(signal_name, ReversalVolSignal(lookback=5, vol_window=20)) - - start, end = "2023-01-01", "2024-12-31" - initial_cash = 1_000_000 - - logger.info(f"Universe: {universe} ({len(symbols)} stocks), Signal: {signal.name}") - - # 1-2. Download daily data for the universe. - data = download_batch(symbols, start, end) - data = {s: df for s, df in data.items() if df is not None and not df.empty} - logger.info(f"Downloaded {len(data)}/{len(symbols)} symbols") - - # 3. Compute the signal per stock. - signal_series: dict[str, pd.Series] = {} - for sym, df in data.items(): - sig = signal.compute(df) - sig.index = pd.to_datetime(df["date"]) - signal_series[sym] = sig - - # 4. Cross-sectional IC at the matching forward horizon. - signals_df = pd.DataFrame(signal_series) - returns_df = _forward_returns(data, forward_horizon) - signal_eval = evaluate_cross_sectional(signals_df, returns_df) - - # 4b. Multi-horizon IC. - horizon_evals = { - h: evaluate_cross_sectional(signals_df, _forward_returns(data, h)) - for h in (1, 5, 20) - } - - # 5. Attach the signal column to each DataFrame and build feeds. - config = BacktestConfig( - symbols=list(data.keys()), - start_date=start, - end_date=end, - initial_cash=initial_cash, - sizer_percent=0.95, - ) - runner = BacktestRunner(config) - builder = RankEqualWeightBuilder(top_pct=0.2) - for sym, df in data.items(): - df = df.copy() - df["signal"] = signal.compute(df).values - runner.add_signal_data(df, name=sym) - - # 6. Run the multi-stock backtest. - results = runner.run_prepared(AlphaStrategy, {"builder": builder}) - - # 7. Reports. - artifacts = generate_report( - 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. - print("\nSIGNAL IC") - print("=" * 50) - print(f"Universe: {universe} | Signal: {signal.name}") - print(f"IC mean / std / IR: {signal_eval['ic_mean']:.4f} / " - f"{signal_eval['ic_std']:.4f} / {signal_eval['ir']:.4f}") - print(f"Rank IC mean / std / IR: {signal_eval['rank_ic_mean']:.4f} / " - f"{signal_eval['rank_ic_std']:.4f} / {signal_eval['rank_ir']:.4f}") - print(f"Hit rate: {signal_eval['hit_rate']:.2%}") - print(f"Periods: {signal_eval['n_periods']}") - - print("\nMULTI-HORIZON IC") - print("=" * 50) - print(f"{'Horizon':>8} {'Rank IC':>9} {'Rank IR':>9} {'Hit rate':>9} {'Periods':>8}") - for h, ev in horizon_evals.items(): - print(f"{f'{h}d':>8} {ev['rank_ic_mean']:>9.4f} {ev['rank_ir']:>9.4f} " - f"{ev['hit_rate']:>8.2%} {ev['n_periods']:>8}") - - print(f"\nReports written to: {artifacts}") - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Chinese equity quant backtest") - parser.add_argument("--universe", default="csi500", choices=["hs300", "csi500"]) - parser.add_argument("--signal", default="reversal_vol", choices=["reversal", "reversal_vol"]) - parser.add_argument("--dump-dir", default="results/") - args = parser.parse_args() - main(universe=args.universe, signal_name=args.signal, dump_dir=args.dump_dir) diff --git a/signals/__init__.py b/signals/__init__.py deleted file mode 100644 index 6b2ba11..0000000 --- a/signals/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Alpha signal abstractions.""" -from signals.base import AlphaSignal -from signals.reversal import ReversalSignal -from signals.reversal_vol import ReversalVolSignal - -__all__ = ["AlphaSignal", "ReversalSignal", "ReversalVolSignal"] diff --git a/signals/base.py b/signals/base.py deleted file mode 100644 index 1beeab0..0000000 --- a/signals/base.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Base class for cross-sectional alpha signals.""" -from abc import ABC, abstractmethod - -import pandas as pd - - -class AlphaSignal(ABC): - """A signal that maps a single stock's OHLCV history to a per-bar score. - - Higher scores indicate a stronger expected forward return. Implementations - operate on one stock at a time; cross-sectional ranking happens downstream. - """ - - @abstractmethod - def compute(self, df: pd.DataFrame) -> pd.Series: - """Compute the signal for one stock. - - Args: - df: OHLCV DataFrame with at least a ``close`` column, ordered by date. - - Returns: - Signal series aligned to ``df`` (NaN where undefined). - """ - - @property - @abstractmethod - def name(self) -> str: - """Human-readable signal identifier.""" diff --git a/signals/momentum.py b/signals/momentum.py deleted file mode 100644 index cd8f7af..0000000 --- a/signals/momentum.py +++ /dev/null @@ -1,21 +0,0 @@ -"""Short-horizon momentum signal.""" -import pandas as pd - -from signals.base import AlphaSignal - - -class MomentumSignal(AlphaSignal): - """Positive trailing return: stocks that rose score high (momentum). - - The signal is ``close.pct_change(lookback)`` — opposite of ReversalSignal. - """ - - def __init__(self, lookback: int = 5): - self.lookback = lookback - - def compute(self, df: pd.DataFrame) -> pd.Series: - return df["close"].pct_change(self.lookback) - - @property - def name(self) -> str: - return f"momentum_{self.lookback}d" diff --git a/signals/reversal.py b/signals/reversal.py deleted file mode 100644 index b516a76..0000000 --- a/signals/reversal.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Short-horizon reversal signal.""" -import pandas as pd - -from signals.base import AlphaSignal - - -class ReversalSignal(AlphaSignal): - """Negative trailing return: oversold stocks score high. - - The signal is ``-close.pct_change(lookback)``, so a stock that fell over the - lookback window gets a positive (bullish) score. - """ - - def __init__(self, lookback: int = 5): - self.lookback = lookback - - def compute(self, df: pd.DataFrame) -> pd.Series: - return -df["close"].pct_change(self.lookback) - - @property - def name(self) -> str: - return f"reversal_{self.lookback}d" diff --git a/signals/reversal_vol.py b/signals/reversal_vol.py deleted file mode 100644 index f9ea5f0..0000000 --- a/signals/reversal_vol.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Volatility-scaled short-horizon reversal signal.""" -import pandas as pd - -from signals.base import AlphaSignal - - -class ReversalVolSignal(AlphaSignal): - """Reversal score normalized by trailing volatility. - - The raw reversal ``-close.pct_change(lookback)`` is divided by the rolling - standard deviation of daily returns over ``vol_window``. Scaling by - volatility damps the score of noisy, high-vol names so the signal favors - oversold stocks whose move is large *relative* to their own volatility. - """ - - def __init__(self, lookback: int = 5, vol_window: int = 20): - self.lookback = lookback - self.vol_window = vol_window - - def compute(self, df: pd.DataFrame) -> pd.Series: - reversal = -df["close"].pct_change(self.lookback) - vol = df["close"].pct_change().rolling(self.vol_window).std() - return reversal / vol - - @property - def name(self) -> str: - return f"reversal_vol_{self.lookback}d_{self.vol_window}d" diff --git a/strategies/alpha_strategy.py b/strategies/alpha_strategy.py deleted file mode 100644 index 19f4eba..0000000 --- a/strategies/alpha_strategy.py +++ /dev/null @@ -1,52 +0,0 @@ -"""Signal-driven multi-stock strategy.""" -import backtrader as bt -import pandas as pd - - -class AlphaStrategy(bt.Strategy): - """Trade feeds based on precomputed ``signal`` line. - - Supports two builder modes: - - ThresholdBuilder: per-stock threshold (passed ``(signal_value, in_position)``) - - RankEqualWeightBuilder: cross-sectional ranking (passed ``{symbol: signal}`` dict) - """ - - def __init__(self, builder): - self.builder = builder - - def next(self): - # Collect all signals - signals: dict[str, float] = {} - for data in self.datas: - sig = data.signal[0] - if not pd.isna(sig): - signals[data._name] = float(sig) - - if not signals: - return - - # Detect builder type: if RankEqualWeightBuilder, use cross-sectional mode - from portfolio.builder import RankEqualWeightBuilder - if isinstance(self.builder, RankEqualWeightBuilder): - actions = self.builder.build(signals) - for data in self.datas: - name = data._name - if name not in actions: - continue - action = actions[name] - if action.action == "buy": - self.order_target_percent(data=data, target=action.size_pct) - elif action.action == "sell": - self.close(data=data) - else: - # Legacy per-stock ThresholdBuilder - for data in self.datas: - name = data._name - if name not in signals: - continue - in_position = bool(self.getposition(data).size) - action = self.builder.build(signals[name], in_position) - if action.action == "buy": - self.order_target_percent(data=data, target=action.size_pct) - elif action.action == "sell": - self.close(data=data) diff --git a/strategies/base.py b/strategies/base.py deleted file mode 100644 index c158bfe..0000000 --- a/strategies/base.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Base strategy and example SMA crossover for Chinese equities.""" -import backtrader as bt - - -class SmaCross(bt.Strategy): - """Simple SMA crossover strategy: buy when fast crosses above slow, sell when below.""" - - params = ( - ("fast", 10), - ("slow", 30), - ) - - def __init__(self): - self.fast_ma = bt.indicators.SMA(self.data.close, period=self.params.fast) - self.slow_ma = bt.indicators.SMA(self.data.close, period=self.params.slow) - self.crossover = bt.indicators.CrossOver(self.fast_ma, self.slow_ma) - - def next(self): - if not self.position: - if self.crossover > 0: # fast crosses above slow - self.buy() - elif self.crossover < 0: # fast crosses below slow - self.close() diff --git a/strategies/reversal.py b/strategies/reversal.py deleted file mode 100644 index ab6e6c3..0000000 --- a/strategies/reversal.py +++ /dev/null @@ -1,27 +0,0 @@ -import backtrader as bt - - -class FiveDayReversal(bt.Strategy): - """Buy on 5-day oversold signal, sell on bounce or time stop.""" - - params = ( - ("lookback", 5), - ("entry_threshold", -0.05), # buy when 5-day return < -5% - ("exit_threshold", 0.0), # sell when 1-day return > 0% - ("max_hold", 3), # max hold days - ) - - def __init__(self): - self.roc5 = bt.indicators.RateOfChange(self.data.close, period=self.params.lookback) - self.hold_counter = 0 - - def next(self): - if not self.position: - if self.roc5[0] < self.params.entry_threshold: # RateOfChange returns a fraction, not % - self.buy() - self.hold_counter = 0 - else: - self.hold_counter += 1 - roc1 = (self.data.close[0] / self.data.close[-1] - 1) * 100 - if roc1 > self.params.exit_threshold * 100 or self.hold_counter >= self.params.max_hold: - self.close() diff --git a/tests/test_alpha.py b/tests/test_alpha.py new file mode 100644 index 0000000..9da04ef --- /dev/null +++ b/tests/test_alpha.py @@ -0,0 +1,180 @@ +"""Tests for pipeline alpha computation and combination (no network).""" +import textwrap + +import numpy as np +import pandas as pd +import pytest + +from pipeline.alpha.base import BaseAlpha +from pipeline.alpha.compute import compute_alpha, evaluate_alpha +from pipeline.alpha.registry import ( + available_alphas, + get_alpha, + load_alpha_module, + register_alpha, +) +from pipeline.combo.combine import combine_alphas, _equal_weight +from pipeline.common.schema import ALPHA_COLUMNS, COMBO_COLUMNS + + +def _make_data(n_days: int = 30, symbols=("sh600000", "sz000001", "sh600519")) -> pd.DataFrame: + """Build a synthetic long-format DATA_COLUMNS frame with deterministic prices.""" + dates = pd.date_range("2024-01-01", periods=n_days) + rng = np.random.default_rng(0) + frames = [] + for i, sym in enumerate(symbols): + # Distinct drift per symbol so the cross-section is non-degenerate. + close = 100.0 + i * 5 + np.cumsum(rng.standard_normal(n_days)) + frames.append(pd.DataFrame({ + "symbol_id": sym, + "symbol_name": sym, + "date": dates, + "open": close, + "high": close, + "low": close, + "close": close, + "volume": 1_000.0, + "amount": 1_000.0 * close, + })) + return pd.concat(frames, ignore_index=True) + + +def test_compute_alpha_schema_and_naming(): + alpha = compute_alpha(_make_data(), "rev5", "reversal", lookback=5) + assert list(alpha.columns) == ALPHA_COLUMNS + assert (alpha["alpha_name"] == "rev5").all() + + +def test_reversal_sign_matches_negative_trailing_return(): + # Cross-sectional z-score preserves the sign relative to the cross-section, + # so the stock with the most negative trailing return ranks highest. + data = _make_data() + alpha = compute_alpha(data, "rev5", "reversal", lookback=5) + close = data.pivot_table(index="date", columns="symbol_id", values="close").sort_index() + raw = -close.pct_change(5) + last = raw.index[-1] + expected_top = raw.loc[last].idxmax() + got = alpha[alpha["date"] == last].set_index("symbol_id")["weight"].idxmax() + assert got == expected_top + + +def test_weights_are_cross_sectional_zscore(): + # Each date's weights are a z-score, so the per-date mean is ~0. + alpha = compute_alpha(_make_data(), "rev5", "reversal", lookback=5) + per_date_mean = alpha.groupby("date")["weight"].mean().abs() + assert (per_date_mean < 1e-9).all() + + +def test_evaluate_alpha_keys(): + data = _make_data() + alpha = compute_alpha(data, "rev5", "reversal", lookback=5) + metrics = evaluate_alpha(alpha, data) + for key in ("cumulative_return", "sharpe_annual", "turnover_annual", + "max_drawdown", "hit_rate", "n_dates"): + assert key in metrics + + +def test_equal_weight_is_mean_of_alphas(): + data = _make_data() + a = compute_alpha(data, "rev", "reversal", lookback=5) + b = compute_alpha(data, "mom", "momentum", lookback=5) + combo = _equal_weight([a, b]) + # reversal = -momentum before z-scoring, but after independent per-date + # z-scoring they are exact negatives, so the equal-weight mean is ~0. + assert combo["weight"].abs().max() < 1e-9 + + +def test_combine_alphas_schema(tmp_path): + data = _make_data() + a_path = tmp_path / "a.pq" + b_path = tmp_path / "b.pq" + compute_alpha(data, "rev", "reversal", lookback=5).to_parquet(a_path, index=False) + compute_alpha(data, "revvol", "reversal_vol", lookback=5, vol_window=10).to_parquet(b_path, index=False) + combo = combine_alphas([str(a_path), str(b_path)], "eq", method="equal_weight") + assert list(combo.columns) == COMBO_COLUMNS + assert (combo["combo_name"] == "eq").all() + + +# --- registry / factory ----------------------------------------------------- + +def test_builtins_are_registered(): + assert {"reversal", "reversal_vol", "momentum"} <= set(available_alphas()) + + +def test_get_alpha_filters_unaccepted_params(): + # reversal only accepts lookback; passing vol_window too must not error. + alpha = get_alpha("reversal", lookback=7, vol_window=99) + assert alpha.name == "reversal" + assert alpha.lookback == 7 + assert not hasattr(alpha, "vol_window") + + +def test_get_alpha_unknown_raises(): + with pytest.raises(KeyError): + get_alpha("does_not_exist") + + +def test_register_duplicate_name_raises(): + available_alphas() # ensure built-ins loaded + + with pytest.raises(ValueError): + @register_alpha + class Dup(BaseAlpha): + name = "reversal" + + def signal(self, close): + return close + + +def test_register_rejects_non_basealpha(): + with pytest.raises(TypeError): + register_alpha(object) # type: ignore[arg-type] + + +# --- base class -------------------------------------------------------------- + +def test_to_weights_are_per_date_zscore(): + class _Const(BaseAlpha): + name = "_const_test" + + def signal(self, close): + return close # arbitrary finite signal + + close = _make_data().pivot_table(index="date", columns="symbol_id", values="close") + weights = _Const().weights(close.sort_index()) + # Each date demeaned to ~0. + assert (weights.mean(axis=1).abs() < 1e-9).all() + + +# --- external plugin loading ------------------------------------------------- + +def test_load_external_alpha_module(tmp_path): + module_path = tmp_path / "my_external_alpha.py" + module_path.write_text(textwrap.dedent(''' + import pandas as pd + from pipeline.alpha.base import BaseAlpha + from pipeline.alpha.registry import register_alpha + + @register_alpha + class ExternalDemoAlpha(BaseAlpha): + name = "external_demo" + + def __init__(self, span: int = 3): + self.span = span + + def signal(self, close: pd.DataFrame) -> pd.DataFrame: + return -close.pct_change(self.span) + ''')) + + load_alpha_module(str(module_path)) + assert "external_demo" in available_alphas() + + # The factory forwards the external alpha's own param (`span`). + instance = get_alpha("external_demo", span=4, lookback=99) + assert instance.span == 4 + + # And it works end-to-end through compute_alpha. + result = compute_alpha(_make_data(), "ext", "external_demo", span=4) + assert list(result.columns) == ALPHA_COLUMNS + assert (result["alpha_name"] == "ext").all() + diff --git a/tests/test_downloader.py b/tests/test_downloader.py index 77230a3..a472262 100644 --- a/tests/test_downloader.py +++ b/tests/test_downloader.py @@ -11,8 +11,22 @@ def test_download_single_stock(): assert df["close"].notna().all() -def test_download_baostock_fallback(): - """Test baostock works as secondary source.""" +def test_download_baostock_primary(): + """baostock is the primary source for 'auto'.""" df = download_daily("sz000001", "2024-06-01", "2024-06-15", source="baostock") assert df is not None assert len(df) > 0 + + +def test_download_akshare_fallback(): + """akshare works as the secondary source when reachable. + + akshare is the fallback precisely because it is unreliable on some + networks; skip rather than fail when it cannot be reached. + """ + try: + df = download_daily("sh600000", "2024-01-01", "2024-01-31", source="akshare") + except RuntimeError as e: + pytest.skip(f"akshare unreachable on this network: {e}") + assert df is not None + assert len(df) > 0 diff --git a/tests/test_eval.py b/tests/test_eval.py deleted file mode 100644 index df049ab..0000000 --- a/tests/test_eval.py +++ /dev/null @@ -1,53 +0,0 @@ -"""Tests for cross-sectional IC evaluation.""" -import numpy as np -import pandas as pd - -from eval.metrics import evaluate_cross_sectional - - -def test_cross_sectional_keys_present(): - dates = pd.date_range("2024-01-01", periods=10) - cols = ["a", "b", "c"] - rng = np.random.default_rng(0) - signals = pd.DataFrame(rng.standard_normal((10, 3)), index=dates, columns=cols) - returns = pd.DataFrame(rng.standard_normal((10, 3)), index=dates, columns=cols) - res = evaluate_cross_sectional(signals, returns) - for key in ( - "ic_mean", "ic_std", "ir", "rank_ic_mean", "rank_ic_std", - "rank_ir", "hit_rate", "n_periods", - ): - assert key in res - - -def test_perfect_signal_has_positive_rank_ic(): - # When the signal equals next-period returns, rank IC should be ~1 each day. - dates = pd.date_range("2024-01-01", periods=8) - cols = ["a", "b", "c"] - rng = np.random.default_rng(42) - returns = pd.DataFrame(rng.standard_normal((8, 3)), index=dates, columns=cols) - signals = returns.copy() # perfect foresight - res = evaluate_cross_sectional(signals, returns) - assert res["rank_ic_mean"] > 0.99 - assert res["hit_rate"] == 1.0 - assert res["n_periods"] == 8 - - -def test_inverted_signal_has_negative_rank_ic(): - dates = pd.date_range("2024-01-01", periods=6) - cols = ["a", "b", "c"] - rng = np.random.default_rng(7) - returns = pd.DataFrame(rng.standard_normal((6, 3)), index=dates, columns=cols) - signals = -returns # perfectly wrong - res = evaluate_cross_sectional(signals, returns) - assert res["rank_ic_mean"] < -0.99 - - -def test_single_stock_falls_back_to_rolling(): - dates = pd.date_range("2024-01-01", periods=40) - rng = np.random.default_rng(1) - signals = pd.DataFrame({"a": rng.standard_normal(40)}, index=dates) - returns = pd.DataFrame({"a": rng.standard_normal(40)}, index=dates) - res = evaluate_cross_sectional(signals, returns) - # Rolling fallback still yields the standard metric keys. - assert "rank_ic_mean" in res - assert res["n_periods"] > 0 diff --git a/tests/test_reversal.py b/tests/test_reversal.py deleted file mode 100644 index 6ed7cdb..0000000 --- a/tests/test_reversal.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest -from backtest.config import BacktestConfig -from backtest.runner import BacktestRunner -from strategies.reversal import FiveDayReversal - - -def test_reversal_smoke(): - """Smoke test: run a minimal reversal backtest and check results exist.""" - config = BacktestConfig( - symbols=["sh600000"], - start_date="2024-01-01", - end_date="2024-03-31", - initial_cash=100_000, - ) - runner = BacktestRunner(config) - results = runner.run(FiveDayReversal) - assert results is not None - assert len(results) == 1 - # Check analyzers exist - sharpe = results[0].analyzers.sharpe.get_analysis() - assert "sharperatio" in sharpe diff --git a/tests/test_runner.py b/tests/test_runner.py deleted file mode 100644 index e55bf29..0000000 --- a/tests/test_runner.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest -from backtest.config import BacktestConfig -from backtest.runner import BacktestRunner -from strategies.base import SmaCross - - -def test_backtest_smoke(): - """Smoke test: run a minimal backtest and check results exist.""" - config = BacktestConfig( - symbols=["sh600000"], - start_date="2024-01-01", - end_date="2024-03-31", - initial_cash=100_000, - ) - runner = BacktestRunner(config) - results = runner.run(SmaCross) - assert results is not None - assert len(results) == 1 - # Check analyzers exist - sharpe = results[0].analyzers.sharpe.get_analysis() - assert "sharperatio" in sharpe diff --git a/tests/test_signals.py b/tests/test_signals.py deleted file mode 100644 index fe61aff..0000000 --- a/tests/test_signals.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Tests for alpha signal computation.""" -import pandas as pd - -from signals.reversal import ReversalSignal - - -def _make_df(closes): - return pd.DataFrame({"close": closes}) - - -def test_reversal_name(): - assert ReversalSignal(lookback=5).name == "reversal_5d" - assert ReversalSignal(lookback=10).name == "reversal_10d" - - -def test_reversal_is_negative_trailing_return(): - # Monotonically rising prices -> negative (bearish) reversal signal. - df = _make_df([10.0, 11.0, 12.0, 13.0, 14.0, 15.0]) - sig = ReversalSignal(lookback=5).compute(df) - # First 5 values are NaN (insufficient lookback). - assert sig.iloc[:5].isna().all() - # 15/10 - 1 = 0.5 return -> signal = -0.5 - assert abs(sig.iloc[5] - (-0.5)) < 1e-9 - - -def test_reversal_oversold_is_positive(): - # Falling prices -> positive (bullish) reversal signal. - df = _make_df([20.0, 18.0, 16.0, 14.0, 12.0, 10.0]) - sig = ReversalSignal(lookback=5).compute(df) - assert sig.iloc[5] > 0 - # 10/20 - 1 = -0.5 -> signal = +0.5 - assert abs(sig.iloc[5] - 0.5) < 1e-9 - - -def test_reversal_output_length_matches_input(): - df = _make_df([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0]) - sig = ReversalSignal(lookback=3).compute(df) - assert len(sig) == len(df)