candlebench 0.9.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.
- candlebench-0.9.0/LICENSE +21 -0
- candlebench-0.9.0/PKG-INFO +319 -0
- candlebench-0.9.0/README.md +283 -0
- candlebench-0.9.0/candlebench/__init__.py +10 -0
- candlebench-0.9.0/candlebench/__main__.py +3 -0
- candlebench-0.9.0/candlebench/_enginecore.cpp +330 -0
- candlebench-0.9.0/candlebench/alpaca.py +346 -0
- candlebench-0.9.0/candlebench/bars.py +504 -0
- candlebench-0.9.0/candlebench/cli.py +461 -0
- candlebench-0.9.0/candlebench/config.py +420 -0
- candlebench-0.9.0/candlebench/costs.py +133 -0
- candlebench-0.9.0/candlebench/engine.py +671 -0
- candlebench-0.9.0/candlebench/leaderboard.py +403 -0
- candlebench-0.9.0/candlebench/metrics.py +568 -0
- candlebench-0.9.0/candlebench/patterns/__init__.py +132 -0
- candlebench-0.9.0/candlebench/patterns/context.py +163 -0
- candlebench-0.9.0/candlebench/patterns/control.py +62 -0
- candlebench-0.9.0/candlebench/patterns/double.py +263 -0
- candlebench-0.9.0/candlebench/patterns/multi.py +129 -0
- candlebench-0.9.0/candlebench/patterns/single.py +103 -0
- candlebench-0.9.0/candlebench/patterns/triple.py +420 -0
- candlebench-0.9.0/candlebench/provenance.py +118 -0
- candlebench-0.9.0/candlebench/quotes.py +244 -0
- candlebench-0.9.0/candlebench/report.py +402 -0
- candlebench-0.9.0/candlebench/runner.py +601 -0
- candlebench-0.9.0/candlebench/sampling.py +152 -0
- candlebench-0.9.0/candlebench/signals.py +134 -0
- candlebench-0.9.0/candlebench/site.py +239 -0
- candlebench-0.9.0/candlebench/synthetic.py +257 -0
- candlebench-0.9.0/candlebench/ticks.py +134 -0
- candlebench-0.9.0/candlebench/trades.py +311 -0
- candlebench-0.9.0/candlebench/universe.py +58 -0
- candlebench-0.9.0/candlebench/walkforward.py +168 -0
- candlebench-0.9.0/candlebench/web/__init__.py +1 -0
- candlebench-0.9.0/candlebench/web/history.py +153 -0
- candlebench-0.9.0/candlebench/web/jobs.py +194 -0
- candlebench-0.9.0/candlebench/web/server.py +787 -0
- candlebench-0.9.0/candlebench/web/static/app.css +226 -0
- candlebench-0.9.0/candlebench/web/static/app.js +1260 -0
- candlebench-0.9.0/candlebench/web/static/index.html +175 -0
- candlebench-0.9.0/candlebench.egg-info/PKG-INFO +319 -0
- candlebench-0.9.0/candlebench.egg-info/SOURCES.txt +79 -0
- candlebench-0.9.0/candlebench.egg-info/dependency_links.txt +1 -0
- candlebench-0.9.0/candlebench.egg-info/entry_points.txt +2 -0
- candlebench-0.9.0/candlebench.egg-info/requires.txt +14 -0
- candlebench-0.9.0/candlebench.egg-info/top_level.txt +1 -0
- candlebench-0.9.0/pyproject.toml +82 -0
- candlebench-0.9.0/setup.cfg +4 -0
- candlebench-0.9.0/setup.py +46 -0
- candlebench-0.9.0/tests/test_alpaca.py +509 -0
- candlebench-0.9.0/tests/test_bars.py +245 -0
- candlebench-0.9.0/tests/test_bars_retry.py +92 -0
- candlebench-0.9.0/tests/test_breakdowns.py +177 -0
- candlebench-0.9.0/tests/test_cli.py +26 -0
- candlebench-0.9.0/tests/test_config.py +174 -0
- candlebench-0.9.0/tests/test_context.py +84 -0
- candlebench-0.9.0/tests/test_costs.py +452 -0
- candlebench-0.9.0/tests/test_end_to_end.py +290 -0
- candlebench-0.9.0/tests/test_engine.py +424 -0
- candlebench-0.9.0/tests/test_engine_core.py +193 -0
- candlebench-0.9.0/tests/test_engine_golden.py +43 -0
- candlebench-0.9.0/tests/test_holdout.py +90 -0
- candlebench-0.9.0/tests/test_jobs.py +92 -0
- candlebench-0.9.0/tests/test_leaderboard.py +133 -0
- candlebench-0.9.0/tests/test_metrics.py +610 -0
- candlebench-0.9.0/tests/test_patterns.py +711 -0
- candlebench-0.9.0/tests/test_provenance.py +62 -0
- candlebench-0.9.0/tests/test_quotes.py +172 -0
- candlebench-0.9.0/tests/test_report.py +109 -0
- candlebench-0.9.0/tests/test_signals.py +170 -0
- candlebench-0.9.0/tests/test_site.py +134 -0
- candlebench-0.9.0/tests/test_stock.py +236 -0
- candlebench-0.9.0/tests/test_synthetic.py +197 -0
- candlebench-0.9.0/tests/test_ticks.py +390 -0
- candlebench-0.9.0/tests/test_trade_table.py +127 -0
- candlebench-0.9.0/tests/test_trades.py +296 -0
- candlebench-0.9.0/tests/test_validation_api.py +73 -0
- candlebench-0.9.0/tests/test_validation_run.py +207 -0
- candlebench-0.9.0/tests/test_walkforward.py +65 -0
- candlebench-0.9.0/tests/test_web.py +898 -0
- candlebench-0.9.0/tests/test_windows.py +244 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kaitlyn Bassford
|
|
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,319 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: candlebench
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: Measure which candlestick patterns have a real intraday edge
|
|
5
|
+
Author: Kaitlyn Bassford
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dpologdvinity/candlebench
|
|
8
|
+
Project-URL: Repository, https://github.com/dpologdvinity/candlebench
|
|
9
|
+
Project-URL: Issues, https://github.com/dpologdvinity/candlebench/issues
|
|
10
|
+
Keywords: backtesting,candlestick,trading,statistics,bootstrap,market-microstructure
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
License-File: LICENSE
|
|
23
|
+
Requires-Dist: yfinance>=0.2
|
|
24
|
+
Requires-Dist: pandas>=2.0
|
|
25
|
+
Requires-Dist: numpy>=1.24
|
|
26
|
+
Requires-Dist: pyarrow>=14
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
29
|
+
Requires-Dist: pylint>=3; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
31
|
+
Requires-Dist: pybind11>=2.13; extra == "dev"
|
|
32
|
+
Provides-Extra: browser
|
|
33
|
+
Requires-Dist: pytest>=8; extra == "browser"
|
|
34
|
+
Requires-Dist: playwright>=1.50; extra == "browser"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
# candlebench
|
|
38
|
+
|
|
39
|
+
[](https://github.com/dpologdvinity/candlebench/actions/workflows/ci.yml)
|
|
40
|
+

|
|
41
|
+
[](https://github.com/dpologdvinity/candlebench/blob/master/LICENSE)
|
|
42
|
+
|
|
43
|
+
**Do classic candlestick patterns have a real intraday edge?** candlebench tests
|
|
44
|
+
66 patterns by turning every signal into a mechanical trade. It charges each
|
|
45
|
+
trade the spread quoted at that time of day, and compares the result with random
|
|
46
|
+
entries taken on the same days. A pattern counts only if it beats that control,
|
|
47
|
+
makes money after costs, and holds up on dates it was not chosen on.
|
|
48
|
+
|
|
49
|
+
The same test runs on any signal you write: see
|
|
50
|
+
[Testing your own signal](#testing-your-own-signal).
|
|
51
|
+
|
|
52
|
+
**Live:** [interactive two-year results](https://dpologdvinity.github.io/candlebench/app/real/) ·
|
|
53
|
+
[interactive synthetic demo](https://dpologdvinity.github.io/candlebench/app/demo/) ·
|
|
54
|
+
[results site](https://dpologdvinity.github.io/candlebench/)
|
|
55
|
+
|
|
56
|
+

|
|
57
|
+
|
|
58
|
+
## The answer
|
|
59
|
+
|
|
60
|
+
The run covers two years of 1-minute bars (October 2024 to October 2026) for
|
|
61
|
+
117 liquid US stocks and ETFs: 5,000 sampled symbol-days on 501 market dates
|
|
62
|
+
and 1,021,358 pattern trades, charged an average quoted spread of **2.39 bps
|
|
63
|
+
per leg**. Four earlier runs came first (20 patterns on 50 symbols twice, 66 on
|
|
64
|
+
100, and 66 on 117 before the data was brought to one common date range), so
|
|
65
|
+
the multiple-comparison correction counts five experiments.
|
|
66
|
+
|
|
67
|
+
- **No pattern earns `EDGE`.** None of the 66 has positive expectancy after
|
|
68
|
+
costs, and 59 have 95% intervals entirely below zero.
|
|
69
|
+
- **No pattern beats random entry, even before costs.** Compared in gross R
|
|
70
|
+
with its own stop-matched random entries, no row survives multiple-comparison
|
|
71
|
+
correction.
|
|
72
|
+
- **Random entry itself loses about 0.34R per trade at 1m**, almost all of it
|
|
73
|
+
spread. Before costs, the two controls sit at +0.015R and +0.009R.
|
|
74
|
+
- **"No edge" has a size.** Each row reports the smallest advantage its test
|
|
75
|
+
would catch 80% of the time. For the 19 patterns with over 20,000 trades that
|
|
76
|
+
is 0.018R to 0.034R, so an advantage over random entry larger than that would
|
|
77
|
+
very likely have been found. Rare patterns report 0.085R or more; the
|
|
78
|
+
rarest, the abandoned babies, trade too seldom to measure.
|
|
79
|
+
- **The same holds at 5, 15 and 60 minutes.** A second run over the same two
|
|
80
|
+
years and 117 symbols sampled 5,000 symbol-days per timeframe (227,879
|
|
81
|
+
pattern trades, 2.21 bps per leg). No pattern beats its matched controls at
|
|
82
|
+
any of the three. Random entry loses 0.15R to 0.25R net there, less than at
|
|
83
|
+
1m because the spread is a smaller share of a wider stop. At 5m the tweezers
|
|
84
|
+
would reveal an advantage of 0.05R; at 1h, trades are too few to rule out
|
|
85
|
+
less than about 0.2R. [Interactive](https://dpologdvinity.github.io/candlebench/app/intervals/) ·
|
|
86
|
+
[report](https://dpologdvinity.github.io/candlebench/two-year-5m-15m-1h.html).
|
|
87
|
+
|
|
88
|
+
An earlier version of this project reported that eight patterns beat random
|
|
89
|
+
entry on signal alone and lost only to costs. That came from a measurement bias
|
|
90
|
+
the project later found in its own method; the next section explains it.
|
|
91
|
+
|
|
92
|
+
## How it is checked
|
|
93
|
+
|
|
94
|
+
The method has to find nothing where nothing exists. `candlebench.synthetic`
|
|
95
|
+
generates a random walk that no pattern can predict, and
|
|
96
|
+
[a calibration run](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/null-calibration.md) counts how often each
|
|
97
|
+
test passes on it anyway.
|
|
98
|
+
|
|
99
|
+
It exposed two real flaws, and both are fixed:
|
|
100
|
+
|
|
101
|
+
1. **Net R rewards wide stops.** Costs are a fixed number of bps, and R divides
|
|
102
|
+
by the distance to the stop, so a pattern with a wider stop pays fewer R for
|
|
103
|
+
the same spread. Compared with random entry in net R, wide-stop patterns
|
|
104
|
+
"won" with no signal at all: 38 of 800 rows across ten seeds, at least one in
|
|
105
|
+
every run.
|
|
106
|
+
2. **A control with a different stop is not a fair control.** Even before
|
|
107
|
+
costs, a tighter stop is caught more often by the rule that a bar touching
|
|
108
|
+
both levels is a stop. Each pattern trade is now compared with its own
|
|
109
|
+
matched control. The control has the same direction and stop distance and
|
|
110
|
+
the same exit code, and enters on a random bar one to five bars later.
|
|
111
|
+
Entering *earlier* turned out to be a trap: those bars were selected by the
|
|
112
|
+
pattern itself, and a control placed five bars before a tweezer top lost
|
|
113
|
+
0.56R on pure noise.
|
|
114
|
+
|
|
115
|
+
With both fixes, 1 row in 800 passes on the random walk, in 1 run of 10, which
|
|
116
|
+
is what a 5% familywise error rate predicts. Profitability is still tested net
|
|
117
|
+
of costs.
|
|
118
|
+
|
|
119
|
+
Finding nothing in noise is half the check; a method that never finds anything
|
|
120
|
+
passes it too. [The power study](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/detection-power.md) plants
|
|
121
|
+
edges of known size in independent synthetic markets and runs the real-data
|
|
122
|
+
design over them. With all 66 patterns, an advantage of 0.144R was caught
|
|
123
|
+
every time and 0.097R half the time, bracketing the 0.11R limit each row
|
|
124
|
+
reports. It also shows that making money needs a gross edge above about 0.3R, and that
|
|
125
|
+
a single 20% holdout confirms only large edges;
|
|
126
|
+
[walk-forward confirmation](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/walk-forward.md) pools every
|
|
127
|
+
fold's out-of-sample trades and confirms far more.
|
|
128
|
+
|
|
129
|
+

|
|
130
|
+
|
|
131
|
+
Other safeguards:
|
|
132
|
+
|
|
133
|
+
| Concern | What the code does |
|
|
134
|
+
| --- | --- |
|
|
135
|
+
| Lucky draws | Every pattern trade is paired with a random-entry control in the same session, with the same direction and stop distance. Matched-rate random-entry rows show what random entry loses at each timeframe. |
|
|
136
|
+
| Correlated trades | Bootstrap intervals resample whole market dates, not individual trades. |
|
|
137
|
+
| Many comparisons | Holm correction across every row and timeframe, plus a declared-experiment multiplier. A run warns when bootstrap resolution makes passing impossible. |
|
|
138
|
+
| Overfitting | The newest 20% of dates are held out. Only discovery candidates are tested on them, and `EDGE` needs both samples. |
|
|
139
|
+
| Flattering fills | Entry is on the next bar. A bar touching both stop and target counts as a stop unless it opened past the target. Gaps fill at the open. |
|
|
140
|
+
| Optimistic costs | The quoted model samples NBBO spreads per symbol and time of day, because the open runs about 4x midday. Borrowed buckets and estimates are reported separately from observed ones. |
|
|
141
|
+
| Tiny samples | A verdict needs at least 30 trades and 10 independent dates; a 3-trade "+0.95R" stays `INSUFFICIENT`. |
|
|
142
|
+
|
|
143
|
+
## Quick start
|
|
144
|
+
|
|
145
|
+
The demo needs no API key or download:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
python -m venv .venv && source .venv/bin/activate
|
|
149
|
+
pip install -e .
|
|
150
|
+
candlebench demo # synthetic market -> full run -> dashboard on :8765
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The demo generates eight symbols of synthetic 1m bars and runs every pattern at
|
|
154
|
+
three timeframes in a few seconds. It then opens the dashboard on that run.
|
|
155
|
+
Every leaderboard row drills down to its equity curve, its per-symbol and
|
|
156
|
+
time-of-day breakdowns, and each session's chart with the trades drawn on it.
|
|
157
|
+
|
|
158
|
+

|
|
159
|
+
|
|
160
|
+
### Sharing a result
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
candlebench report out.json # out.html: one self-contained file, no scripts
|
|
164
|
+
candlebench site out.json --out app/ # the interactive dashboard as static files
|
|
165
|
+
docker build -t candlebench-demo . # or served live and read-only
|
|
166
|
+
docker run --rm -p 8765:8765 candlebench-demo
|
|
167
|
+
fly deploy # the same container on Fly.io (fly.toml)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`candlebench site` exports the full dashboard for one run as plain files, so
|
|
171
|
+
any static host serves it with no server. A browser test sends every query to
|
|
172
|
+
the live server and to the exported copy and requires identical answers.
|
|
173
|
+
A report holds the summary, a leaderboard per timeframe, and a drill-down for
|
|
174
|
+
every pattern. Trades appear only as R multiples and dates, never as prices, so
|
|
175
|
+
a report built from licensed market data can be published. The dashboard can
|
|
176
|
+
download any saved run as a report. `serve --read-only` shows saved runs but
|
|
177
|
+
refuses to start work, and only a read-only server will listen beyond loopback.
|
|
178
|
+
The [results site](https://dpologdvinity.github.io/candlebench/) is rebuilt
|
|
179
|
+
by CI on every push.
|
|
180
|
+
|
|
181
|
+
### With real market data
|
|
182
|
+
|
|
183
|
+
Two years of intraday history come from Alpaca's free market-data API. Yahoo
|
|
184
|
+
Finance needs no key but serves only about a month. Keys are read from the
|
|
185
|
+
environment only, never from a config file or the browser.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
export ALPACA_API_KEY=... ALPACA_SECRET_KEY=...
|
|
189
|
+
# in config/backtest.toml: source = "alpaca", and model = "quoted" for real spreads
|
|
190
|
+
candlebench fetch # download and cache bars
|
|
191
|
+
candlebench quotes --sessions 4 # sample NBBO spreads into a table
|
|
192
|
+
candlebench run -v --json out.json # terminal leaderboard; trades go to out.parquet
|
|
193
|
+
candlebench serve # dashboard
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
A 200-trial run over two years of 1m bars takes about 20 seconds and peaks under
|
|
197
|
+
1 GiB of memory; the published 5,000-trial run over 117 symbols and 66 patterns
|
|
198
|
+
took 7 to 25 minutes on one shared core and peaked at 4.0 GiB. Its trade file is 40 MB, so
|
|
199
|
+
the published trade files sit on the
|
|
200
|
+
[data release](https://github.com/dpologdvinity/candlebench/releases/tag/data-2026-10-08)
|
|
201
|
+
rather than in git; `docs/results/` keeps the JSON summaries.
|
|
202
|
+
|
|
203
|
+
## Testing your own signal
|
|
204
|
+
|
|
205
|
+
Most backtests compare a strategy with doing nothing, which a strategy can beat
|
|
206
|
+
by accident: the market drifted, the stops were wide, the costs were left out.
|
|
207
|
+
`candlebench test` holds any signal to the comparison the patterns faced. Each
|
|
208
|
+
trade is paired with a random entry on the same session, in the same direction
|
|
209
|
+
and at the same stop distance, so the signal is credited only for choosing
|
|
210
|
+
*when* to enter. It is charged the same costs, corrected for multiple testing,
|
|
211
|
+
and confirmed on held-out dates.
|
|
212
|
+
|
|
213
|
+
A signal is a Python file that registers a detector, as the built-in patterns
|
|
214
|
+
do. This one is [examples/breakout_20.py](https://github.com/dpologdvinity/candlebench/blob/master/examples/breakout_20.py):
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
from candlebench.patterns import pattern
|
|
218
|
+
from candlebench.patterns.context import lag, rolling_max
|
|
219
|
+
|
|
220
|
+
@pattern("breakout_20", bias="bull", bars_required=21)
|
|
221
|
+
def breakout_20(geom, thresholds):
|
|
222
|
+
# True on a bar that closes above the highest high of the 20 before it.
|
|
223
|
+
return geom.close > lag(rolling_max(geom.high, 20))
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
`geom` holds one session's bars as numpy arrays, and the detector returns one
|
|
227
|
+
boolean per bar. The trade enters at the next bar's open, with its stop just
|
|
228
|
+
beyond the extreme of the last `bars_required` bars. `bias="bear"` sells instead.
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
candlebench test examples/breakout_20.py --synthetic # offline random walk: no edge can exist
|
|
232
|
+
candlebench test examples/breakout_20.py -v # on the cached market bars
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
The leaderboard is followed by one verdict per signal and timeframe in plain
|
|
236
|
+
words, and a null result says how large an edge it rules out. From the
|
|
237
|
+
synthetic run above:
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
breakout_20 at 1m: NEGATIVE: averages -0.09R a trade after costs over 763 trades on 61
|
|
241
|
+
dates (corrected p 0.001), and +0.00R a trade against its matched random entries.
|
|
242
|
+
breakout_20 at 5m: NOISE: no confirmed edge over 117 trades on 54 dates: -0.01R a trade
|
|
243
|
+
against its matched random entries, -0.10R after costs. An advantage of 0.11R or more
|
|
244
|
+
over random entry would have been caught 80% of the time.
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The 1m line is the comparison doing its job: the breakout loses money, but no
|
|
248
|
+
more than random entry does, so what it loses is the cost of trading, not a
|
|
249
|
+
signal that points the wrong way.
|
|
250
|
+
|
|
251
|
+
Several files can be tested at once, and `--json`, `--csv` and `--html` work as
|
|
252
|
+
for `run`. The dashboard (`serve`, `site`) shows the built-in patterns only.
|
|
253
|
+
|
|
254
|
+
## Engineering
|
|
255
|
+
|
|
256
|
+
- **About 1,400 tests**, including 32 headless-Chromium tests. CI runs
|
|
257
|
+
them on Python 3.11 and 3.12, along with Pylint and a clean-environment wheel
|
|
258
|
+
install.
|
|
259
|
+
- **Every run says what produced it.** Reports carry the git commit, package
|
|
260
|
+
versions, a hash of the effective config, and a content hash of every cached
|
|
261
|
+
file the run read, so two runs that disagree can be told apart.
|
|
262
|
+
- **Browser input is untrusted.** Every field is type-, range- and path-checked
|
|
263
|
+
before it reaches a file path or a DataFrame. Malformed requests get a 400 or
|
|
264
|
+
404 with a reason, never a traceback.
|
|
265
|
+
- **Nothing half-written is ever published.** Bar caches, trade files and run
|
|
266
|
+
reports are written to a temporary file and renamed. A run becomes the latest
|
|
267
|
+
only after its report, trades and archive copy are all saved. An incomplete
|
|
268
|
+
refresh keeps the cached history rather than shortening it.
|
|
269
|
+
- **Unavailable is never zero.** A missing statistic is `None` and is shown as
|
|
270
|
+
`n/a`. A cost that was estimated is never labelled observed.
|
|
271
|
+
- **A C++ trade walk, checked against the Python one.** The signal-to-exit loop
|
|
272
|
+
cannot be vectorised, since whether a signal trades depends on where the last
|
|
273
|
+
trade exited, so it runs in C++ through pybind11 when a compiler is present.
|
|
274
|
+
A parity test holds every trade equal to the Python walk's to the last bit;
|
|
275
|
+
without a compiler the package installs and runs the Python walk.
|
|
276
|
+
- **Standard-library web server, no frontend build.** The dashboard is one
|
|
277
|
+
dependency-free page with hand-drawn SVG charts. CI also builds the demo
|
|
278
|
+
container and checks that it serves read-only.
|
|
279
|
+
|
|
280
|
+
```
|
|
281
|
+
candlebench/
|
|
282
|
+
bars.py alpaca.py ticks.py synthetic.py data sources, validation, Parquet cache
|
|
283
|
+
quotes.py costs.py observed spreads and the fallback estimator
|
|
284
|
+
patterns/ 20 detectors and the random controls
|
|
285
|
+
sampling.py engine.py runner.py paired trials -> trades -> results
|
|
286
|
+
_enginecore.cpp optional C++ trade walk behind engine.py
|
|
287
|
+
metrics.py leaderboard.py clustered bootstrap, Holm, detection limits, verdicts
|
|
288
|
+
report.py site.py shareable HTML report, static interactive export
|
|
289
|
+
web/ loopback server, dashboard, run history
|
|
290
|
+
scripts/null_calibration.py false-positive check on a random walk
|
|
291
|
+
scripts/detection_power.py power curve from planted edges
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Documentation
|
|
295
|
+
|
|
296
|
+
- [Guide](https://github.com/dpologdvinity/candlebench/blob/master/docs/guide.md): commands, the trade and cost models, reading the
|
|
297
|
+
leaderboard, configuration, data sources and historical measurements.
|
|
298
|
+
- Experiment reports: [null calibration](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/null-calibration.md),
|
|
299
|
+
[detection power](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/detection-power.md),
|
|
300
|
+
[matched-control entry window](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/matched-window.md),
|
|
301
|
+
[robustness to clustered volatility and rare patterns](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/robustness.md),
|
|
302
|
+
[walk-forward confirmation](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/walk-forward.md),
|
|
303
|
+
[trade-management sweep](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/trade-management-sweep.md) and
|
|
304
|
+
[stop-buffer sweep](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/stop-buffer-sweep.md).
|
|
305
|
+
- [Data and cost measurements](https://github.com/dpologdvinity/candlebench/blob/master/docs/hft-data-and-costs.md).
|
|
306
|
+
- [`stock.py`](https://github.com/dpologdvinity/candlebench/blob/master/docs/stock-py.md): a separate companion script that scores one
|
|
307
|
+
ticker for long-term, swing and day trading from current fundamentals and
|
|
308
|
+
technicals. It shares no code with candlebench.
|
|
309
|
+
|
|
310
|
+
## Limits
|
|
311
|
+
|
|
312
|
+
This measures 66 textbook patterns under one mechanical trade rule: a stop at
|
|
313
|
+
the pattern's extreme, a fixed reward multiple and a holding cap. It does not
|
|
314
|
+
show that no candlestick-based strategy could work. The universe is 117 liquid
|
|
315
|
+
stocks and ETFs that exist today, so it carries survivorship bias. Quotes are
|
|
316
|
+
sampled, not complete, and the tested history covers two years. Read the
|
|
317
|
+
guide's caveats before relying on any number here.
|
|
318
|
+
|
|
319
|
+
MIT licensed.
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# candlebench
|
|
2
|
+
|
|
3
|
+
[](https://github.com/dpologdvinity/candlebench/actions/workflows/ci.yml)
|
|
4
|
+

|
|
5
|
+
[](https://github.com/dpologdvinity/candlebench/blob/master/LICENSE)
|
|
6
|
+
|
|
7
|
+
**Do classic candlestick patterns have a real intraday edge?** candlebench tests
|
|
8
|
+
66 patterns by turning every signal into a mechanical trade. It charges each
|
|
9
|
+
trade the spread quoted at that time of day, and compares the result with random
|
|
10
|
+
entries taken on the same days. A pattern counts only if it beats that control,
|
|
11
|
+
makes money after costs, and holds up on dates it was not chosen on.
|
|
12
|
+
|
|
13
|
+
The same test runs on any signal you write: see
|
|
14
|
+
[Testing your own signal](#testing-your-own-signal).
|
|
15
|
+
|
|
16
|
+
**Live:** [interactive two-year results](https://dpologdvinity.github.io/candlebench/app/real/) ·
|
|
17
|
+
[interactive synthetic demo](https://dpologdvinity.github.io/candlebench/app/demo/) ·
|
|
18
|
+
[results site](https://dpologdvinity.github.io/candlebench/)
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
## The answer
|
|
23
|
+
|
|
24
|
+
The run covers two years of 1-minute bars (October 2024 to October 2026) for
|
|
25
|
+
117 liquid US stocks and ETFs: 5,000 sampled symbol-days on 501 market dates
|
|
26
|
+
and 1,021,358 pattern trades, charged an average quoted spread of **2.39 bps
|
|
27
|
+
per leg**. Four earlier runs came first (20 patterns on 50 symbols twice, 66 on
|
|
28
|
+
100, and 66 on 117 before the data was brought to one common date range), so
|
|
29
|
+
the multiple-comparison correction counts five experiments.
|
|
30
|
+
|
|
31
|
+
- **No pattern earns `EDGE`.** None of the 66 has positive expectancy after
|
|
32
|
+
costs, and 59 have 95% intervals entirely below zero.
|
|
33
|
+
- **No pattern beats random entry, even before costs.** Compared in gross R
|
|
34
|
+
with its own stop-matched random entries, no row survives multiple-comparison
|
|
35
|
+
correction.
|
|
36
|
+
- **Random entry itself loses about 0.34R per trade at 1m**, almost all of it
|
|
37
|
+
spread. Before costs, the two controls sit at +0.015R and +0.009R.
|
|
38
|
+
- **"No edge" has a size.** Each row reports the smallest advantage its test
|
|
39
|
+
would catch 80% of the time. For the 19 patterns with over 20,000 trades that
|
|
40
|
+
is 0.018R to 0.034R, so an advantage over random entry larger than that would
|
|
41
|
+
very likely have been found. Rare patterns report 0.085R or more; the
|
|
42
|
+
rarest, the abandoned babies, trade too seldom to measure.
|
|
43
|
+
- **The same holds at 5, 15 and 60 minutes.** A second run over the same two
|
|
44
|
+
years and 117 symbols sampled 5,000 symbol-days per timeframe (227,879
|
|
45
|
+
pattern trades, 2.21 bps per leg). No pattern beats its matched controls at
|
|
46
|
+
any of the three. Random entry loses 0.15R to 0.25R net there, less than at
|
|
47
|
+
1m because the spread is a smaller share of a wider stop. At 5m the tweezers
|
|
48
|
+
would reveal an advantage of 0.05R; at 1h, trades are too few to rule out
|
|
49
|
+
less than about 0.2R. [Interactive](https://dpologdvinity.github.io/candlebench/app/intervals/) ·
|
|
50
|
+
[report](https://dpologdvinity.github.io/candlebench/two-year-5m-15m-1h.html).
|
|
51
|
+
|
|
52
|
+
An earlier version of this project reported that eight patterns beat random
|
|
53
|
+
entry on signal alone and lost only to costs. That came from a measurement bias
|
|
54
|
+
the project later found in its own method; the next section explains it.
|
|
55
|
+
|
|
56
|
+
## How it is checked
|
|
57
|
+
|
|
58
|
+
The method has to find nothing where nothing exists. `candlebench.synthetic`
|
|
59
|
+
generates a random walk that no pattern can predict, and
|
|
60
|
+
[a calibration run](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/null-calibration.md) counts how often each
|
|
61
|
+
test passes on it anyway.
|
|
62
|
+
|
|
63
|
+
It exposed two real flaws, and both are fixed:
|
|
64
|
+
|
|
65
|
+
1. **Net R rewards wide stops.** Costs are a fixed number of bps, and R divides
|
|
66
|
+
by the distance to the stop, so a pattern with a wider stop pays fewer R for
|
|
67
|
+
the same spread. Compared with random entry in net R, wide-stop patterns
|
|
68
|
+
"won" with no signal at all: 38 of 800 rows across ten seeds, at least one in
|
|
69
|
+
every run.
|
|
70
|
+
2. **A control with a different stop is not a fair control.** Even before
|
|
71
|
+
costs, a tighter stop is caught more often by the rule that a bar touching
|
|
72
|
+
both levels is a stop. Each pattern trade is now compared with its own
|
|
73
|
+
matched control. The control has the same direction and stop distance and
|
|
74
|
+
the same exit code, and enters on a random bar one to five bars later.
|
|
75
|
+
Entering *earlier* turned out to be a trap: those bars were selected by the
|
|
76
|
+
pattern itself, and a control placed five bars before a tweezer top lost
|
|
77
|
+
0.56R on pure noise.
|
|
78
|
+
|
|
79
|
+
With both fixes, 1 row in 800 passes on the random walk, in 1 run of 10, which
|
|
80
|
+
is what a 5% familywise error rate predicts. Profitability is still tested net
|
|
81
|
+
of costs.
|
|
82
|
+
|
|
83
|
+
Finding nothing in noise is half the check; a method that never finds anything
|
|
84
|
+
passes it too. [The power study](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/detection-power.md) plants
|
|
85
|
+
edges of known size in independent synthetic markets and runs the real-data
|
|
86
|
+
design over them. With all 66 patterns, an advantage of 0.144R was caught
|
|
87
|
+
every time and 0.097R half the time, bracketing the 0.11R limit each row
|
|
88
|
+
reports. It also shows that making money needs a gross edge above about 0.3R, and that
|
|
89
|
+
a single 20% holdout confirms only large edges;
|
|
90
|
+
[walk-forward confirmation](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/walk-forward.md) pools every
|
|
91
|
+
fold's out-of-sample trades and confirms far more.
|
|
92
|
+
|
|
93
|
+

|
|
94
|
+
|
|
95
|
+
Other safeguards:
|
|
96
|
+
|
|
97
|
+
| Concern | What the code does |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| Lucky draws | Every pattern trade is paired with a random-entry control in the same session, with the same direction and stop distance. Matched-rate random-entry rows show what random entry loses at each timeframe. |
|
|
100
|
+
| Correlated trades | Bootstrap intervals resample whole market dates, not individual trades. |
|
|
101
|
+
| Many comparisons | Holm correction across every row and timeframe, plus a declared-experiment multiplier. A run warns when bootstrap resolution makes passing impossible. |
|
|
102
|
+
| Overfitting | The newest 20% of dates are held out. Only discovery candidates are tested on them, and `EDGE` needs both samples. |
|
|
103
|
+
| Flattering fills | Entry is on the next bar. A bar touching both stop and target counts as a stop unless it opened past the target. Gaps fill at the open. |
|
|
104
|
+
| Optimistic costs | The quoted model samples NBBO spreads per symbol and time of day, because the open runs about 4x midday. Borrowed buckets and estimates are reported separately from observed ones. |
|
|
105
|
+
| Tiny samples | A verdict needs at least 30 trades and 10 independent dates; a 3-trade "+0.95R" stays `INSUFFICIENT`. |
|
|
106
|
+
|
|
107
|
+
## Quick start
|
|
108
|
+
|
|
109
|
+
The demo needs no API key or download:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
python -m venv .venv && source .venv/bin/activate
|
|
113
|
+
pip install -e .
|
|
114
|
+
candlebench demo # synthetic market -> full run -> dashboard on :8765
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The demo generates eight symbols of synthetic 1m bars and runs every pattern at
|
|
118
|
+
three timeframes in a few seconds. It then opens the dashboard on that run.
|
|
119
|
+
Every leaderboard row drills down to its equity curve, its per-symbol and
|
|
120
|
+
time-of-day breakdowns, and each session's chart with the trades drawn on it.
|
|
121
|
+
|
|
122
|
+

|
|
123
|
+
|
|
124
|
+
### Sharing a result
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
candlebench report out.json # out.html: one self-contained file, no scripts
|
|
128
|
+
candlebench site out.json --out app/ # the interactive dashboard as static files
|
|
129
|
+
docker build -t candlebench-demo . # or served live and read-only
|
|
130
|
+
docker run --rm -p 8765:8765 candlebench-demo
|
|
131
|
+
fly deploy # the same container on Fly.io (fly.toml)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`candlebench site` exports the full dashboard for one run as plain files, so
|
|
135
|
+
any static host serves it with no server. A browser test sends every query to
|
|
136
|
+
the live server and to the exported copy and requires identical answers.
|
|
137
|
+
A report holds the summary, a leaderboard per timeframe, and a drill-down for
|
|
138
|
+
every pattern. Trades appear only as R multiples and dates, never as prices, so
|
|
139
|
+
a report built from licensed market data can be published. The dashboard can
|
|
140
|
+
download any saved run as a report. `serve --read-only` shows saved runs but
|
|
141
|
+
refuses to start work, and only a read-only server will listen beyond loopback.
|
|
142
|
+
The [results site](https://dpologdvinity.github.io/candlebench/) is rebuilt
|
|
143
|
+
by CI on every push.
|
|
144
|
+
|
|
145
|
+
### With real market data
|
|
146
|
+
|
|
147
|
+
Two years of intraday history come from Alpaca's free market-data API. Yahoo
|
|
148
|
+
Finance needs no key but serves only about a month. Keys are read from the
|
|
149
|
+
environment only, never from a config file or the browser.
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
export ALPACA_API_KEY=... ALPACA_SECRET_KEY=...
|
|
153
|
+
# in config/backtest.toml: source = "alpaca", and model = "quoted" for real spreads
|
|
154
|
+
candlebench fetch # download and cache bars
|
|
155
|
+
candlebench quotes --sessions 4 # sample NBBO spreads into a table
|
|
156
|
+
candlebench run -v --json out.json # terminal leaderboard; trades go to out.parquet
|
|
157
|
+
candlebench serve # dashboard
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
A 200-trial run over two years of 1m bars takes about 20 seconds and peaks under
|
|
161
|
+
1 GiB of memory; the published 5,000-trial run over 117 symbols and 66 patterns
|
|
162
|
+
took 7 to 25 minutes on one shared core and peaked at 4.0 GiB. Its trade file is 40 MB, so
|
|
163
|
+
the published trade files sit on the
|
|
164
|
+
[data release](https://github.com/dpologdvinity/candlebench/releases/tag/data-2026-10-08)
|
|
165
|
+
rather than in git; `docs/results/` keeps the JSON summaries.
|
|
166
|
+
|
|
167
|
+
## Testing your own signal
|
|
168
|
+
|
|
169
|
+
Most backtests compare a strategy with doing nothing, which a strategy can beat
|
|
170
|
+
by accident: the market drifted, the stops were wide, the costs were left out.
|
|
171
|
+
`candlebench test` holds any signal to the comparison the patterns faced. Each
|
|
172
|
+
trade is paired with a random entry on the same session, in the same direction
|
|
173
|
+
and at the same stop distance, so the signal is credited only for choosing
|
|
174
|
+
*when* to enter. It is charged the same costs, corrected for multiple testing,
|
|
175
|
+
and confirmed on held-out dates.
|
|
176
|
+
|
|
177
|
+
A signal is a Python file that registers a detector, as the built-in patterns
|
|
178
|
+
do. This one is [examples/breakout_20.py](https://github.com/dpologdvinity/candlebench/blob/master/examples/breakout_20.py):
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
from candlebench.patterns import pattern
|
|
182
|
+
from candlebench.patterns.context import lag, rolling_max
|
|
183
|
+
|
|
184
|
+
@pattern("breakout_20", bias="bull", bars_required=21)
|
|
185
|
+
def breakout_20(geom, thresholds):
|
|
186
|
+
# True on a bar that closes above the highest high of the 20 before it.
|
|
187
|
+
return geom.close > lag(rolling_max(geom.high, 20))
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`geom` holds one session's bars as numpy arrays, and the detector returns one
|
|
191
|
+
boolean per bar. The trade enters at the next bar's open, with its stop just
|
|
192
|
+
beyond the extreme of the last `bars_required` bars. `bias="bear"` sells instead.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
candlebench test examples/breakout_20.py --synthetic # offline random walk: no edge can exist
|
|
196
|
+
candlebench test examples/breakout_20.py -v # on the cached market bars
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
The leaderboard is followed by one verdict per signal and timeframe in plain
|
|
200
|
+
words, and a null result says how large an edge it rules out. From the
|
|
201
|
+
synthetic run above:
|
|
202
|
+
|
|
203
|
+
```
|
|
204
|
+
breakout_20 at 1m: NEGATIVE: averages -0.09R a trade after costs over 763 trades on 61
|
|
205
|
+
dates (corrected p 0.001), and +0.00R a trade against its matched random entries.
|
|
206
|
+
breakout_20 at 5m: NOISE: no confirmed edge over 117 trades on 54 dates: -0.01R a trade
|
|
207
|
+
against its matched random entries, -0.10R after costs. An advantage of 0.11R or more
|
|
208
|
+
over random entry would have been caught 80% of the time.
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The 1m line is the comparison doing its job: the breakout loses money, but no
|
|
212
|
+
more than random entry does, so what it loses is the cost of trading, not a
|
|
213
|
+
signal that points the wrong way.
|
|
214
|
+
|
|
215
|
+
Several files can be tested at once, and `--json`, `--csv` and `--html` work as
|
|
216
|
+
for `run`. The dashboard (`serve`, `site`) shows the built-in patterns only.
|
|
217
|
+
|
|
218
|
+
## Engineering
|
|
219
|
+
|
|
220
|
+
- **About 1,400 tests**, including 32 headless-Chromium tests. CI runs
|
|
221
|
+
them on Python 3.11 and 3.12, along with Pylint and a clean-environment wheel
|
|
222
|
+
install.
|
|
223
|
+
- **Every run says what produced it.** Reports carry the git commit, package
|
|
224
|
+
versions, a hash of the effective config, and a content hash of every cached
|
|
225
|
+
file the run read, so two runs that disagree can be told apart.
|
|
226
|
+
- **Browser input is untrusted.** Every field is type-, range- and path-checked
|
|
227
|
+
before it reaches a file path or a DataFrame. Malformed requests get a 400 or
|
|
228
|
+
404 with a reason, never a traceback.
|
|
229
|
+
- **Nothing half-written is ever published.** Bar caches, trade files and run
|
|
230
|
+
reports are written to a temporary file and renamed. A run becomes the latest
|
|
231
|
+
only after its report, trades and archive copy are all saved. An incomplete
|
|
232
|
+
refresh keeps the cached history rather than shortening it.
|
|
233
|
+
- **Unavailable is never zero.** A missing statistic is `None` and is shown as
|
|
234
|
+
`n/a`. A cost that was estimated is never labelled observed.
|
|
235
|
+
- **A C++ trade walk, checked against the Python one.** The signal-to-exit loop
|
|
236
|
+
cannot be vectorised, since whether a signal trades depends on where the last
|
|
237
|
+
trade exited, so it runs in C++ through pybind11 when a compiler is present.
|
|
238
|
+
A parity test holds every trade equal to the Python walk's to the last bit;
|
|
239
|
+
without a compiler the package installs and runs the Python walk.
|
|
240
|
+
- **Standard-library web server, no frontend build.** The dashboard is one
|
|
241
|
+
dependency-free page with hand-drawn SVG charts. CI also builds the demo
|
|
242
|
+
container and checks that it serves read-only.
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
candlebench/
|
|
246
|
+
bars.py alpaca.py ticks.py synthetic.py data sources, validation, Parquet cache
|
|
247
|
+
quotes.py costs.py observed spreads and the fallback estimator
|
|
248
|
+
patterns/ 20 detectors and the random controls
|
|
249
|
+
sampling.py engine.py runner.py paired trials -> trades -> results
|
|
250
|
+
_enginecore.cpp optional C++ trade walk behind engine.py
|
|
251
|
+
metrics.py leaderboard.py clustered bootstrap, Holm, detection limits, verdicts
|
|
252
|
+
report.py site.py shareable HTML report, static interactive export
|
|
253
|
+
web/ loopback server, dashboard, run history
|
|
254
|
+
scripts/null_calibration.py false-positive check on a random walk
|
|
255
|
+
scripts/detection_power.py power curve from planted edges
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
## Documentation
|
|
259
|
+
|
|
260
|
+
- [Guide](https://github.com/dpologdvinity/candlebench/blob/master/docs/guide.md): commands, the trade and cost models, reading the
|
|
261
|
+
leaderboard, configuration, data sources and historical measurements.
|
|
262
|
+
- Experiment reports: [null calibration](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/null-calibration.md),
|
|
263
|
+
[detection power](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/detection-power.md),
|
|
264
|
+
[matched-control entry window](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/matched-window.md),
|
|
265
|
+
[robustness to clustered volatility and rare patterns](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/robustness.md),
|
|
266
|
+
[walk-forward confirmation](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/walk-forward.md),
|
|
267
|
+
[trade-management sweep](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/trade-management-sweep.md) and
|
|
268
|
+
[stop-buffer sweep](https://github.com/dpologdvinity/candlebench/blob/master/docs/experiments/stop-buffer-sweep.md).
|
|
269
|
+
- [Data and cost measurements](https://github.com/dpologdvinity/candlebench/blob/master/docs/hft-data-and-costs.md).
|
|
270
|
+
- [`stock.py`](https://github.com/dpologdvinity/candlebench/blob/master/docs/stock-py.md): a separate companion script that scores one
|
|
271
|
+
ticker for long-term, swing and day trading from current fundamentals and
|
|
272
|
+
technicals. It shares no code with candlebench.
|
|
273
|
+
|
|
274
|
+
## Limits
|
|
275
|
+
|
|
276
|
+
This measures 66 textbook patterns under one mechanical trade rule: a stop at
|
|
277
|
+
the pattern's extreme, a fixed reward multiple and a holding cap. It does not
|
|
278
|
+
show that no candlestick-based strategy could work. The universe is 117 liquid
|
|
279
|
+
stocks and ETFs that exist today, so it carries survivorship bias. Quotes are
|
|
280
|
+
sampled, not complete, and the tested history covers two years. Read the
|
|
281
|
+
guide's caveats before relying on any number here.
|
|
282
|
+
|
|
283
|
+
MIT licensed.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""candlebench - measure which candlestick patterns have an intraday edge.
|
|
2
|
+
|
|
3
|
+
The package answers one narrow question: given a pattern signal on an intraday
|
|
4
|
+
chart, and a mechanical risk-managed trade taken on the next bar, does the
|
|
5
|
+
resulting distribution of returns differ from the distribution produced by
|
|
6
|
+
entering at random? Patterns that cannot beat their random-entry control are
|
|
7
|
+
reported as noise rather than ranked as though the difference were real.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
__version__ = "0.9.0"
|