alphafinch 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alphafinch-0.1.0/.gitignore +12 -0
- alphafinch-0.1.0/LICENSE +21 -0
- alphafinch-0.1.0/PKG-INFO +255 -0
- alphafinch-0.1.0/README.md +221 -0
- alphafinch-0.1.0/alphafinch/__init__.py +0 -0
- alphafinch-0.1.0/alphafinch/braille.py +66 -0
- alphafinch-0.1.0/alphafinch/cinema.py +238 -0
- alphafinch-0.1.0/alphafinch/cli.py +455 -0
- alphafinch-0.1.0/alphafinch/data.py +438 -0
- alphafinch-0.1.0/alphafinch/engine.py +78 -0
- alphafinch-0.1.0/alphafinch/evolve.py +462 -0
- alphafinch-0.1.0/alphafinch/fitness.py +103 -0
- alphafinch-0.1.0/alphafinch/forward.py +96 -0
- alphafinch-0.1.0/alphafinch/lab.py +252 -0
- alphafinch-0.1.0/alphafinch/llm.py +146 -0
- alphafinch-0.1.0/alphafinch/mandate.py +131 -0
- alphafinch-0.1.0/alphafinch/prompts.py +164 -0
- alphafinch-0.1.0/alphafinch/report.py +188 -0
- alphafinch-0.1.0/alphafinch/sandbox.py +71 -0
- alphafinch-0.1.0/alphafinch/seeds.py +175 -0
- alphafinch-0.1.0/alphafinch/toolkit.py +140 -0
- alphafinch-0.1.0/alphafinch/ui.py +120 -0
- alphafinch-0.1.0/alphafinch/universe.py +114 -0
- alphafinch-0.1.0/alphafinch/universes/australia.csv +201 -0
- alphafinch-0.1.0/alphafinch/universes/canada.csv +61 -0
- alphafinch-0.1.0/alphafinch/universes/europe.csv +179 -0
- alphafinch-0.1.0/alphafinch/universes/hongkong.csv +86 -0
- alphafinch-0.1.0/alphafinch/universes/india_fno.csv +215 -0
- alphafinch-0.1.0/alphafinch/universes/japan.csv +226 -0
- alphafinch-0.1.0/alphafinch/universes/korea.csv +201 -0
- alphafinch-0.1.0/alphafinch/universes/uk.csv +101 -0
- alphafinch-0.1.0/alphafinch/world.py +110 -0
- alphafinch-0.1.0/integrations/claude-code/alphafinch/SKILL.md +43 -0
- alphafinch-0.1.0/pyproject.toml +37 -0
- alphafinch-0.1.0/tests/test_core.py +271 -0
alphafinch-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shlok Sobti
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: alphafinch
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Evolve trading strategies with AI while you sleep, then make them pass an exam they cannot cheat.
|
|
5
|
+
Project-URL: Homepage, https://github.com/shloksobti/alphafinch
|
|
6
|
+
Project-URL: Documentation, https://github.com/shloksobti/alphafinch/blob/main/docs/guide.md
|
|
7
|
+
Project-URL: Issues, https://github.com/shloksobti/alphafinch/issues
|
|
8
|
+
Author: Shlok Sobti
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,alphaevolve,backtesting,evolution,llm,quant,trading
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Requires-Dist: numpy>=1.24
|
|
17
|
+
Requires-Dist: pandas>=2.0
|
|
18
|
+
Requires-Dist: requests>=2.28
|
|
19
|
+
Requires-Dist: rich>=13.0
|
|
20
|
+
Requires-Dist: scipy>=1.10
|
|
21
|
+
Provides-Extra: all
|
|
22
|
+
Requires-Dist: anthropic>=1.0; extra == 'all'
|
|
23
|
+
Requires-Dist: matplotlib>=3.7; extra == 'all'
|
|
24
|
+
Requires-Dist: openai>=1.0; extra == 'all'
|
|
25
|
+
Provides-Extra: anthropic
|
|
26
|
+
Requires-Dist: anthropic>=1.0; extra == 'anthropic'
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
29
|
+
Provides-Extra: openai
|
|
30
|
+
Requires-Dist: openai>=1.0; extra == 'openai'
|
|
31
|
+
Provides-Extra: report
|
|
32
|
+
Requires-Dist: matplotlib>=3.7; extra == 'report'
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
|
|
35
|
+
<div align="center">
|
|
36
|
+
|
|
37
|
+
# 🐦 AlphaFinch
|
|
38
|
+
|
|
39
|
+
### AI evolves trading strategies while you sleep.<br>Then they sit an exam they can't cheat.
|
|
40
|
+
|
|
41
|
+
<p>
|
|
42
|
+
<a href="https://github.com/shloksobti/alphafinch/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/shloksobti/alphafinch/tests.yml?branch=main&label=tests&logo=github" alt="tests"></a>
|
|
43
|
+
<a href="https://github.com/shloksobti/alphafinch"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-3776AB?logo=python&logoColor=white" alt="Python 3.10+"></a>
|
|
44
|
+
<a href="https://github.com/shloksobti/alphafinch/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license"></a>
|
|
45
|
+
<br>
|
|
46
|
+
<a href="https://github.com/shloksobti/alphafinch/blob/main/docs/preregistration-world.md"><img src="https://img.shields.io/badge/world%20exam-PASS%207%2F7%20markets-brightgreen" alt="World exam: PASS in 7 of 7 markets"></a>
|
|
47
|
+
<a href="https://github.com/shloksobti/alphafinch/tree/main/docs"><img src="https://img.shields.io/badge/results-pre--registered-blueviolet" alt="Pre-registered results"></a>
|
|
48
|
+
<a href="https://github.com/shloksobti/alphafinch/blob/main/docs/guide.md#markets"><img src="https://img.shields.io/badge/markets-15%20(stocks%20%C2%B7%20futures%20%C2%B7%20crypto)-orange" alt="15 markets"></a>
|
|
49
|
+
<a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7557458"><img src="https://img.shields.io/badge/paper-SSRN%207557458-b31b1b" alt="Paper on SSRN"></a>
|
|
50
|
+
<img src="https://img.shields.io/badge/data-free%2C%20no%20API%20keys-informational" alt="Free data">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
<img src="https://raw.githubusercontent.com/shloksobti/alphafinch/main/docs/demo.gif" alt="AlphaFinch: install, evolve strategies, and the sealed exam" width="900">
|
|
54
|
+
|
|
55
|
+
Works with **Claude Code**, **Anthropic**, **OpenAI**, **Ollama**, any OpenAI-compatible server, or **no AI at all**.
|
|
56
|
+
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
AlphaFinch is an [AlphaEvolve](https://deepmind.google/discover/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/)-style lab for markets. An AI writes trading strategies as short Python functions, backtests them, and breeds the fittest: mutating them, crossing them and letting populations evolve on separate islands.
|
|
62
|
+
|
|
63
|
+
The catch with every AI trading demo: **evolution is the best overfitting machine ever built.** Run it long enough and it will "discover" a brilliant strategy in pure noise. AlphaFinch is built around that problem. The most recent years are locked in a **sealed exam** that neither evolution nor the AI ever sees, and the bar to pass accounts for every attempt.
|
|
64
|
+
|
|
65
|
+
**Most runs end with nothing passing.** That's the honest answer, and the point.
|
|
66
|
+
|
|
67
|
+
## What it found
|
|
68
|
+
|
|
69
|
+
We pre-registered every test before running it ([`docs/`](docs)) and report every result.
|
|
70
|
+
|
|
71
|
+
- **One market, three sealed years: 0 of 10 passed** on the S&P 500 and NIFTY 200 ([details](docs/preregistration-v2.md); earlier runs on industries and India: 0 of 8, [details](docs/preregistration.md)). Training scores rose while out-of-sample alpha fell: overfitting, caught in the act.
|
|
72
|
+
- **So we rebuilt the search** to score alpha, choose champions on years breeding never saw, and demand an edge that holds across random halves of the stocks. On a stand-in exam it beat the original search in 4 of 4 matched runs ([details](docs/search-ablation.md)).
|
|
73
|
+
- **Then the world exam:** five strategies, bred only on data before 2017, were frozen and tested unchanged on **seven stock markets they had never seen**, from October 2017 to October 2026.
|
|
74
|
+
|
|
75
|
+
| Strategy | Alpha / year | t (bar 2.33) | Markets with positive alpha | |
|
|
76
|
+
|---|---|---|---|---|
|
|
77
|
+
| **Quiet Sector Tether v2** | **+2.9%** | **3.40** | **7 of 7** | ✅ PASS |
|
|
78
|
+
| The Team (India-bred) | +2.3% | 2.14 | 6 of 7 | 🟡 PROMISING |
|
|
79
|
+
| Quiet Intraday Relay v2 | +0.9% | 1.03 | 5 of 7 | 🟡 PROMISING |
|
|
80
|
+
| Two others | negative | | | ❌ FAIL |
|
|
81
|
+
|
|
82
|
+
*Quiet Sector Tether* buys, within each sector, the stocks that move least with the market and shorts those that move most. It is essentially **"betting against correlation"**, an anomaly published by AQR researchers in 2020: the AI rediscovered it from pre-2017 US data, and it held up in the UK, the Eurozone, Japan, Hong Kong, Australia, Canada and Korea. It survives higher trading costs (t 2.94 at 15 bps), a causal beta hedge (t 3.22) and the six standard Fama–French factors (alpha +2.5%/yr, t 2.64).
|
|
83
|
+
|
|
84
|
+
The caveats are in the [full write-up](docs/preregistration-world.md): it is weaker in 2020–26 alone (t 2.17), short-borrowing fees aren't modelled, and Korea banned short selling for parts of the period. It's frozen in [`forward/`](forward) to be judged again on data that doesn't exist yet.
|
|
85
|
+
|
|
86
|
+
## Quick start
|
|
87
|
+
|
|
88
|
+
Needs Python 3.10+. No API keys or data subscriptions: market data is free and downloaded on first use.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
pip install "alphafinch[all] @ git+https://github.com/shloksobti/alphafinch"
|
|
92
|
+
|
|
93
|
+
alphafinch demo # offline: synthetic market, no AI, no network (~1 min)
|
|
94
|
+
alphafinch evolve india # NIFTY 200 stocks; AI provider auto-detected
|
|
95
|
+
alphafinch evolve india --long-only # same, but no shorting (cash market)
|
|
96
|
+
alphafinch evolve india-futures # NSE futures: NIFTY, BANKNIFTY, every F&O stock
|
|
97
|
+
alphafinch evolve futures # 39 global futures: indices, bonds, FX, commodities
|
|
98
|
+
alphafinch holdings runs/<run>/champion.py india # what the champion wants to hold today
|
|
99
|
+
alphafinch world-exam runs/<run>/champion.py # test it on 7 markets it has never seen
|
|
100
|
+
alphafinch replay runs/<run> # re-watch a finished run as a short story
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Two choices shape every run: the **market** (a plain word, e.g. `india`) and the **rules** (flags, e.g. `--long-only`). Type `alphafinch` alone for an overview, `alphafinch evolve -h` for every option, or read the **[guide](docs/guide.md)**.
|
|
104
|
+
|
|
105
|
+
**What a run takes.** The default run (20 generations × 4 islands) makes a few hundred AI calls and takes roughly 20–40 minutes. The first run on a market also downloads its data (a few minutes, then cached). With `claude-code` it uses your existing subscription; with an API key you pay your provider's usual rates.
|
|
106
|
+
|
|
107
|
+
**What you get** in `runs/<timestamp>/`:
|
|
108
|
+
|
|
109
|
+
| File | Contents |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `report.html` | The morning report: champion, exam verdict, equity curves, the team, the lab notebook, the family tree |
|
|
112
|
+
| `champion.py` | The champion's code, ready for `backtest`, `holdings` or `world-exam` |
|
|
113
|
+
| `team.py` | A diversified team of survivors, when one forms |
|
|
114
|
+
| `population.json` | Every strategy bred, with its scores |
|
|
115
|
+
|
|
116
|
+
`alphafinch replay runs/<timestamp>` re-tells any finished run as a short story:
|
|
117
|
+
|
|
118
|
+
<img src="https://raw.githubusercontent.com/shloksobti/alphafinch/main/docs/replay.gif" alt="alphafinch replay: the AI writing a strategy, a new champion, and the sealed exam" width="820">
|
|
119
|
+
|
|
120
|
+
## How it works
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
breed ─▶ choose ─▶ 🔒 sealed exam ─▶ 🌍 world exam ─▶ ⏳ forward test
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**Breeding.** Four islands, each with a population of strategies. Every generation the AI **mutates** a parent using its report card, **crosses** two parents into one idea, or invents an **immigrant** from a fresh hypothesis. No-AI operators **tweak** a constant or **blend** two portfolios. Champions migrate between islands, and each island keeps the best strategy in every niche (fast or slow, market-neutral or market-hugging), so the population can't collapse onto one idea.
|
|
127
|
+
|
|
128
|
+
**Fitness rewards a real edge, not a lucky one:**
|
|
129
|
+
- **Alpha, not returns.** Each of four training eras is scored on its appraisal ratio: return beyond market exposure, per unit of risk. The *worst* era counts as much as the typical one.
|
|
130
|
+
- **Broad, not narrow.** The portfolio is re-scored on random halves of the stocks, and the worst half counts.
|
|
131
|
+
- **Stable, not knife-edge.** Parameters are nudged and neighbours re-scored. Penalties for heavy trading and bloated code.
|
|
132
|
+
|
|
133
|
+
**The AI works like a researcher.** Every strategy starts with a written hypothesis. A lab notebook of every idea tried, and how it fared, goes into each prompt. A toolkit (`tk`) makes sector-neutral long/short books, residual returns and volatility targeting one-liners. Use `--strong-model` to give crossovers and new ideas to a bigger model.
|
|
134
|
+
|
|
135
|
+
**Choosing the champion.** The last three training years are held back from breeding. The top ten finalists and a diversified team are scored once on those years, and the best becomes the champion. The AI never sees those scores.
|
|
136
|
+
|
|
137
|
+
**The sealed exam.** The most recent three years. Each attempt reveals only PASS or FAIL, and the bar is t⁻¹(α / attempts), valid however adaptively the search ran ([the theory](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7557458)). Results are graded **PASS**, **PROMISING** (t > 1, with the years of data a pass would need) or **FAIL**.
|
|
138
|
+
|
|
139
|
+
**The world exam.** Three years of one market can rarely prove a realistic edge. So a frozen strategy runs unchanged on seven other markets, and the evidence is pooled. The verdict uses the lower of two pooled t-statistics (Newey–West and Stouffer), so a strategy has to convince both. Calibrated with placebos: 0 of 200 random strategies passed.
|
|
140
|
+
|
|
141
|
+
**The forward test.** `alphafinch forward freeze runs/<run>` today, `alphafinch forward score` in six months. No model has seen tomorrow's data.
|
|
142
|
+
|
|
143
|
+
## Trading rules
|
|
144
|
+
|
|
145
|
+
Two choices shape every run: the **market** (*what* can be traded) and the **rules** (*how*). Rules are enforced by the engine on every strategy's positions, so a strategy can't break them and still look good, and the AI is told them up front.
|
|
146
|
+
|
|
147
|
+
| Flag | Rule |
|
|
148
|
+
|---|---|
|
|
149
|
+
| *(none)* | Long or short, up to 1× capital |
|
|
150
|
+
| `--long-only` | Buy only, no leverage: the cash / spot market |
|
|
151
|
+
| `--market-neutral` | Longs and shorts roughly equal, with a borrow fee on shorts |
|
|
152
|
+
| `--max-position 5%` | No single position above 5% |
|
|
153
|
+
| `--leverage 2` | Total exposure up to 2× capital |
|
|
154
|
+
|
|
155
|
+
Futures markets get futures rules automatically: shorting is as easy as buying, up to 3× exposure.
|
|
156
|
+
|
|
157
|
+
## Futures
|
|
158
|
+
|
|
159
|
+
- `futures`: 39 global futures across stock indices, government bonds, currencies, energy, metals and agriculture, for hypotheses like trend-following, crisis alpha, carry or cross-asset signals.
|
|
160
|
+
- `india-futures`: NSE futures, NIFTY and BANKNIFTY plus every F&O stock.
|
|
161
|
+
|
|
162
|
+
Free continuous futures prices fake big gains or losses at every contract roll: on Yahoo's natural-gas series a rolled position "earned" +20% a year when it really lost 12%. So AlphaFinch builds futures from funds that hold and roll the real contracts (and Indian stock futures from each stock's total return), converted to excess returns over the short-term interest rate, which is what a futures position earns.
|
|
163
|
+
|
|
164
|
+
## Safety and honesty
|
|
165
|
+
|
|
166
|
+
- **Sandbox:** AI-written code may import only `numpy`, `pandas` and `math`, with no file, network or dunder access. It runs in separate processes with restricted builtins, a CPU limit and a timeout.
|
|
167
|
+
- **Look-ahead detector:** every strategy is re-run on truncated histories. If past weights change when future data is removed, it's discarded.
|
|
168
|
+
- **No hindsight by name:** code that hard-codes a ticker or sector is rejected, so the AI can't simply pick stocks it knows did well.
|
|
169
|
+
- **Costs:** 5 bps per unit of turnover. Weights act from the next close.
|
|
170
|
+
- **Survivorship:** universes are *today's* index members. Alpha is measured against the same list, which limits the bias but doesn't remove it.
|
|
171
|
+
|
|
172
|
+
## Bring any AI
|
|
173
|
+
|
|
174
|
+
| `--provider` | Setup | Notes |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| `claude-code` | [Claude Code](https://claude.com/claude-code) installed | no API key; runs `claude -p` |
|
|
177
|
+
| `anthropic` | `ANTHROPIC_API_KEY` | default `claude-opus-5-5` |
|
|
178
|
+
| `openai` | `OPENAI_API_KEY` | pick with `--model` |
|
|
179
|
+
| `ollama` | a local model at `localhost:11434` | free and private |
|
|
180
|
+
| `compatible` | `--base-url … --model …` | any OpenAI-compatible server |
|
|
181
|
+
| `none` | nothing | tweaks and blends only, offline |
|
|
182
|
+
|
|
183
|
+
`--provider auto` (the default) uses the first one it finds. A Claude Code skill is included in [`integrations/claude-code`](integrations/claude-code).
|
|
184
|
+
|
|
185
|
+
## Markets and data
|
|
186
|
+
|
|
187
|
+
All free, no keys:
|
|
188
|
+
|
|
189
|
+
| Market | Universe |
|
|
190
|
+
|---|---|
|
|
191
|
+
| `us` | S&P 500 since 2010, with SEC fundamentals (point-in-time, the day after each 10-K) |
|
|
192
|
+
| `india` | NIFTY 200 since 2010 |
|
|
193
|
+
| `india-futures` | NSE futures: NIFTY, BANKNIFTY and every F&O stock, since 2012 |
|
|
194
|
+
| `futures` | 39 global futures since 2012 |
|
|
195
|
+
| `uk` `europe` `japan` `hongkong` `australia` `canada` `korea` | FTSE 100, Eurozone large caps, Nikkei 225, Hang Seng, ASX 200, TSX 60, KOSPI 200 |
|
|
196
|
+
| `us30` `crypto` `industries` `synthetic` | 30 US mega-caps, 15 coins, 49 US industries since 1970, simulated |
|
|
197
|
+
|
|
198
|
+
Or use your own list: `--tickers RELIANCE.NS,TCS.NS,INFY.NS` (any Yahoo symbols).
|
|
199
|
+
|
|
200
|
+
Strategies see `prices` plus `data.open/high/low/volume`, `data.sector`, `data.macro` (VIX, index, oil, gold, rates and more) and, for the US, `data.fund` (market cap, earnings yield, book-to-market, ROE, sales growth).
|
|
201
|
+
|
|
202
|
+
US fundamentals need a contact email, because the SEC asks every client for one: `export ALPHAFINCH_SEC_CONTACT="Your Name you@example.com"`.
|
|
203
|
+
|
|
204
|
+
## Write your own
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
def strategy(prices, data):
|
|
208
|
+
"""Sector Spread: sector-neutral 6-month momentum. Hypothesis: news diffuses slowly within industries."""
|
|
209
|
+
score = tk.neutralize(tk.zscore(prices.pct_change(126)), data.sector)
|
|
210
|
+
return tk.rebalance(tk.long_short(score, q=0.2), every="M")
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
A strategy returns, for every day, the fraction of capital to hold in each asset (negative means short). It may use `numpy`, `pandas`, `math` and the built-in toolkit `tk`, must never use future data, and may not name tickers. The [guide](docs/guide.md#writing-a-strategy) lists every field and toolkit function.
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
alphafinch backtest my_strategy.py us # training years only; the sealed years stay sealed
|
|
217
|
+
alphafinch holdings my_strategy.py us # what it wants to hold after the latest close
|
|
218
|
+
alphafinch world-exam my_strategy.py # 7 stock markets it has never seen
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## FAQ
|
|
222
|
+
|
|
223
|
+
**Will this make me money?** Probably not, and AlphaFinch is built to tell you so. A PASS is a lead worth researching, not a trading signal.
|
|
224
|
+
|
|
225
|
+
**Why not just backtest on all the data?** With enough tries, something always worked by luck. Only data the search never touched can tell luck from skill.
|
|
226
|
+
|
|
227
|
+
**Can I re-run until something passes?** You can, but then the exam means nothing. Count your earlier looks (`--alpha`, `--prior-looks`) or test on new markets and new data.
|
|
228
|
+
|
|
229
|
+
**Does it tell me what to buy?** `alphafinch holdings` shows the positions a strategy wants today. That's the output of a research tool, not a recommendation: check the exam verdict and the caveats first.
|
|
230
|
+
|
|
231
|
+
**Can I use my own data?** Any Yahoo symbols with `--tickers`. Other sources can be added in `alphafinch/data.py`, which returns a simple `Panel` of aligned tables.
|
|
232
|
+
|
|
233
|
+
## Development
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
git clone https://github.com/shloksobti/alphafinch && cd alphafinch
|
|
237
|
+
pip install -e ".[all,dev]"
|
|
238
|
+
pytest -q # about 40 tests, offline, under a minute
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Issues and pull requests are welcome: new markets, data sources, toolkit functions and exams especially.
|
|
242
|
+
|
|
243
|
+
## Citation
|
|
244
|
+
|
|
245
|
+
The sealed exam's bar comes from:
|
|
246
|
+
|
|
247
|
+
> Shlok Sobti, *Deflate by Bits, Not Trials*, SSRN 7557458 (2026). [papers.ssrn.com/abstract=7557458](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7557458)
|
|
248
|
+
|
|
249
|
+
## About
|
|
250
|
+
|
|
251
|
+
Built by [Shlok Sobti](https://github.com/shloksobti) at [Invsify](https://invsify.com), a SEBI-registered investment advisory in India. AlphaFinch is an independent open-source research project: nothing in this repository is investment advice or a recommendation from Invsify.
|
|
252
|
+
|
|
253
|
+
Research and educational software. Backtests ignore taxes, capacity limits and slippage beyond the modelled costs; borrow fees are charged only under `--market-neutral`.
|
|
254
|
+
|
|
255
|
+
MIT License.
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# 🐦 AlphaFinch
|
|
4
|
+
|
|
5
|
+
### AI evolves trading strategies while you sleep.<br>Then they sit an exam they can't cheat.
|
|
6
|
+
|
|
7
|
+
<p>
|
|
8
|
+
<a href="https://github.com/shloksobti/alphafinch/actions/workflows/tests.yml"><img src="https://img.shields.io/github/actions/workflow/status/shloksobti/alphafinch/tests.yml?branch=main&label=tests&logo=github" alt="tests"></a>
|
|
9
|
+
<a href="https://github.com/shloksobti/alphafinch"><img src="https://img.shields.io/badge/python-3.10%20%7C%203.11%20%7C%203.12-3776AB?logo=python&logoColor=white" alt="Python 3.10+"></a>
|
|
10
|
+
<a href="https://github.com/shloksobti/alphafinch/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT license"></a>
|
|
11
|
+
<br>
|
|
12
|
+
<a href="https://github.com/shloksobti/alphafinch/blob/main/docs/preregistration-world.md"><img src="https://img.shields.io/badge/world%20exam-PASS%207%2F7%20markets-brightgreen" alt="World exam: PASS in 7 of 7 markets"></a>
|
|
13
|
+
<a href="https://github.com/shloksobti/alphafinch/tree/main/docs"><img src="https://img.shields.io/badge/results-pre--registered-blueviolet" alt="Pre-registered results"></a>
|
|
14
|
+
<a href="https://github.com/shloksobti/alphafinch/blob/main/docs/guide.md#markets"><img src="https://img.shields.io/badge/markets-15%20(stocks%20%C2%B7%20futures%20%C2%B7%20crypto)-orange" alt="15 markets"></a>
|
|
15
|
+
<a href="https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7557458"><img src="https://img.shields.io/badge/paper-SSRN%207557458-b31b1b" alt="Paper on SSRN"></a>
|
|
16
|
+
<img src="https://img.shields.io/badge/data-free%2C%20no%20API%20keys-informational" alt="Free data">
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<img src="https://raw.githubusercontent.com/shloksobti/alphafinch/main/docs/demo.gif" alt="AlphaFinch: install, evolve strategies, and the sealed exam" width="900">
|
|
20
|
+
|
|
21
|
+
Works with **Claude Code**, **Anthropic**, **OpenAI**, **Ollama**, any OpenAI-compatible server, or **no AI at all**.
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
AlphaFinch is an [AlphaEvolve](https://deepmind.google/discover/blog/alphaevolve-a-gemini-powered-coding-agent-for-designing-advanced-algorithms/)-style lab for markets. An AI writes trading strategies as short Python functions, backtests them, and breeds the fittest: mutating them, crossing them and letting populations evolve on separate islands.
|
|
28
|
+
|
|
29
|
+
The catch with every AI trading demo: **evolution is the best overfitting machine ever built.** Run it long enough and it will "discover" a brilliant strategy in pure noise. AlphaFinch is built around that problem. The most recent years are locked in a **sealed exam** that neither evolution nor the AI ever sees, and the bar to pass accounts for every attempt.
|
|
30
|
+
|
|
31
|
+
**Most runs end with nothing passing.** That's the honest answer, and the point.
|
|
32
|
+
|
|
33
|
+
## What it found
|
|
34
|
+
|
|
35
|
+
We pre-registered every test before running it ([`docs/`](docs)) and report every result.
|
|
36
|
+
|
|
37
|
+
- **One market, three sealed years: 0 of 10 passed** on the S&P 500 and NIFTY 200 ([details](docs/preregistration-v2.md); earlier runs on industries and India: 0 of 8, [details](docs/preregistration.md)). Training scores rose while out-of-sample alpha fell: overfitting, caught in the act.
|
|
38
|
+
- **So we rebuilt the search** to score alpha, choose champions on years breeding never saw, and demand an edge that holds across random halves of the stocks. On a stand-in exam it beat the original search in 4 of 4 matched runs ([details](docs/search-ablation.md)).
|
|
39
|
+
- **Then the world exam:** five strategies, bred only on data before 2017, were frozen and tested unchanged on **seven stock markets they had never seen**, from October 2017 to October 2026.
|
|
40
|
+
|
|
41
|
+
| Strategy | Alpha / year | t (bar 2.33) | Markets with positive alpha | |
|
|
42
|
+
|---|---|---|---|---|
|
|
43
|
+
| **Quiet Sector Tether v2** | **+2.9%** | **3.40** | **7 of 7** | ✅ PASS |
|
|
44
|
+
| The Team (India-bred) | +2.3% | 2.14 | 6 of 7 | 🟡 PROMISING |
|
|
45
|
+
| Quiet Intraday Relay v2 | +0.9% | 1.03 | 5 of 7 | 🟡 PROMISING |
|
|
46
|
+
| Two others | negative | | | ❌ FAIL |
|
|
47
|
+
|
|
48
|
+
*Quiet Sector Tether* buys, within each sector, the stocks that move least with the market and shorts those that move most. It is essentially **"betting against correlation"**, an anomaly published by AQR researchers in 2020: the AI rediscovered it from pre-2017 US data, and it held up in the UK, the Eurozone, Japan, Hong Kong, Australia, Canada and Korea. It survives higher trading costs (t 2.94 at 15 bps), a causal beta hedge (t 3.22) and the six standard Fama–French factors (alpha +2.5%/yr, t 2.64).
|
|
49
|
+
|
|
50
|
+
The caveats are in the [full write-up](docs/preregistration-world.md): it is weaker in 2020–26 alone (t 2.17), short-borrowing fees aren't modelled, and Korea banned short selling for parts of the period. It's frozen in [`forward/`](forward) to be judged again on data that doesn't exist yet.
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Needs Python 3.10+. No API keys or data subscriptions: market data is free and downloaded on first use.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install "alphafinch[all] @ git+https://github.com/shloksobti/alphafinch"
|
|
58
|
+
|
|
59
|
+
alphafinch demo # offline: synthetic market, no AI, no network (~1 min)
|
|
60
|
+
alphafinch evolve india # NIFTY 200 stocks; AI provider auto-detected
|
|
61
|
+
alphafinch evolve india --long-only # same, but no shorting (cash market)
|
|
62
|
+
alphafinch evolve india-futures # NSE futures: NIFTY, BANKNIFTY, every F&O stock
|
|
63
|
+
alphafinch evolve futures # 39 global futures: indices, bonds, FX, commodities
|
|
64
|
+
alphafinch holdings runs/<run>/champion.py india # what the champion wants to hold today
|
|
65
|
+
alphafinch world-exam runs/<run>/champion.py # test it on 7 markets it has never seen
|
|
66
|
+
alphafinch replay runs/<run> # re-watch a finished run as a short story
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Two choices shape every run: the **market** (a plain word, e.g. `india`) and the **rules** (flags, e.g. `--long-only`). Type `alphafinch` alone for an overview, `alphafinch evolve -h` for every option, or read the **[guide](docs/guide.md)**.
|
|
70
|
+
|
|
71
|
+
**What a run takes.** The default run (20 generations × 4 islands) makes a few hundred AI calls and takes roughly 20–40 minutes. The first run on a market also downloads its data (a few minutes, then cached). With `claude-code` it uses your existing subscription; with an API key you pay your provider's usual rates.
|
|
72
|
+
|
|
73
|
+
**What you get** in `runs/<timestamp>/`:
|
|
74
|
+
|
|
75
|
+
| File | Contents |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `report.html` | The morning report: champion, exam verdict, equity curves, the team, the lab notebook, the family tree |
|
|
78
|
+
| `champion.py` | The champion's code, ready for `backtest`, `holdings` or `world-exam` |
|
|
79
|
+
| `team.py` | A diversified team of survivors, when one forms |
|
|
80
|
+
| `population.json` | Every strategy bred, with its scores |
|
|
81
|
+
|
|
82
|
+
`alphafinch replay runs/<timestamp>` re-tells any finished run as a short story:
|
|
83
|
+
|
|
84
|
+
<img src="https://raw.githubusercontent.com/shloksobti/alphafinch/main/docs/replay.gif" alt="alphafinch replay: the AI writing a strategy, a new champion, and the sealed exam" width="820">
|
|
85
|
+
|
|
86
|
+
## How it works
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
breed ─▶ choose ─▶ 🔒 sealed exam ─▶ 🌍 world exam ─▶ ⏳ forward test
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**Breeding.** Four islands, each with a population of strategies. Every generation the AI **mutates** a parent using its report card, **crosses** two parents into one idea, or invents an **immigrant** from a fresh hypothesis. No-AI operators **tweak** a constant or **blend** two portfolios. Champions migrate between islands, and each island keeps the best strategy in every niche (fast or slow, market-neutral or market-hugging), so the population can't collapse onto one idea.
|
|
93
|
+
|
|
94
|
+
**Fitness rewards a real edge, not a lucky one:**
|
|
95
|
+
- **Alpha, not returns.** Each of four training eras is scored on its appraisal ratio: return beyond market exposure, per unit of risk. The *worst* era counts as much as the typical one.
|
|
96
|
+
- **Broad, not narrow.** The portfolio is re-scored on random halves of the stocks, and the worst half counts.
|
|
97
|
+
- **Stable, not knife-edge.** Parameters are nudged and neighbours re-scored. Penalties for heavy trading and bloated code.
|
|
98
|
+
|
|
99
|
+
**The AI works like a researcher.** Every strategy starts with a written hypothesis. A lab notebook of every idea tried, and how it fared, goes into each prompt. A toolkit (`tk`) makes sector-neutral long/short books, residual returns and volatility targeting one-liners. Use `--strong-model` to give crossovers and new ideas to a bigger model.
|
|
100
|
+
|
|
101
|
+
**Choosing the champion.** The last three training years are held back from breeding. The top ten finalists and a diversified team are scored once on those years, and the best becomes the champion. The AI never sees those scores.
|
|
102
|
+
|
|
103
|
+
**The sealed exam.** The most recent three years. Each attempt reveals only PASS or FAIL, and the bar is t⁻¹(α / attempts), valid however adaptively the search ran ([the theory](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7557458)). Results are graded **PASS**, **PROMISING** (t > 1, with the years of data a pass would need) or **FAIL**.
|
|
104
|
+
|
|
105
|
+
**The world exam.** Three years of one market can rarely prove a realistic edge. So a frozen strategy runs unchanged on seven other markets, and the evidence is pooled. The verdict uses the lower of two pooled t-statistics (Newey–West and Stouffer), so a strategy has to convince both. Calibrated with placebos: 0 of 200 random strategies passed.
|
|
106
|
+
|
|
107
|
+
**The forward test.** `alphafinch forward freeze runs/<run>` today, `alphafinch forward score` in six months. No model has seen tomorrow's data.
|
|
108
|
+
|
|
109
|
+
## Trading rules
|
|
110
|
+
|
|
111
|
+
Two choices shape every run: the **market** (*what* can be traded) and the **rules** (*how*). Rules are enforced by the engine on every strategy's positions, so a strategy can't break them and still look good, and the AI is told them up front.
|
|
112
|
+
|
|
113
|
+
| Flag | Rule |
|
|
114
|
+
|---|---|
|
|
115
|
+
| *(none)* | Long or short, up to 1× capital |
|
|
116
|
+
| `--long-only` | Buy only, no leverage: the cash / spot market |
|
|
117
|
+
| `--market-neutral` | Longs and shorts roughly equal, with a borrow fee on shorts |
|
|
118
|
+
| `--max-position 5%` | No single position above 5% |
|
|
119
|
+
| `--leverage 2` | Total exposure up to 2× capital |
|
|
120
|
+
|
|
121
|
+
Futures markets get futures rules automatically: shorting is as easy as buying, up to 3× exposure.
|
|
122
|
+
|
|
123
|
+
## Futures
|
|
124
|
+
|
|
125
|
+
- `futures`: 39 global futures across stock indices, government bonds, currencies, energy, metals and agriculture, for hypotheses like trend-following, crisis alpha, carry or cross-asset signals.
|
|
126
|
+
- `india-futures`: NSE futures, NIFTY and BANKNIFTY plus every F&O stock.
|
|
127
|
+
|
|
128
|
+
Free continuous futures prices fake big gains or losses at every contract roll: on Yahoo's natural-gas series a rolled position "earned" +20% a year when it really lost 12%. So AlphaFinch builds futures from funds that hold and roll the real contracts (and Indian stock futures from each stock's total return), converted to excess returns over the short-term interest rate, which is what a futures position earns.
|
|
129
|
+
|
|
130
|
+
## Safety and honesty
|
|
131
|
+
|
|
132
|
+
- **Sandbox:** AI-written code may import only `numpy`, `pandas` and `math`, with no file, network or dunder access. It runs in separate processes with restricted builtins, a CPU limit and a timeout.
|
|
133
|
+
- **Look-ahead detector:** every strategy is re-run on truncated histories. If past weights change when future data is removed, it's discarded.
|
|
134
|
+
- **No hindsight by name:** code that hard-codes a ticker or sector is rejected, so the AI can't simply pick stocks it knows did well.
|
|
135
|
+
- **Costs:** 5 bps per unit of turnover. Weights act from the next close.
|
|
136
|
+
- **Survivorship:** universes are *today's* index members. Alpha is measured against the same list, which limits the bias but doesn't remove it.
|
|
137
|
+
|
|
138
|
+
## Bring any AI
|
|
139
|
+
|
|
140
|
+
| `--provider` | Setup | Notes |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `claude-code` | [Claude Code](https://claude.com/claude-code) installed | no API key; runs `claude -p` |
|
|
143
|
+
| `anthropic` | `ANTHROPIC_API_KEY` | default `claude-opus-5-5` |
|
|
144
|
+
| `openai` | `OPENAI_API_KEY` | pick with `--model` |
|
|
145
|
+
| `ollama` | a local model at `localhost:11434` | free and private |
|
|
146
|
+
| `compatible` | `--base-url … --model …` | any OpenAI-compatible server |
|
|
147
|
+
| `none` | nothing | tweaks and blends only, offline |
|
|
148
|
+
|
|
149
|
+
`--provider auto` (the default) uses the first one it finds. A Claude Code skill is included in [`integrations/claude-code`](integrations/claude-code).
|
|
150
|
+
|
|
151
|
+
## Markets and data
|
|
152
|
+
|
|
153
|
+
All free, no keys:
|
|
154
|
+
|
|
155
|
+
| Market | Universe |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `us` | S&P 500 since 2010, with SEC fundamentals (point-in-time, the day after each 10-K) |
|
|
158
|
+
| `india` | NIFTY 200 since 2010 |
|
|
159
|
+
| `india-futures` | NSE futures: NIFTY, BANKNIFTY and every F&O stock, since 2012 |
|
|
160
|
+
| `futures` | 39 global futures since 2012 |
|
|
161
|
+
| `uk` `europe` `japan` `hongkong` `australia` `canada` `korea` | FTSE 100, Eurozone large caps, Nikkei 225, Hang Seng, ASX 200, TSX 60, KOSPI 200 |
|
|
162
|
+
| `us30` `crypto` `industries` `synthetic` | 30 US mega-caps, 15 coins, 49 US industries since 1970, simulated |
|
|
163
|
+
|
|
164
|
+
Or use your own list: `--tickers RELIANCE.NS,TCS.NS,INFY.NS` (any Yahoo symbols).
|
|
165
|
+
|
|
166
|
+
Strategies see `prices` plus `data.open/high/low/volume`, `data.sector`, `data.macro` (VIX, index, oil, gold, rates and more) and, for the US, `data.fund` (market cap, earnings yield, book-to-market, ROE, sales growth).
|
|
167
|
+
|
|
168
|
+
US fundamentals need a contact email, because the SEC asks every client for one: `export ALPHAFINCH_SEC_CONTACT="Your Name you@example.com"`.
|
|
169
|
+
|
|
170
|
+
## Write your own
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
def strategy(prices, data):
|
|
174
|
+
"""Sector Spread: sector-neutral 6-month momentum. Hypothesis: news diffuses slowly within industries."""
|
|
175
|
+
score = tk.neutralize(tk.zscore(prices.pct_change(126)), data.sector)
|
|
176
|
+
return tk.rebalance(tk.long_short(score, q=0.2), every="M")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
A strategy returns, for every day, the fraction of capital to hold in each asset (negative means short). It may use `numpy`, `pandas`, `math` and the built-in toolkit `tk`, must never use future data, and may not name tickers. The [guide](docs/guide.md#writing-a-strategy) lists every field and toolkit function.
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
alphafinch backtest my_strategy.py us # training years only; the sealed years stay sealed
|
|
183
|
+
alphafinch holdings my_strategy.py us # what it wants to hold after the latest close
|
|
184
|
+
alphafinch world-exam my_strategy.py # 7 stock markets it has never seen
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## FAQ
|
|
188
|
+
|
|
189
|
+
**Will this make me money?** Probably not, and AlphaFinch is built to tell you so. A PASS is a lead worth researching, not a trading signal.
|
|
190
|
+
|
|
191
|
+
**Why not just backtest on all the data?** With enough tries, something always worked by luck. Only data the search never touched can tell luck from skill.
|
|
192
|
+
|
|
193
|
+
**Can I re-run until something passes?** You can, but then the exam means nothing. Count your earlier looks (`--alpha`, `--prior-looks`) or test on new markets and new data.
|
|
194
|
+
|
|
195
|
+
**Does it tell me what to buy?** `alphafinch holdings` shows the positions a strategy wants today. That's the output of a research tool, not a recommendation: check the exam verdict and the caveats first.
|
|
196
|
+
|
|
197
|
+
**Can I use my own data?** Any Yahoo symbols with `--tickers`. Other sources can be added in `alphafinch/data.py`, which returns a simple `Panel` of aligned tables.
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
git clone https://github.com/shloksobti/alphafinch && cd alphafinch
|
|
203
|
+
pip install -e ".[all,dev]"
|
|
204
|
+
pytest -q # about 40 tests, offline, under a minute
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Issues and pull requests are welcome: new markets, data sources, toolkit functions and exams especially.
|
|
208
|
+
|
|
209
|
+
## Citation
|
|
210
|
+
|
|
211
|
+
The sealed exam's bar comes from:
|
|
212
|
+
|
|
213
|
+
> Shlok Sobti, *Deflate by Bits, Not Trials*, SSRN 7557458 (2026). [papers.ssrn.com/abstract=7557458](https://papers.ssrn.com/sol3/papers.cfm?abstract_id=7557458)
|
|
214
|
+
|
|
215
|
+
## About
|
|
216
|
+
|
|
217
|
+
Built by [Shlok Sobti](https://github.com/shloksobti) at [Invsify](https://invsify.com), a SEBI-registered investment advisory in India. AlphaFinch is an independent open-source research project: nothing in this repository is investment advice or a recommendation from Invsify.
|
|
218
|
+
|
|
219
|
+
Research and educational software. Backtests ignore taxes, capacity limits and slippage beyond the modelled costs; borrow fees are charged only under `--market-neutral`.
|
|
220
|
+
|
|
221
|
+
MIT License.
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Terminal line charts.
|
|
2
|
+
|
|
3
|
+
`chart` draws solid lines with box-drawing characters (asciichart style), which render
|
|
4
|
+
crisply in any monospace font. Several series share one y-axis; later series draw on top.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _resample(y: np.ndarray, n: int) -> np.ndarray:
|
|
13
|
+
"""Average within each column (smooths daily noise instead of sampling it)."""
|
|
14
|
+
if len(y) <= n:
|
|
15
|
+
idx = np.linspace(0, len(y) - 1, n)
|
|
16
|
+
return np.interp(idx, np.arange(len(y)), y)
|
|
17
|
+
edges = np.linspace(0, len(y), n + 1).astype(int)
|
|
18
|
+
return np.array([y[a:b].mean() for a, b in zip(edges[:-1], edges[1:])])
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def chart(series: list[tuple[np.ndarray, str]], width: int = 56, height: int = 12,
|
|
22
|
+
upto: float = 1.0, log: bool = True) -> Text:
|
|
23
|
+
"""Render series (values, rich style) as solid lines. `upto` in (0, 1] draws only the
|
|
24
|
+
first fraction of each series, for animation."""
|
|
25
|
+
ys = []
|
|
26
|
+
for v, _ in series:
|
|
27
|
+
v = np.asarray(v, float)
|
|
28
|
+
ys.append(np.log(v) if log else v)
|
|
29
|
+
cols = max(2, width)
|
|
30
|
+
rs = [_resample(y, cols) for y in ys]
|
|
31
|
+
lo = min(float(r.min()) for r in rs)
|
|
32
|
+
hi = max(float(r.max()) for r in rs)
|
|
33
|
+
span = hi - lo if hi > lo else 1.0
|
|
34
|
+
grid = [[" "] * cols for _ in range(height)]
|
|
35
|
+
style = [[""] * cols for _ in range(height)]
|
|
36
|
+
last = max(2, int(cols * upto))
|
|
37
|
+
LIGHT = dict(h="─", up_from="╯", up_to="╭", down_from="╮", down_to="╰", v="│")
|
|
38
|
+
HEAVY = dict(h="━", up_from="┛", up_to="┏", down_from="┓", down_to="┗", v="┃")
|
|
39
|
+
for r, (_, st) in zip(rs, series):
|
|
40
|
+
g = HEAVY if "bold" in st else LIGHT
|
|
41
|
+
rows = np.round((1 - (r - lo) / span) * (height - 1)).astype(int)
|
|
42
|
+
for x in range(last):
|
|
43
|
+
y0 = rows[x]
|
|
44
|
+
if x == 0:
|
|
45
|
+
grid[y0][x], style[y0][x] = g["h"], st
|
|
46
|
+
continue
|
|
47
|
+
yp = rows[x - 1]
|
|
48
|
+
if y0 == yp:
|
|
49
|
+
grid[y0][x], style[y0][x] = g["h"], st
|
|
50
|
+
elif y0 < yp: # going up (smaller row index)
|
|
51
|
+
grid[yp][x], style[yp][x] = g["up_from"], st
|
|
52
|
+
grid[y0][x], style[y0][x] = g["up_to"], st
|
|
53
|
+
for k in range(y0 + 1, yp):
|
|
54
|
+
grid[k][x], style[k][x] = g["v"], st
|
|
55
|
+
else: # going down
|
|
56
|
+
grid[yp][x], style[yp][x] = g["down_from"], st
|
|
57
|
+
grid[y0][x], style[y0][x] = g["down_to"], st
|
|
58
|
+
for k in range(yp + 1, y0):
|
|
59
|
+
grid[k][x], style[k][x] = g["v"], st
|
|
60
|
+
out = Text()
|
|
61
|
+
for r in range(height):
|
|
62
|
+
for c in range(cols):
|
|
63
|
+
out.append(grid[r][c], style=style[r][c])
|
|
64
|
+
if r < height - 1:
|
|
65
|
+
out.append("\n")
|
|
66
|
+
return out
|