ci: keep internal documents out of tracked files
This commit is contained in:
1 parent
b116a5e094
commit
4da7579065
7 files changed
+199
-273
No files matched your search
@@ -116,8 +116,8 @@ jobs:
|
|||||||
- name: Repository safety gates
|
- name: Repository safety gates
|
||||||
run: bash tools/ci_grep_gates.sh
|
run: bash tools/ci_grep_gates.sh
|
||||||
|
|
||||||
- name: Test environment variable gate
|
- name: Test repository safety gates
|
||||||
run: pytest tools/test_ci_env_var_gate.py -q
|
run: pytest tools/test_ci_env_var_gate.py tools/test_ci_private_files_gate.py -q
|
||||||
|
|
||||||
- name: Syntax check
|
- name: Syntax check
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -55,6 +55,27 @@ For AI-assisted or automation-assisted contributions, also see
|
|||||||
local checks, higher-risk broker/MCP/credential surfaces, and the expected PR
|
local checks, higher-risk broker/MCP/credential surfaces, and the expected PR
|
||||||
risk notes for agent-authored changes.
|
risk notes for agent-authored changes.
|
||||||
|
|
||||||
|
## Keep internal documents local
|
||||||
|
|
||||||
|
The repository-root `docs/` directory holds local working notes and must not
|
||||||
|
be committed. Local agent instructions and memory files (`AGENTS.md`,
|
||||||
|
`AGENTS.override.md`, `CLAUDE.md`, `CLAUDE.local.md`, `MEMORY.md`,
|
||||||
|
`CODEX_MEMORY.md`, and `NOTES.md`) must also remain untracked at any depth.
|
||||||
|
Publish project documentation in `wiki/docs/` or the relevant source directory.
|
||||||
|
|
||||||
|
Before committing, run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python tools/ci_private_files_gate.py
|
||||||
|
```
|
||||||
|
|
||||||
|
This check also runs through `bash tools/ci_grep_gates.sh` in CI on pushes and
|
||||||
|
pull requests. It reads the Git index, so force-adding an ignored document is
|
||||||
|
still rejected; ignored local files and public documentation are allowed.
|
||||||
|
It prints filenames only, never document contents. If a file was accidentally
|
||||||
|
staged, use `git rm --cached -- <path>` to stop tracking it while preserving the
|
||||||
|
local copy. This does not erase copies in previous commits.
|
||||||
|
|
||||||
## Developer Certificate of Origin (DCO)
|
## Developer Certificate of Origin (DCO)
|
||||||
|
|
||||||
Every commit in a community pull request MUST carry a `Signed-off-by:`
|
Every commit in a community pull request MUST carry a `Signed-off-by:`
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
# Specialized analysis reliability and interaction
|
|
||||||
|
|
||||||
User scope: implement all proposed options/factor/correlation improvements and investigate repeated missing-data feedback. Baseline: local commit `3bb01ebe`; no remote publishing requested.
|
|
||||||
|
|
||||||
## Confirmed before changes
|
|
||||||
|
|
||||||
- Options: invalid edits leave old metrics visible; request generation changes only at dispatch, leaving a debounce race. Charts dispose/init on every data change. At 1280 px the leg editor horizontally clips columns.
|
|
||||||
- Correlation: matrix and regime fetch separately; Promise.all discards the successful result if either fails. Input edits clear results; no restored page state or observed sample coverage.
|
|
||||||
- Factors: full-zoo default, unworkable single-asset BTC universe, progress begins after data loading, job id is lost on navigation even while the backend continues. Job stores are process-local.
|
|
||||||
- Shared market data: empty DataFrames were counted as successful partial results and removed from the fallback queue. US class-share symbols did not match source routing; concatenated crypto could route to China loaders.
|
|
||||||
|
|
||||||
## Implemented
|
|
||||||
|
|
||||||
- Shared market data treats empty/None frames, including empty resampled frames,
|
|
||||||
as missing and continues fallback only for unresolved symbols. US class-share
|
|
||||||
routing and USDT alias normalization preserve requested identities; substituted
|
|
||||||
providers are attempted once and diagnostics name the actual provider.
|
|
||||||
Sanitized attempts and recovery guidance distinguish unavailable sources,
|
|
||||||
failed requests and no bars. Explicit-only currency/fallback boundaries remain.
|
|
||||||
- S&P 500 factor evaluation uses that shared fallback with observed DataFrames.
|
|
||||||
Selected fundamental factors load only their declared SEC fields at filing
|
|
||||||
dates, preserve missing values and reject unsupported universes before price
|
|
||||||
downloads. Both agent/MCP and shared CLI/API paths use this preparation.
|
|
||||||
- Factor UI evaluates one selected factor or compares a few, checks configured
|
|
||||||
readiness, reports loading/computing phases and observed coverage, and warns
|
|
||||||
when no factor was evaluable. BTC's single-asset pool is unavailable for these
|
|
||||||
cross-sectional evaluations. Request-ID replay avoids duplicate accepted jobs;
|
|
||||||
snapshots/SSE and tab-local state recover progress after navigation or reload.
|
|
||||||
- Correlation matrix and regime use one price snapshot, respect source priority,
|
|
||||||
normalize duplicate identities, show actual overlapping observations/dates,
|
|
||||||
and keep successful output when the other analysis lacks sufficient data.
|
|
||||||
- Options and correlation cancel superseded requests and keep previous results
|
|
||||||
explicitly labeled. Options charts use the captured result parameters, chart
|
|
||||||
instances are reused, advanced inputs are collapsed, and the contract chain
|
|
||||||
loads on expansion with 15 contracts per side around the actual quote.
|
|
||||||
- Drafts/results persist in the browser tab. All new UI strings cover nine locales.
|
|
||||||
|
|
||||||
## Verified
|
|
||||||
|
|
||||||
- Frontend: 76 test files / 725 tests pass, production build passes, including
|
|
||||||
locale parity. Log: `/tmp/vibe-trading-uiux-20261008/analysis-frontend-full.log`.
|
|
||||||
- Related backend suite: 335 tests pass. After the final empty-resampling fix,
|
|
||||||
the 91-test market-data/reliability subset passes (overlapping, not additive).
|
|
||||||
Logs: `analysis-backend-final.log` and `final-fallback-tests.log` in the same
|
|
||||||
temporary directory. Changed Python files pass Ruff; `git diff --check` passes.
|
|
||||||
- Actual MarketDataTool requests returned bars for AAPL.US, BRK.B.US,
|
|
||||||
600519.SH, 0700.HK and BTCUSDT through Yahoo, Tencent and OKX. The combined
|
|
||||||
correlation path returned AAPL/SPY/BRK.B with 30 common observations for
|
|
||||||
2026-08-26 through 2026-10-07 and a regime result. These are live provider
|
|
||||||
probes, separate from mocked regressions. A final request to the restarted
|
|
||||||
latest API returned HTTP 200, 30 common observations from 2026-08-27 through
|
|
||||||
2026-10-08, a regime result and no errors.
|
|
||||||
- Actual single-factor alpha101_001 evaluation for sp500 / 2025-2025 completed:
|
|
||||||
503 requested instruments, 500 observed, one evaluated factor. Missing:
|
|
||||||
FDXF.US, HONA.US, VYLR.US. The UI listed these and current-roster survivorship
|
|
||||||
bias. Navigation/reload recovered the result. After restarting the isolated
|
|
||||||
backend with the latest source, the UI still read the saved result; a new
|
|
||||||
job `4a84ae13fdf948ffaebb8650e309bec3` reused the panel and completed with the
|
|
||||||
same coverage and factor result. Initial progress was 0/1, terminal progress 1/1.
|
|
||||||
- Actual PIT SEC panel for AAPL.US/MSFT.US returned net_income and
|
|
||||||
shares_diluted. This verifies real field retrieval, not a full 503-company
|
|
||||||
fundamental benchmark. Health requests stayed responsive during the large
|
|
||||||
price fetch (0.5–13.1 ms in five observations).
|
|
||||||
- Local browser QA: late payoff response/edit races, result restoration,
|
|
||||||
correlation partial results, chain 15/15 versus full 60/56 rows, desktop leg
|
|
||||||
editor at 1280 px, and no whole-page overflow at 390 px. Stored Chinese/dark
|
|
||||||
preferences were preserved. Screenshot: `factor-final.png` in the temporary
|
|
||||||
directory. Preview: http://127.0.0.1:5899/.
|
|
||||||
|
|
||||||
## Remaining operational limits
|
|
||||||
|
|
||||||
- First large-universe download still takes minutes because real public-source
|
|
||||||
calls are rate-limited. Scope selection and reuse avoid unnecessary work;
|
|
||||||
source throttling and scientific sample gates were not reduced.
|
|
||||||
- Historical absence, credentials, optional dependencies and provider outages
|
|
||||||
can still prevent data retrieval. Diagnostics expose these instead of
|
|
||||||
fabricating values. Configured readiness does not promise live availability.
|
|
||||||
- Job stores remain process-local; restart loses unfinished jobs. Completed
|
|
||||||
snapshots are pruned after one hour, while completed tab-local results remain
|
|
||||||
readable. New submissions after lost process state can start fresh jobs.
|
|
||||||
- Current S&P 500 constituents carry survivorship bias. The pre-existing static
|
|
||||||
constituent-source date was not independently refreshed in this task.
|
|
||||||
- No protected agent/session/provider code, live broker action or remote
|
|
||||||
publishing was performed. Runtime caches and live probe outputs stayed outside
|
|
||||||
the checkout under `/tmp/vibe-trading-uiux-20261008/`.
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
# Options strategies worth automating: candidate backtests
|
|
||||||
|
|
||||||
Goal: backtest a small set of options strategies with the existing engine
|
|
||||||
(`agent/backtest/engines/options_portfolio.py`, unchanged) and decide which, if
|
|
||||||
any, to carry forward to paper trading.
|
|
||||||
|
|
||||||
Code: `agent/src/skills/options-strategy/example_signal_engine.py` (engines),
|
|
||||||
`agent/scripts/options_candidates_backtest.py` (this study; reruns end to end
|
|
||||||
with `PYTHONPATH=agent python agent/scripts/options_candidates_backtest.py`),
|
|
||||||
`agent/tests/test_options_strategy_engines.py`.
|
|
||||||
|
|
||||||
## Bottom line
|
|
||||||
|
|
||||||
- **No premium-selling strategy qualifies.** Cash-secured puts, iron condors
|
|
||||||
and short strangles made between -4% and +4% a year out of sample, against
|
|
||||||
15–19% for buy-and-hold. The model has no volatility risk premium: options
|
|
||||||
are priced at trailing realized vol, and that premium is where these
|
|
||||||
strategies' edge comes from in practice. **This backtest cannot validate them
|
|
||||||
either way.** They need real option-chain history first.
|
|
||||||
- **Carry forward one: a 0.20-delta, ~35-DTE covered call on SPY (QQQ as a
|
|
||||||
second underlying).** It was the only strategy that matched or beat
|
|
||||||
buy-and-hold on risk-adjusted terms in both periods for the index ETFs:
|
|
||||||
- SPY out of sample: Sharpe 1.10 vs 0.94, max drawdown -19% vs -25%, CAGR
|
|
||||||
about the same.
|
|
||||||
- QQQ: Sharpe about equal, drawdown -29% vs -35%, about 2 points a year less
|
|
||||||
CAGR.
|
|
||||||
|
|
||||||
Its result doesn't depend on the missing premium: real calls are richer than
|
|
||||||
modeled here, which would help it. It lagged buy-and-hold on AAPL and MSFT
|
|
||||||
(CAGR 4–5 points a year lower), so it is for index ETFs only.
|
|
||||||
- **Don't carry the protective put forward, even though it looks good.** Puts
|
|
||||||
are priced at trailing realized vol plus a mild skew, which makes them
|
|
||||||
several vol points cheaper than real index puts. Its strong in-sample
|
|
||||||
result (Sharpe 1.2 against 0.86 for SPY) mostly comes from the cheap
|
|
||||||
pre-COVID puts in February 2020, priced at about 12% vol. In reality those
|
|
||||||
puts cost far more.
|
|
||||||
- The vol gate helped the strangle in every run: an "elevated" regime (30-day
|
|
||||||
realized vol above its 1-year median) beat no gate in sample. Out of sample
|
|
||||||
it kept drawdowns to 8–19% but still earned ~0–1% a year. The gate's benefit
|
|
||||||
here comes from realized vol reverting to its mean. Real implied vol already
|
|
||||||
prices that in, so don't expect the benefit to carry over.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
| Item | Value |
|
|
||||||
|---|---|
|
|
||||||
| Underlyings | SPY, QQQ (index ETFs); AAPL, MSFT (single names). yfinance closes, dividend- and split-adjusted |
|
|
||||||
| In-sample | 2016-01-04 → 2020-12-31 (includes 2020 crash), history from 2015 used for vol warm-up |
|
|
||||||
| Out-of-sample | 2021-01-04 → 2026-10-08 (includes 2022 bear market), parameters frozen |
|
|
||||||
| Tuning | one parameter per strategy, best mean Sharpe over SPY+QQQ in sample (grid in the script) |
|
|
||||||
| Pricing | Black-Scholes on 30-day historical vol, `iv_skew=-0.3` smile, r = 2%, European, next-bar-close fills |
|
|
||||||
| Costs | commission = 1% of premium per side for ETFs, 2% for single names (≈ half-spread + fees) |
|
|
||||||
| Margin | engine's CBOE-style naked margin for cash-secured puts and strangles; off for covered call, protective put and condor (see caveats) |
|
|
||||||
| Capital | $100k, size compounds on each strategy's own realized P&L |
|
|
||||||
|
|
||||||
Strategies (chosen parameters in bold):
|
|
||||||
|
|
||||||
| Strategy | Rules |
|
|
||||||
|---|---|
|
|
||||||
| Covered call | shares + short call **0.20Δ** (vs 0.30), ~35 DTE, hold to expiry, shares re-sized to equity each cycle |
|
|
||||||
| Cash-secured put | short put **0.30Δ** (vs 0.20), ~35 DTE, hold to expiry, notional = capital (fully secured) |
|
|
||||||
| Iron condor | short **0.16Δ** (vs 0.10/0.25) put+call, 0.05Δ wings, 45 DTE, close at 50% of credit or 21 DTE; max loss 10% of capital per trade |
|
|
||||||
| Vol-gated strangle | short 0.16Δ put+call, 45 DTE, 50% / 21 DTE; gate **elevated** (vs none / contracting); notional = capital |
|
|
||||||
| Protective put | shares + long put **0.15Δ** (vs 0.30), ~60 DTE, rolled at 21 DTE |
|
|
||||||
|
|
||||||
## Out-of-sample results (2021-01 → 2026-10)
|
|
||||||
|
|
||||||
CAGR+bill is CAGR after crediting the 13-week T-bill rate on cash (the engine
|
|
||||||
pays 0% on cash). It matters for cash-heavy short-premium books.
|
|
||||||
|
|
||||||
| Strategy | Code | CAGR | CAGR+bill | Sharpe | MaxDD | Worst month | Worst day | CVaR 5% (daily) | Skew | Win rate | Trades |
|
|
||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
||||||
| buy & hold | SPY | 15.3% | | 0.94 | -24.5% | -9.2% | -5.9% | -2.4% | 0.28 | | |
|
|
||||||
| covered call | SPY | 15.5% | 15.5% | **1.10** | -19.4% | -8.3% | -5.6% | -2.1% | -0.00 | 81% | 59 |
|
|
||||||
| cash-secured put | SPY | 2.9% | 5.8% | 0.42 | -13.9% | -7.3% | -4.8% | -1.3% | -0.16 | 85% | 59 |
|
|
||||||
| iron condor | SPY | -1.4% | 1.6% | -0.18 | -14.8% | -4.2% | -2.8% | -1.2% | -0.51 | 64% | 90 |
|
|
||||||
| vol-gated strangle | SPY | 0.0% | 3.0% | 0.02 | -8.4% | -3.2% | -3.1% | -0.7% | -1.61 | 72% | 53 |
|
|
||||||
| protective put | SPY | 15.1% | 15.1% | 1.07 | -20.5% | -8.2% | -3.8% | -1.9% | 0.75 | 14% | 49 |
|
|
||||||
| buy & hold | QQQ | 17.2% | | 0.82 | -35.1% | -13.6% | -6.2% | -3.2% | 0.15 | | |
|
|
||||||
| covered call | QQQ | 15.4% | 15.4% | 0.86 | -29.2% | -12.5% | -5.9% | -2.8% | -0.06 | 73% | 59 |
|
|
||||||
| cash-secured put | QQQ | 3.7% | 6.4% | 0.42 | -21.4% | -7.2% | -4.9% | -1.7% | -0.58 | 88% | 59 |
|
|
||||||
| iron condor | QQQ | -3.1% | 0.1% | -0.46 | -22.9% | -5.1% | -2.7% | -1.1% | -1.42 | 62% | 90 |
|
|
||||||
| vol-gated strangle | QQQ | 1.3% | 4.3% | 0.29 | -10.5% | -6.1% | -2.8% | -0.9% | -2.37 | 77% | 52 |
|
|
||||||
| protective put | QQQ | 16.7% | 16.7% | 0.92 | -31.0% | -12.6% | -4.1% | -2.5% | 0.48 | 18% | 49 |
|
|
||||||
| buy & hold | AAPL | 18.9% | | 0.77 | -33.4% | -12.2% | -9.2% | -3.9% | 0.34 | | |
|
|
||||||
| covered call | AAPL | 13.7% | 13.8% | 0.67 | -32.0% | -11.4% | -8.7% | -3.5% | 0.27 | 71% | 59 |
|
|
||||||
| cash-secured put | AAPL | 3.4% | 6.3% | 0.32 | -23.9% | -6.0% | -6.1% | -2.3% | 0.62 | 78% | 59 |
|
|
||||||
| iron condor | AAPL | -4.3% | -0.8% | -0.59 | -28.7% | -8.7% | -4.3% | -1.3% | -1.45 | 66% | 90 |
|
|
||||||
| vol-gated strangle | AAPL | -0.4% | 2.8% | -0.01 | -15.1% | -9.1% | -4.2% | -1.4% | -1.25 | 75% | 52 |
|
|
||||||
| protective put | AAPL | 17.2% | 17.2% | 0.79 | -29.0% | -12.9% | -6.2% | -3.1% | 0.67 | 16% | 49 |
|
|
||||||
| buy & hold | MSFT | 17.4% | | 0.73 | -37.1% | -17.2% | -10.0% | -3.7% | 0.54 | | |
|
|
||||||
| covered call | MSFT | 12.7% | 12.9% | 0.67 | -28.7% | -13.5% | -9.2% | -3.2% | -0.20 | 78% | 59 |
|
|
||||||
| cash-secured put | MSFT | 4.1% | 7.1% | 0.42 | -18.8% | -7.9% | -6.1% | -2.0% | -0.88 | 86% | 59 |
|
|
||||||
| iron condor | MSFT | -1.5% | 1.7% | -0.19 | -13.6% | -4.1% | -3.9% | -1.2% | -1.71 | 67% | 91 |
|
|
||||||
| vol-gated strangle | MSFT | 0.7% | 4.0% | 0.13 | -18.5% | -7.0% | -6.6% | -1.2% | -3.53 | 72% | 47 |
|
|
||||||
| protective put | MSFT | 13.7% | 13.7% | 0.65 | -33.1% | -14.8% | -10.0% | -3.1% | 1.03 | 22% | 49 |
|
|
||||||
|
|
||||||
Win rate is per structure (all legs of one entry), excluding the share leg.
|
|
||||||
No opens were rejected for margin in any run.
|
|
||||||
|
|
||||||
## In-sample (2016 → 2020), chosen parameters
|
|
||||||
|
|
||||||
| Strategy | SPY CAGR / Sharpe / MaxDD | QQQ CAGR / Sharpe / MaxDD |
|
|
||||||
|---|---|---|
|
|
||||||
| buy & hold | 15.5% / 0.86 / -33.7% | 24.5% / 1.11 / -28.6% |
|
|
||||||
| covered call 0.20Δ | 13.7% / 0.85 / -32.7% | 20.2% / 1.07 / -27.6% |
|
|
||||||
| cash-secured put 0.30Δ | -1.2% / -0.02 / -33.1% | 1.1% / 0.15 / -26.8% |
|
|
||||||
| iron condor 0.16Δ | -2.8% / -0.32 / -26.9% | -3.1% / -0.38 / -26.5% |
|
|
||||||
| strangle, elevated gate | 1.0% / 0.18 / -18.2% | 3.1% / 0.48 / -12.3% |
|
|
||||||
| protective put 0.15Δ | 15.8% / 1.20 / -14.4% | 23.0% / 1.31 / -18.9% |
|
|
||||||
|
|
||||||
The full in-sample grid (every parameter, worst month/day, CVaR, skew) is
|
|
||||||
printed by the script.
|
|
||||||
|
|
||||||
## Tail behavior
|
|
||||||
|
|
||||||
- Short premium has the expected left tail. Every short-premium book has
|
|
||||||
negative daily skew, worst at -5.3 for the ungated SPY strangle in sample.
|
|
||||||
In 2020 the SPY cash-secured put lost 16–17% in its worst month, and the
|
|
||||||
ungated SPY strangle lost 14%. Win rates of 80–90% sit alongside negative CAGR,
|
|
||||||
so a high win rate is no evidence of an edge here.
|
|
||||||
- The covered call keeps almost all of the underlying's crash exposure: its
|
|
||||||
in-sample max drawdown is -33% against -34% for SPY. It mainly gives up
|
|
||||||
upside, which is why it lags on strongly trending single names.
|
|
||||||
- The protective put is the only strategy with positive skew, and it cut
|
|
||||||
the worst day on SPY, QQQ and AAPL. That benefit is overstated for the reason given in the bottom
|
|
||||||
line.
|
|
||||||
|
|
||||||
## Sensitivity to the smile (SPY, out of sample)
|
|
||||||
|
|
||||||
`iv_skew` is the only setting the engine has for how rich OTM options are.
|
|
||||||
Changing it barely moves the ranking:
|
|
||||||
|
|
||||||
| iv_skew | covered call Sharpe | CSP CAGR+bill | condor CAGR+bill | strangle CAGR+bill | protective put Sharpe |
|
|
||||||
|---|---|---|---|---|---|
|
|
||||||
| 0.0 | 1.15 | 5.1% | 0.9% | 3.9% | 1.18 |
|
|
||||||
| -0.3 (base) | 1.10 | 5.8% | 1.6% | 3.0% | 1.07 |
|
|
||||||
| -0.6 | 1.04 | 6.4% | 1.5% | 2.7% | 0.95 |
|
|
||||||
|
|
||||||
## Caveats
|
|
||||||
|
|
||||||
1. **Prices come from a model, not from real option chains.** Every price is
|
|
||||||
Black-Scholes on trailing 30-day realized vol plus a fixed quadratic smile:
|
|
||||||
- The volatility risk premium is effectively **zero**. Real index implied
|
|
||||||
vol has averaged a few points above later realized vol. That gap is the
|
|
||||||
return premium sellers collect and buyers pay. Here, premium selling
|
|
||||||
looks worse than it is and option buying looks better.
|
|
||||||
- Real implied vol jumps the day a crash starts. Here it lags by up to 30
|
|
||||||
days: options are cheap going into a crash and expensive after it.
|
|
||||||
- The bid/ask spread is modeled as a flat 1–2% of premium. Real spreads
|
|
||||||
widen sharply in a crisis, the exact days that matter for short-premium
|
|
||||||
strategies.
|
|
||||||
2. **There is no early assignment and no share delivery.** Options are
|
|
||||||
European and settle in cash. The covered call and protective put hold a
|
|
||||||
"share replica" (a call struck at $0.01) and re-size it to equity each
|
|
||||||
cycle, which approximates assignment and re-buying. The cash-secured put
|
|
||||||
settles in cash, so it is not a full wheel (no calls written on assigned
|
|
||||||
shares). Dividends reach the results only through adjusted closes.
|
|
||||||
3. **Some margin was switched off.** The engine margins every short leg as
|
|
||||||
naked. That is right for cash-secured puts and strangles. It overcharges
|
|
||||||
covered calls and defined-risk condors, and it rejected single condor legs
|
|
||||||
in a trial run, which left the rest of the condor naked. So those three
|
|
||||||
were run with `margin_enabled: false`:
|
|
||||||
- the condor is sized so its maximum loss is 10% of capital, which matches
|
|
||||||
the Reg-T spread requirement;
|
|
||||||
- the share-based strategies hold about zero cash.
|
|
||||||
4. **Cash earns 0% in the engine.** The CAGR+bill column adds T-bill interest
|
|
||||||
after the fact, as simple interest on the engine's cash balance.
|
|
||||||
5. **This is a small study.** It covers four underlyings, about 11 years, and
|
|
||||||
~50–90 trades per strategy per period. The parameter grid is deliberately
|
|
||||||
small (2–3 values), so the in-sample choice gives only a little room for
|
|
||||||
overfitting. A Sharpe ratio measured over ~5 years has a standard error of about
|
|
||||||
0.45, so none of the Sharpe gaps above are statistically significant.
|
|
||||||
|
|
||||||
## Next steps
|
|
||||||
|
|
||||||
- Paper-trade the SPY 0.20Δ / 35-DTE covered call (QQQ optional). Record real
|
|
||||||
fills against the model's mid price to measure the actual spread cost.
|
|
||||||
- Before paper-trading any short-premium strategy, replace the synthetic
|
|
||||||
pricing with historical chain IV, for example by loading an IV series in
|
|
||||||
place of `historical_volatility`. That is the input that decides whether
|
|
||||||
the cash-secured put or the vol-gated strangle has an edge. This study
|
|
||||||
can't answer that.
|
|
||||||
+12
-3
@@ -1,7 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# ci_grep_gates.sh — repo-wide safety floor enforced in CI.
|
# ci_grep_gates.sh — repo-wide safety floor enforced in CI.
|
||||||
#
|
#
|
||||||
# Four gates run sequentially; any failure exits non-zero and names the
|
# Gates run sequentially; any failure exits non-zero and names the
|
||||||
# offending files. Run locally before pushing:
|
# offending files. Run locally before pushing:
|
||||||
#
|
#
|
||||||
# bash tools/ci_grep_gates.sh
|
# bash tools/ci_grep_gates.sh
|
||||||
@@ -20,6 +20,9 @@
|
|||||||
# (e) No raw `os.getenv` / `os.environ.get` / `os.environ["KEY"]` reads
|
# (e) No raw `os.getenv` / `os.environ.get` / `os.environ["KEY"]` reads
|
||||||
# outside the centralized config layer (`agent/src/config/`).
|
# outside the centralized config layer (`agent/src/config/`).
|
||||||
# AST-based; uses `tools/ci_env_var_gate.py`.
|
# AST-based; uses `tools/ci_env_var_gate.py`.
|
||||||
|
# (f) No process-wide os patches in tests.
|
||||||
|
# (g) No internal documents in the Git index, even if force-added.
|
||||||
|
# Uses `tools/ci_private_files_gate.py`; ignored local files are allowed.
|
||||||
#
|
#
|
||||||
# Exclusions: .git, node_modules, __pycache__, .venv, dist, build, this
|
# Exclusions: .git, node_modules, __pycache__, .venv, dist, build, this
|
||||||
# script itself. The HTML scan in (c) is scoped to wiki/alpha-library/**
|
# script itself. The HTML scan in (c) is scoped to wiki/alpha-library/**
|
||||||
@@ -58,8 +61,8 @@ fi
|
|||||||
# -------------------------------------------------------------- gate (b)
|
# -------------------------------------------------------------- gate (b)
|
||||||
# docs/ is excluded: those are internal planning docs that discuss the
|
# docs/ is excluded: those are internal planning docs that discuss the
|
||||||
# trademark policy itself (e.g. "the string 'WorldQuant' must not appear in
|
# trademark policy itself (e.g. "the string 'WorldQuant' must not appear in
|
||||||
# user-facing artifacts"). docs/ is not shipped to PyPI / public consumers
|
# user-facing artifacts"). Gate (g) prevents these local documents from
|
||||||
# (memory: feedback_no_push_docs). The gate's real target is source code,
|
# being tracked. This gate's target is source code,
|
||||||
# READMEs, HTML/JSON manifests, and the wiki.
|
# READMEs, HTML/JSON manifests, and the wiki.
|
||||||
echo "[gate b] no 'WorldQuant' trademark string in shipped artifacts ..."
|
echo "[gate b] no 'WorldQuant' trademark string in shipped artifacts ..."
|
||||||
B_HITS=$(grep -rni --include='*.py' --include='*.md' --include='*.html' --include='*.json' \
|
B_HITS=$(grep -rni --include='*.py' --include='*.md' --include='*.html' --include='*.json' \
|
||||||
@@ -162,6 +165,12 @@ else
|
|||||||
echo "${GREEN}ok${NC}"
|
echo "${GREEN}ok${NC}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# -------------------------------------------------------------- gate (g)
|
||||||
|
echo "[gate g] no tracked internal documents ..."
|
||||||
|
if ! python tools/ci_private_files_gate.py; then
|
||||||
|
FAILED=1
|
||||||
|
fi
|
||||||
|
|
||||||
# --------------------------------------------------------------- result
|
# --------------------------------------------------------------- result
|
||||||
if [ "$FAILED" -ne 0 ]; then
|
if [ "$FAILED" -ne 0 ]; then
|
||||||
echo
|
echo
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Reject internal documents in the Git index, including force-added files.
|
||||||
|
|
||||||
|
Run before committing with ``python tools/ci_private_files_gate.py``.
|
||||||
|
Only indexed paths are inspected; ignored local files and their contents are
|
||||||
|
never read. The same index check runs on CI's checked-out commit.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path, PurePosixPath
|
||||||
|
|
||||||
|
|
||||||
|
PRIVATE_DOCUMENT_NAMES = frozenset({
|
||||||
|
"agents.md",
|
||||||
|
"agents.override.md",
|
||||||
|
"claude.md",
|
||||||
|
"claude.local.md",
|
||||||
|
"memory.md",
|
||||||
|
"codex_memory.md",
|
||||||
|
"notes.md",
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def main(repo_root: Path | None = None) -> int:
|
||||||
|
"""Check the Git index for internal document paths.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repo_root: Repository to check; defaults to this script's repository.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Zero for a clean index, one for forbidden paths, or two if Git fails.
|
||||||
|
"""
|
||||||
|
root = repo_root if repo_root is not None else Path(__file__).resolve().parent.parent
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["git", "ls-files", "--cached", "--full-name", "-z"],
|
||||||
|
cwd=root,
|
||||||
|
capture_output=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
print("FAIL: cannot read the Git index; internal-document check did not run.")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
forbidden = []
|
||||||
|
for raw_path in result.stdout.split(b"\0"):
|
||||||
|
if not raw_path:
|
||||||
|
continue
|
||||||
|
name = os.fsdecode(raw_path)
|
||||||
|
path = PurePosixPath(name)
|
||||||
|
if path.parts[0].casefold() == "docs" or path.name.casefold() in PRIVATE_DOCUMENT_NAMES:
|
||||||
|
forbidden.append(name)
|
||||||
|
|
||||||
|
if forbidden:
|
||||||
|
print("FAIL: internal documents are tracked in Git:")
|
||||||
|
for name in sorted(set(forbidden)):
|
||||||
|
# Escape control characters so a filename cannot inject CI output.
|
||||||
|
print(f" {ascii(name)}")
|
||||||
|
print("Use git rm --cached -- <path> to retain the local file without tracking it.")
|
||||||
|
print("Publish project documentation under wiki/docs/ or the relevant source directory.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("OK: no internal documents are tracked in Git.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
"""Exercise the privacy gate against real temporary Git indexes."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ci_private_files_gate import main
|
||||||
|
|
||||||
|
|
||||||
|
def _git(root: Path, *args: str) -> None:
|
||||||
|
"""Run Git in an isolated test repository."""
|
||||||
|
subprocess.run(["git", "-C", str(root), *args], check=True, capture_output=True)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def repo(tmp_path: Path) -> Path:
|
||||||
|
"""Create a real repository with an ignored internal-documents directory."""
|
||||||
|
_git(tmp_path, "init", "-q")
|
||||||
|
(tmp_path / ".gitignore").write_text("/docs/\nAGENTS.md\nCLAUDE.md\n", encoding="utf-8")
|
||||||
|
_git(tmp_path, "add", "--", ".gitignore")
|
||||||
|
return tmp_path
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("name", [
|
||||||
|
"docs/sessions/private.md",
|
||||||
|
"docs/plans/design.json",
|
||||||
|
"docs",
|
||||||
|
"DOCS/private.md",
|
||||||
|
"AGENTS.md",
|
||||||
|
"agent/AGENTS.override.md",
|
||||||
|
"CLAUDE.md",
|
||||||
|
"frontend/CLAUDE.local.md",
|
||||||
|
"MEMORY.md",
|
||||||
|
"nested/CODEX_MEMORY.md",
|
||||||
|
"NOTES.md",
|
||||||
|
"docs/notes with spaces\nand newline.md",
|
||||||
|
])
|
||||||
|
def test_force_added_internal_documents_fail(repo: Path, name: str, capsys) -> None:
|
||||||
|
path = repo / name
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text("private content must never appear in output", encoding="utf-8")
|
||||||
|
_git(repo, "add", "-f", "--", name)
|
||||||
|
|
||||||
|
assert main(repo) == 1
|
||||||
|
output = capsys.readouterr().out
|
||||||
|
assert ascii(name) in output
|
||||||
|
assert "private content" not in output
|
||||||
|
|
||||||
|
|
||||||
|
def test_public_docs_and_ignored_local_notes_pass(repo: Path) -> None:
|
||||||
|
for name in ["wiki/docs/guide.md", "agent/src/skills/example/SKILL.md", "README.md"]:
|
||||||
|
path = repo / name
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
path.write_text("public documentation", encoding="utf-8")
|
||||||
|
_git(repo, "add", "--", name)
|
||||||
|
(repo / "docs").mkdir()
|
||||||
|
(repo / "docs/private.md").write_text("local notes", encoding="utf-8")
|
||||||
|
(repo / "CLAUDE.md").write_text("local instructions", encoding="utf-8")
|
||||||
|
|
||||||
|
assert main(repo) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_unstaged_deletion_does_not_hide_an_indexed_file(repo: Path) -> None:
|
||||||
|
path = repo / "docs/private.md"
|
||||||
|
path.parent.mkdir()
|
||||||
|
path.write_text("private", encoding="utf-8")
|
||||||
|
_git(repo, "add", "-f", "--", "docs/private.md")
|
||||||
|
path.unlink()
|
||||||
|
|
||||||
|
assert main(repo) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_untracking_keeps_local_content_and_clears_failure(repo: Path) -> None:
|
||||||
|
path = repo / "docs/private.md"
|
||||||
|
path.parent.mkdir()
|
||||||
|
original = b"local notes\r\n\x00unchanged\n"
|
||||||
|
path.write_bytes(original)
|
||||||
|
_git(repo, "add", "-f", "--", "docs/private.md")
|
||||||
|
assert main(repo) == 1
|
||||||
|
|
||||||
|
_git(repo, "rm", "--cached", "--", "docs/private.md")
|
||||||
|
|
||||||
|
assert path.read_bytes() == original
|
||||||
|
assert main(repo) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_git_repository_fails_closed(tmp_path: Path, capsys) -> None:
|
||||||
|
assert main(tmp_path) == 2
|
||||||
|
assert "check did not run" in capsys.readouterr().out
|
||||||
Reference in new issue
Block a user