edgeorluck 2.0.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.
- edgeorluck-2.0.0/LICENSE +21 -0
- edgeorluck-2.0.0/PKG-INFO +226 -0
- edgeorluck-2.0.0/README.md +186 -0
- edgeorluck-2.0.0/pyproject.toml +50 -0
- edgeorluck-2.0.0/setup.cfg +4 -0
- edgeorluck-2.0.0/src/edgeorluck/__init__.py +16 -0
- edgeorluck-2.0.0/src/edgeorluck/cli.py +92 -0
- edgeorluck-2.0.0/src/edgeorluck/compare.py +146 -0
- edgeorluck-2.0.0/src/edgeorluck/data/claims.json +184 -0
- edgeorluck-2.0.0/src/edgeorluck/data/validation.json +641 -0
- edgeorluck-2.0.0/src/edgeorluck/evidence.py +325 -0
- edgeorluck-2.0.0/src/edgeorluck/fills.py +195 -0
- edgeorluck-2.0.0/src/edgeorluck/judge.py +1540 -0
- edgeorluck-2.0.0/src/edgeorluck/llms.txt +59 -0
- edgeorluck-2.0.0/src/edgeorluck/mcp_server.py +113 -0
- edgeorluck-2.0.0/src/edgeorluck/readers.py +1062 -0
- edgeorluck-2.0.0/src/edgeorluck/report.py +142 -0
- edgeorluck-2.0.0/src/edgeorluck/result.py +86 -0
- edgeorluck-2.0.0/src/edgeorluck/samples.py +242 -0
- edgeorluck-2.0.0/src/edgeorluck/schema.py +142 -0
- edgeorluck-2.0.0/src/edgeorluck/single.py +142 -0
- edgeorluck-2.0.0/src/edgeorluck/web/__init__.py +1 -0
- edgeorluck-2.0.0/src/edgeorluck/web/app.py +378 -0
- edgeorluck-2.0.0/src/edgeorluck/web/ledger.py +211 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/ibm-plex-sans-LICENSE.txt +93 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/ibm-plex-sans-latin-400-italic.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/ibm-plex-sans-latin-400-normal.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/ibm-plex-sans-latin-500-normal.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/ibm-plex-sans-latin-600-normal.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/ibm-plex-sans-latin-700-normal.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/kalam-LICENSE.txt +93 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/kalam-latin-400-normal.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/fonts/kalam-latin-700-normal.woff2 +0 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/site.css +182 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/assets/site.js +117 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/how.html +294 -0
- edgeorluck-2.0.0/src/edgeorluck/web/static/index.html +1576 -0
- edgeorluck-2.0.0/src/edgeorluck/words.py +122 -0
- edgeorluck-2.0.0/src/edgeorluck.egg-info/PKG-INFO +226 -0
- edgeorluck-2.0.0/src/edgeorluck.egg-info/SOURCES.txt +52 -0
- edgeorluck-2.0.0/src/edgeorluck.egg-info/dependency_links.txt +1 -0
- edgeorluck-2.0.0/src/edgeorluck.egg-info/entry_points.txt +3 -0
- edgeorluck-2.0.0/src/edgeorluck.egg-info/requires.txt +26 -0
- edgeorluck-2.0.0/src/edgeorluck.egg-info/top_level.txt +1 -0
- edgeorluck-2.0.0/tests/test_bridge_anytime.py +232 -0
- edgeorluck-2.0.0/tests/test_bridge_counting.py +500 -0
- edgeorluck-2.0.0/tests/test_bridge_mixture.py +391 -0
- edgeorluck-2.0.0/tests/test_bridge_selection.py +599 -0
- edgeorluck-2.0.0/tests/test_claims.py +100 -0
- edgeorluck-2.0.0/tests/test_judge.py +363 -0
- edgeorluck-2.0.0/tests/test_readers.py +327 -0
- edgeorluck-2.0.0/tests/test_surfaces.py +146 -0
- edgeorluck-2.0.0/tests/test_web.py +241 -0
- edgeorluck-2.0.0/tests/test_web_get.py +64 -0
edgeorluck-2.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 edge or luck
|
|
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,226 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: edgeorluck
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Is a trading strategy's record a real edge, or luck? An honest, reproducible judge for traders and their AI assistants.
|
|
5
|
+
Author: edge or luck
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/Sakke-code/edge-or-luck
|
|
8
|
+
Project-URL: Source, https://github.com/Sakke-code/edge-or-luck
|
|
9
|
+
Keywords: trading,backtest,statistics,e-values,multiple-testing,mcp,overfitting
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: numpy>=1.23
|
|
19
|
+
Requires-Dist: pandas>=1.5
|
|
20
|
+
Provides-Extra: web
|
|
21
|
+
Requires-Dist: flask>=2.2; extra == "web"
|
|
22
|
+
Requires-Dist: waitress>=2.1; extra == "web"
|
|
23
|
+
Provides-Extra: mcp
|
|
24
|
+
Requires-Dist: mcp>=1.2; extra == "mcp"
|
|
25
|
+
Provides-Extra: xlsx
|
|
26
|
+
Requires-Dist: openpyxl>=3.0; extra == "xlsx"
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: flask>=2.2; extra == "all"
|
|
29
|
+
Requires-Dist: waitress>=2.1; extra == "all"
|
|
30
|
+
Requires-Dist: mcp>=1.2; extra == "all"
|
|
31
|
+
Requires-Dist: openpyxl>=3.0; extra == "all"
|
|
32
|
+
Provides-Extra: dev
|
|
33
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
34
|
+
Requires-Dist: jsonschema>=4; extra == "dev"
|
|
35
|
+
Requires-Dist: flask>=2.2; extra == "dev"
|
|
36
|
+
Requires-Dist: waitress>=2.1; extra == "dev"
|
|
37
|
+
Requires-Dist: mcp>=1.2; extra == "dev"
|
|
38
|
+
Requires-Dist: openpyxl>=3.0; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# edge or luck
|
|
42
|
+
|
|
43
|
+
**Is your trading strategy a real edge, or just luck?** Drop in your trades and get an honest answer in plain words: what the record shows, how sure that is, what would change it, and one next step.
|
|
44
|
+
|
|
45
|
+
Most strategies that look good are luck: the best of many tries, a rising market, a backtest that peeked at the future, or a few big trades. This judge checks for each of those. The same file always gets the same answer, and a "real edge" verdict is wrong rarely. That rate is measured, not promised (see [VALIDATION.md](VALIDATION.md)).
|
|
46
|
+
|
|
47
|
+
Open source (MIT). It runs on your machine and keeps nothing. Not financial advice: a statistical check of past trades.
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
$ edgeorluck "BTC breakout.csv" --tried 40
|
|
51
|
+
## Edge or luck: Can't tell yet
|
|
52
|
+
**Can't tell yet: +0.458% per trade so far, but luck often makes records like this when you tried 40 versions.**
|
|
53
|
+
...
|
|
54
|
+
**Next step:** Keep it on paper until about 630 more trades, then check again.
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Install
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install edgeorluck # the judge (numpy + pandas)
|
|
61
|
+
pip install "edgeorluck[mcp]" # + the MCP server for AI assistants
|
|
62
|
+
pip install "edgeorluck[web]" # + the web page and API
|
|
63
|
+
pip install "edgeorluck[xlsx]" # + Excel files
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Until the package is on PyPI, install it straight from GitHub:
|
|
67
|
+
`pip install "edgeorluck[all] @ git+https://github.com/Sakke-code/edge-or-luck"`.
|
|
68
|
+
|
|
69
|
+
## Use it
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
import edgeorluck
|
|
73
|
+
|
|
74
|
+
r = edgeorluck.judge("trades.csv", variants_tried=40, benchmark_return_pct=34.5)
|
|
75
|
+
print(r["verdict"]["headline"]) # one sentence, safe to quote
|
|
76
|
+
print(r.to_markdown()) # the whole answer, ready to paste
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
What it reads (it never crashes on messy input; when it can't judge, it says exactly what it needs):
|
|
80
|
+
|
|
81
|
+
| You have | Give it |
|
|
82
|
+
|---|---|
|
|
83
|
+
| TradingView strategy tester | Strategy Tester, then **List of trades**, then export (CSV or XLSX), old or new column names |
|
|
84
|
+
| An exchange account | the spot **trade history (fills)**: time, pair, side, price, quantity, fee. Paired into round trips first-in-first-out, net of fees |
|
|
85
|
+
| [backtesting.py](https://kernc.github.io/backtesting.py/) | `stats = bt.run(); print(edgeorluck.judge(stats).to_markdown())` (also uses its Buy & Hold return) |
|
|
86
|
+
| [vectorbt](https://vectorbt.dev/) | `edgeorluck.judge(pf)` or `edgeorluck.judge(pf.trades.records_readable)`; each column is a version |
|
|
87
|
+
| [freqtrade](https://www.freqtrade.io/) | `edgeorluck user_data/backtest_results/backtest-result-....json` (or the `.zip`) |
|
|
88
|
+
| A bot's own log | any CSV with a P&L column (money or %), or `edgeorluck.judge([{"pnl": 12.5, "closed_at": "2025-01-03"}, ...])` |
|
|
89
|
+
| Just numbers | `edgeorluck.judge([12.5, -3, 4.1, ...])` or the text `"+12.5%, -3%, 4.1%"` |
|
|
90
|
+
|
|
91
|
+
Exchange fills: a fee in the base coin is valued at the fill price; a fee in a third coin (BNB, BGB) is reported, not guessed. Sells with no earlier buy, and positions still open at the end, are named in the warnings.
|
|
92
|
+
|
|
93
|
+
**About the export formats:** we built the readers from public descriptions of each export's columns (Binance spot trade history old and new, Bitget spot fills and order history, TradingView's list of trades), not from real accounts. If yours fails, please [open an issue](https://github.com/Sakke-code/edge-or-luck/issues) with the header row and a few anonymised rows.
|
|
94
|
+
|
|
95
|
+
### Make the verdict more honest
|
|
96
|
+
|
|
97
|
+
| Parameter | Why it matters |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `variants_tried=N` | If you tried 40 versions and kept the best, one of them looks good by luck alone. The bar rises to match. |
|
|
100
|
+
| `benchmark_return_pct=34.5` | A strategy that only buys looks good whenever prices rise. Did it beat simply holding? (Derived automatically from TradingView and backtesting.py results when possible.) |
|
|
101
|
+
| `fees_included=True/False` | A small edge before fees is often a loss after them. |
|
|
102
|
+
| `source="backtest" / "paper" / "live"` | Backtests are usually better than live trading. |
|
|
103
|
+
| `backtest=...` | Pass the backtest next to the live trades: is live keeping the backtest's promise? |
|
|
104
|
+
|
|
105
|
+
### The command line
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
edgeorluck trades.csv [--tried 50] [--benchmark 34.5] [--fees yes] [--source live] [--json | --md]
|
|
109
|
+
edgeorluck live.csv --backtest backtest.csv
|
|
110
|
+
edgeorluck --schema
|
|
111
|
+
edgeorluck --serve --port 5078 # the web page, served by waitress
|
|
112
|
+
edgeorluck --single-file # write edgeorluck_single.py
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## What you get back
|
|
116
|
+
|
|
117
|
+
A versioned JSON result (`schema_version` 1.0; `edgeorluck.schema()` or `edgeorluck --schema` for the JSON Schema) built so an assistant can answer a non-coder in three sentences without computing anything else:
|
|
118
|
+
|
|
119
|
+
- `verdict`: `code` (`real_edge`, `cant_tell`, `no_edge`, `losing`, `faded`, `too_early`), `headline` (one sentence, safe to quote), `plain` (2-4 short sentences) and `why`.
|
|
120
|
+
- `next_step`: one concrete step about testing, never buy, sell or size advice, with its `why`.
|
|
121
|
+
- `ask_the_user`: the questions whose answers would change the verdict, each with `why` and the parameter to set (`answer_with`).
|
|
122
|
+
- `what_would_change_it`: for example, "About 140 more trades at this average would settle it."
|
|
123
|
+
- `checks`: pass / warn / fail, each with a fix and a plain `why`. Money comes first: when the average percent is positive but the money is negative, it says so.
|
|
124
|
+
- "Is it broken, or just a bad streak?": the odds of the latest stretch if nothing changed, e.g. "a drop that deep within 30 trades happens about 1 in 12 times for this strategy".
|
|
125
|
+
- Red flags for records that look too good (a very high win rate, no losing streaks, a huge profit factor, almost no drawdown, many small wins with rare huge losses) and an `ai_audit_prompt` to paste to the AI that wrote the bot.
|
|
126
|
+
- `numbers`, `compare` (live vs backtest), `input_fingerprint`, `how_we_know` (the method, with references), `limitations`.
|
|
127
|
+
|
|
128
|
+
## Use it from your AI assistant
|
|
129
|
+
|
|
130
|
+
The MCP server runs on your machine, so the assistant can pass the path of your export. Its tool `judge_trades` takes `file_path`, `csv_text` or `pnl`, plus `variants_tried`, `benchmark_return_pct`, `fees_included`, `source`, `unit` and the `backtest_...` fields.
|
|
131
|
+
|
|
132
|
+
**Once the package is on PyPI** (these use [uv](https://docs.astral.sh/uv/)'s `uvx`):
|
|
133
|
+
|
|
134
|
+
Claude Code:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
claude mcp add edgeorluck -- uvx --from "edgeorluck[mcp]" edgeorluck-mcp
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Codex (`~/.codex/config.toml`):
|
|
141
|
+
|
|
142
|
+
```toml
|
|
143
|
+
[mcp_servers.edgeorluck]
|
|
144
|
+
command = "uvx"
|
|
145
|
+
args = ["--from", "edgeorluck[mcp]", "edgeorluck-mcp"]
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Cursor (`.cursor/mcp.json`) and Claude Desktop (`claude_desktop_config.json`):
|
|
149
|
+
|
|
150
|
+
```json
|
|
151
|
+
{
|
|
152
|
+
"mcpServers": {
|
|
153
|
+
"edgeorluck": {
|
|
154
|
+
"command": "uvx",
|
|
155
|
+
"args": ["--from", "edgeorluck[mcp]", "edgeorluck-mcp"]
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Now, from a local clone** (after `pip install -e ".[mcp]"` in a virtual environment), point the same entries at that environment's Python:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
claude mcp add edgeorluck -- /path/to/edge-or-luck/.venv/bin/python -m edgeorluck.mcp_server
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
```toml
|
|
168
|
+
[mcp_servers.edgeorluck]
|
|
169
|
+
command = "/path/to/edge-or-luck/.venv/bin/python"
|
|
170
|
+
args = ["-m", "edgeorluck.mcp_server"]
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
On Windows the Python is `C:\\path\\to\\edge-or-luck\\.venv\\Scripts\\python.exe` (double the backslashes in JSON and TOML).
|
|
174
|
+
|
|
175
|
+
**ChatGPT and other chats without installs:** download `edgeorluck_single.py` (from the web page, or `edgeorluck --single-file`), upload it together with your trades, and paste:
|
|
176
|
+
|
|
177
|
+
> I uploaded edgeorluck_single.py and my trades. Run `python edgeorluck_single.py <my file>` (add `--tried N` if I tell you how many versions I tried). Then answer me from its output only: quote the verdict's headline word for word, explain it in short simple sentences, tell me the next step, and ask me its questions. Don't add your own statistics. It is not financial advice.
|
|
178
|
+
|
|
179
|
+
**One prompt for any AI** (for a trader who can't code):
|
|
180
|
+
|
|
181
|
+
> Before you tell me my strategy is good, check it with edge or luck (https://github.com/Sakke-code/edge-or-luck): run `edgeorluck.judge()` on my trade export, tell me how many versions I tried (ask me if you don't know), quote its headline word for word, and give me its next step. Don't round "can't tell" up to "good".
|
|
182
|
+
|
|
183
|
+
[llms.txt](llms.txt) describes all of this for assistants in one page.
|
|
184
|
+
|
|
185
|
+
## How it judges (the short version)
|
|
186
|
+
|
|
187
|
+
- **Luck is the default.** A record has to beat what luck does with the same trades. The judge flips each trade's sign at random 1,000 times ("coin-flip copies") to show what luck looks like.
|
|
188
|
+
- **You can check any time.** The 95% range is anytime-valid: from 30 trades on (no range is shown before) it stays honest however often you look. A normal confidence interval checked every day is wrong far more often than it says.
|
|
189
|
+
- **Every version you tried counts.** All strategies shown and versions tried form one family, and the bar rises with its size.
|
|
190
|
+
- **The market is taken out**, roughly, when the input shows prices or a buy-and-hold return.
|
|
191
|
+
- **Skewed records need more.** Many small wins with rare big losses must clear a higher bar, and "real edge" needs at least five losing trades.
|
|
192
|
+
|
|
193
|
+
The method, with references, is in `how_we_know` and [VALIDATION.md](VALIDATION.md): Waudby-Smith, Arbour, Sinha, Kennedy & Ramdas (2021) and Howard, Ramdas, McAuliffe & Sekhon (2021) for anytime-valid ranges; Wang & Ramdas (2022) for e-BH; Newey & West (1987) for autocorrelation; Hall (1987) for the skew correction.
|
|
194
|
+
|
|
195
|
+
## How often it's wrong
|
|
196
|
+
|
|
197
|
+
Measured on simulated records whose truth is known ([VALIDATION.md](VALIDATION.md), regenerated by `python tools/validate.py`):
|
|
198
|
+
|
|
199
|
+
- A strategy with **no edge** is called a "real edge" at most 0.8% of the time, across normal, heavy-tailed, autocorrelated, skewed and martingale-shaped trades at 30 to 1,000 trades (target: 2.5%).
|
|
200
|
+
- **Checked every 10 trades** from 30 to 300, a no-edge record is ever called real 0.5% of the time (1.9% when neighbouring trades move together).
|
|
201
|
+
- **The best of 100 versions** of a no-edge strategy passes 8.8% of the time when the judge isn't told, and 0% with `variants_tried=100`.
|
|
202
|
+
- **The price of that honesty:** a small edge (0.2 standard deviations per trade) is found 60% of the time at 300 trades and 99.7% at 1,000. Below that, the judge says "can't tell yet" and how many more trades would settle it.
|
|
203
|
+
- **Same file, same answer:** identical results in a fresh process, every time.
|
|
204
|
+
|
|
205
|
+
## What it can't tell you
|
|
206
|
+
|
|
207
|
+
- What happens next. It judges past trades; markets change.
|
|
208
|
+
- Whether the backtest itself is honest. It can see signs of a bug (records that look too good), not the bug.
|
|
209
|
+
- Anything exact about small samples: the maths is a normal approximation that gets better as trades accumulate.
|
|
210
|
+
- Trades are treated as independent draws. Trades that move together (open at the same time, the same market move) count more than they should. It warns when trades overlap.
|
|
211
|
+
- The buy-and-hold comparison is rough: it assumes the price moved at a steady pace.
|
|
212
|
+
|
|
213
|
+
## The web page
|
|
214
|
+
|
|
215
|
+
`edgeorluck --serve` runs the page and a small JSON API with waitress: `POST /api/judge` (multipart `files`, optional `backtest`, fields `variants_tried`, `benchmark_return_pct`, `fees_included`, `source`, `unit`; or JSON `{sample}` / `{csv_text | pnl | rows, ...}`), `GET /api/schema`, `/api/samples`, `/api/engine`, `/api/track-record`, `/api/validation`, `/llms.txt`, `/healthz`, `/edgeorluck_single.py`. Uploads are read in memory and never stored. The page's own forecasts ("will the next 30 trades end positive?") are kept as one-way fingerprints in a hash-chained ledger (`EDGEORLUCK_DATA_DIR`) and graded when a trader comes back. The library itself writes nothing.
|
|
216
|
+
|
|
217
|
+
## Development
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
|
221
|
+
.venv/bin/python -m pytest
|
|
222
|
+
python tools/validate.py # VALIDATION.md and src/edgeorluck/data/validation.json
|
|
223
|
+
python tools/write_examples.py # docs/examples/*.json
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
License: MIT.
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# edge or luck
|
|
2
|
+
|
|
3
|
+
**Is your trading strategy a real edge, or just luck?** Drop in your trades and get an honest answer in plain words: what the record shows, how sure that is, what would change it, and one next step.
|
|
4
|
+
|
|
5
|
+
Most strategies that look good are luck: the best of many tries, a rising market, a backtest that peeked at the future, or a few big trades. This judge checks for each of those. The same file always gets the same answer, and a "real edge" verdict is wrong rarely. That rate is measured, not promised (see [VALIDATION.md](VALIDATION.md)).
|
|
6
|
+
|
|
7
|
+
Open source (MIT). It runs on your machine and keeps nothing. Not financial advice: a statistical check of past trades.
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
$ edgeorluck "BTC breakout.csv" --tried 40
|
|
11
|
+
## Edge or luck: Can't tell yet
|
|
12
|
+
**Can't tell yet: +0.458% per trade so far, but luck often makes records like this when you tried 40 versions.**
|
|
13
|
+
...
|
|
14
|
+
**Next step:** Keep it on paper until about 630 more trades, then check again.
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install edgeorluck # the judge (numpy + pandas)
|
|
21
|
+
pip install "edgeorluck[mcp]" # + the MCP server for AI assistants
|
|
22
|
+
pip install "edgeorluck[web]" # + the web page and API
|
|
23
|
+
pip install "edgeorluck[xlsx]" # + Excel files
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Until the package is on PyPI, install it straight from GitHub:
|
|
27
|
+
`pip install "edgeorluck[all] @ git+https://github.com/Sakke-code/edge-or-luck"`.
|
|
28
|
+
|
|
29
|
+
## Use it
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import edgeorluck
|
|
33
|
+
|
|
34
|
+
r = edgeorluck.judge("trades.csv", variants_tried=40, benchmark_return_pct=34.5)
|
|
35
|
+
print(r["verdict"]["headline"]) # one sentence, safe to quote
|
|
36
|
+
print(r.to_markdown()) # the whole answer, ready to paste
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
What it reads (it never crashes on messy input; when it can't judge, it says exactly what it needs):
|
|
40
|
+
|
|
41
|
+
| You have | Give it |
|
|
42
|
+
|---|---|
|
|
43
|
+
| TradingView strategy tester | Strategy Tester, then **List of trades**, then export (CSV or XLSX), old or new column names |
|
|
44
|
+
| An exchange account | the spot **trade history (fills)**: time, pair, side, price, quantity, fee. Paired into round trips first-in-first-out, net of fees |
|
|
45
|
+
| [backtesting.py](https://kernc.github.io/backtesting.py/) | `stats = bt.run(); print(edgeorluck.judge(stats).to_markdown())` (also uses its Buy & Hold return) |
|
|
46
|
+
| [vectorbt](https://vectorbt.dev/) | `edgeorluck.judge(pf)` or `edgeorluck.judge(pf.trades.records_readable)`; each column is a version |
|
|
47
|
+
| [freqtrade](https://www.freqtrade.io/) | `edgeorluck user_data/backtest_results/backtest-result-....json` (or the `.zip`) |
|
|
48
|
+
| A bot's own log | any CSV with a P&L column (money or %), or `edgeorluck.judge([{"pnl": 12.5, "closed_at": "2025-01-03"}, ...])` |
|
|
49
|
+
| Just numbers | `edgeorluck.judge([12.5, -3, 4.1, ...])` or the text `"+12.5%, -3%, 4.1%"` |
|
|
50
|
+
|
|
51
|
+
Exchange fills: a fee in the base coin is valued at the fill price; a fee in a third coin (BNB, BGB) is reported, not guessed. Sells with no earlier buy, and positions still open at the end, are named in the warnings.
|
|
52
|
+
|
|
53
|
+
**About the export formats:** we built the readers from public descriptions of each export's columns (Binance spot trade history old and new, Bitget spot fills and order history, TradingView's list of trades), not from real accounts. If yours fails, please [open an issue](https://github.com/Sakke-code/edge-or-luck/issues) with the header row and a few anonymised rows.
|
|
54
|
+
|
|
55
|
+
### Make the verdict more honest
|
|
56
|
+
|
|
57
|
+
| Parameter | Why it matters |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `variants_tried=N` | If you tried 40 versions and kept the best, one of them looks good by luck alone. The bar rises to match. |
|
|
60
|
+
| `benchmark_return_pct=34.5` | A strategy that only buys looks good whenever prices rise. Did it beat simply holding? (Derived automatically from TradingView and backtesting.py results when possible.) |
|
|
61
|
+
| `fees_included=True/False` | A small edge before fees is often a loss after them. |
|
|
62
|
+
| `source="backtest" / "paper" / "live"` | Backtests are usually better than live trading. |
|
|
63
|
+
| `backtest=...` | Pass the backtest next to the live trades: is live keeping the backtest's promise? |
|
|
64
|
+
|
|
65
|
+
### The command line
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
edgeorluck trades.csv [--tried 50] [--benchmark 34.5] [--fees yes] [--source live] [--json | --md]
|
|
69
|
+
edgeorluck live.csv --backtest backtest.csv
|
|
70
|
+
edgeorluck --schema
|
|
71
|
+
edgeorluck --serve --port 5078 # the web page, served by waitress
|
|
72
|
+
edgeorluck --single-file # write edgeorluck_single.py
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## What you get back
|
|
76
|
+
|
|
77
|
+
A versioned JSON result (`schema_version` 1.0; `edgeorluck.schema()` or `edgeorluck --schema` for the JSON Schema) built so an assistant can answer a non-coder in three sentences without computing anything else:
|
|
78
|
+
|
|
79
|
+
- `verdict`: `code` (`real_edge`, `cant_tell`, `no_edge`, `losing`, `faded`, `too_early`), `headline` (one sentence, safe to quote), `plain` (2-4 short sentences) and `why`.
|
|
80
|
+
- `next_step`: one concrete step about testing, never buy, sell or size advice, with its `why`.
|
|
81
|
+
- `ask_the_user`: the questions whose answers would change the verdict, each with `why` and the parameter to set (`answer_with`).
|
|
82
|
+
- `what_would_change_it`: for example, "About 140 more trades at this average would settle it."
|
|
83
|
+
- `checks`: pass / warn / fail, each with a fix and a plain `why`. Money comes first: when the average percent is positive but the money is negative, it says so.
|
|
84
|
+
- "Is it broken, or just a bad streak?": the odds of the latest stretch if nothing changed, e.g. "a drop that deep within 30 trades happens about 1 in 12 times for this strategy".
|
|
85
|
+
- Red flags for records that look too good (a very high win rate, no losing streaks, a huge profit factor, almost no drawdown, many small wins with rare huge losses) and an `ai_audit_prompt` to paste to the AI that wrote the bot.
|
|
86
|
+
- `numbers`, `compare` (live vs backtest), `input_fingerprint`, `how_we_know` (the method, with references), `limitations`.
|
|
87
|
+
|
|
88
|
+
## Use it from your AI assistant
|
|
89
|
+
|
|
90
|
+
The MCP server runs on your machine, so the assistant can pass the path of your export. Its tool `judge_trades` takes `file_path`, `csv_text` or `pnl`, plus `variants_tried`, `benchmark_return_pct`, `fees_included`, `source`, `unit` and the `backtest_...` fields.
|
|
91
|
+
|
|
92
|
+
**Once the package is on PyPI** (these use [uv](https://docs.astral.sh/uv/)'s `uvx`):
|
|
93
|
+
|
|
94
|
+
Claude Code:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
claude mcp add edgeorluck -- uvx --from "edgeorluck[mcp]" edgeorluck-mcp
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Codex (`~/.codex/config.toml`):
|
|
101
|
+
|
|
102
|
+
```toml
|
|
103
|
+
[mcp_servers.edgeorluck]
|
|
104
|
+
command = "uvx"
|
|
105
|
+
args = ["--from", "edgeorluck[mcp]", "edgeorluck-mcp"]
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Cursor (`.cursor/mcp.json`) and Claude Desktop (`claude_desktop_config.json`):
|
|
109
|
+
|
|
110
|
+
```json
|
|
111
|
+
{
|
|
112
|
+
"mcpServers": {
|
|
113
|
+
"edgeorluck": {
|
|
114
|
+
"command": "uvx",
|
|
115
|
+
"args": ["--from", "edgeorluck[mcp]", "edgeorluck-mcp"]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Now, from a local clone** (after `pip install -e ".[mcp]"` in a virtual environment), point the same entries at that environment's Python:
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
claude mcp add edgeorluck -- /path/to/edge-or-luck/.venv/bin/python -m edgeorluck.mcp_server
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```toml
|
|
128
|
+
[mcp_servers.edgeorluck]
|
|
129
|
+
command = "/path/to/edge-or-luck/.venv/bin/python"
|
|
130
|
+
args = ["-m", "edgeorluck.mcp_server"]
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
On Windows the Python is `C:\\path\\to\\edge-or-luck\\.venv\\Scripts\\python.exe` (double the backslashes in JSON and TOML).
|
|
134
|
+
|
|
135
|
+
**ChatGPT and other chats without installs:** download `edgeorluck_single.py` (from the web page, or `edgeorluck --single-file`), upload it together with your trades, and paste:
|
|
136
|
+
|
|
137
|
+
> I uploaded edgeorluck_single.py and my trades. Run `python edgeorluck_single.py <my file>` (add `--tried N` if I tell you how many versions I tried). Then answer me from its output only: quote the verdict's headline word for word, explain it in short simple sentences, tell me the next step, and ask me its questions. Don't add your own statistics. It is not financial advice.
|
|
138
|
+
|
|
139
|
+
**One prompt for any AI** (for a trader who can't code):
|
|
140
|
+
|
|
141
|
+
> Before you tell me my strategy is good, check it with edge or luck (https://github.com/Sakke-code/edge-or-luck): run `edgeorluck.judge()` on my trade export, tell me how many versions I tried (ask me if you don't know), quote its headline word for word, and give me its next step. Don't round "can't tell" up to "good".
|
|
142
|
+
|
|
143
|
+
[llms.txt](llms.txt) describes all of this for assistants in one page.
|
|
144
|
+
|
|
145
|
+
## How it judges (the short version)
|
|
146
|
+
|
|
147
|
+
- **Luck is the default.** A record has to beat what luck does with the same trades. The judge flips each trade's sign at random 1,000 times ("coin-flip copies") to show what luck looks like.
|
|
148
|
+
- **You can check any time.** The 95% range is anytime-valid: from 30 trades on (no range is shown before) it stays honest however often you look. A normal confidence interval checked every day is wrong far more often than it says.
|
|
149
|
+
- **Every version you tried counts.** All strategies shown and versions tried form one family, and the bar rises with its size.
|
|
150
|
+
- **The market is taken out**, roughly, when the input shows prices or a buy-and-hold return.
|
|
151
|
+
- **Skewed records need more.** Many small wins with rare big losses must clear a higher bar, and "real edge" needs at least five losing trades.
|
|
152
|
+
|
|
153
|
+
The method, with references, is in `how_we_know` and [VALIDATION.md](VALIDATION.md): Waudby-Smith, Arbour, Sinha, Kennedy & Ramdas (2021) and Howard, Ramdas, McAuliffe & Sekhon (2021) for anytime-valid ranges; Wang & Ramdas (2022) for e-BH; Newey & West (1987) for autocorrelation; Hall (1987) for the skew correction.
|
|
154
|
+
|
|
155
|
+
## How often it's wrong
|
|
156
|
+
|
|
157
|
+
Measured on simulated records whose truth is known ([VALIDATION.md](VALIDATION.md), regenerated by `python tools/validate.py`):
|
|
158
|
+
|
|
159
|
+
- A strategy with **no edge** is called a "real edge" at most 0.8% of the time, across normal, heavy-tailed, autocorrelated, skewed and martingale-shaped trades at 30 to 1,000 trades (target: 2.5%).
|
|
160
|
+
- **Checked every 10 trades** from 30 to 300, a no-edge record is ever called real 0.5% of the time (1.9% when neighbouring trades move together).
|
|
161
|
+
- **The best of 100 versions** of a no-edge strategy passes 8.8% of the time when the judge isn't told, and 0% with `variants_tried=100`.
|
|
162
|
+
- **The price of that honesty:** a small edge (0.2 standard deviations per trade) is found 60% of the time at 300 trades and 99.7% at 1,000. Below that, the judge says "can't tell yet" and how many more trades would settle it.
|
|
163
|
+
- **Same file, same answer:** identical results in a fresh process, every time.
|
|
164
|
+
|
|
165
|
+
## What it can't tell you
|
|
166
|
+
|
|
167
|
+
- What happens next. It judges past trades; markets change.
|
|
168
|
+
- Whether the backtest itself is honest. It can see signs of a bug (records that look too good), not the bug.
|
|
169
|
+
- Anything exact about small samples: the maths is a normal approximation that gets better as trades accumulate.
|
|
170
|
+
- Trades are treated as independent draws. Trades that move together (open at the same time, the same market move) count more than they should. It warns when trades overlap.
|
|
171
|
+
- The buy-and-hold comparison is rough: it assumes the price moved at a steady pace.
|
|
172
|
+
|
|
173
|
+
## The web page
|
|
174
|
+
|
|
175
|
+
`edgeorluck --serve` runs the page and a small JSON API with waitress: `POST /api/judge` (multipart `files`, optional `backtest`, fields `variants_tried`, `benchmark_return_pct`, `fees_included`, `source`, `unit`; or JSON `{sample}` / `{csv_text | pnl | rows, ...}`), `GET /api/schema`, `/api/samples`, `/api/engine`, `/api/track-record`, `/api/validation`, `/llms.txt`, `/healthz`, `/edgeorluck_single.py`. Uploads are read in memory and never stored. The page's own forecasts ("will the next 30 trades end positive?") are kept as one-way fingerprints in a hash-chained ledger (`EDGEORLUCK_DATA_DIR`) and graded when a trader comes back. The library itself writes nothing.
|
|
176
|
+
|
|
177
|
+
## Development
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
|
|
181
|
+
.venv/bin/python -m pytest
|
|
182
|
+
python tools/validate.py # VALIDATION.md and src/edgeorluck/data/validation.json
|
|
183
|
+
python tools/write_examples.py # docs/examples/*.json
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
License: MIT.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "edgeorluck"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Is a trading strategy's record a real edge, or luck? An honest, reproducible judge for traders and their AI assistants."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
license-files = ["LICENSE"]
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
authors = [{ name = "edge or luck" }]
|
|
14
|
+
keywords = ["trading", "backtest", "statistics", "e-values", "multiple-testing", "mcp", "overfitting"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
19
|
+
"Topic :: Office/Business :: Financial :: Investment",
|
|
20
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
21
|
+
]
|
|
22
|
+
dependencies = ["numpy>=1.23", "pandas>=1.5"]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
web = ["flask>=2.2", "waitress>=2.1"]
|
|
26
|
+
mcp = ["mcp>=1.2"]
|
|
27
|
+
xlsx = ["openpyxl>=3.0"]
|
|
28
|
+
all = ["flask>=2.2", "waitress>=2.1", "mcp>=1.2", "openpyxl>=3.0"]
|
|
29
|
+
dev = ["pytest>=7", "jsonschema>=4", "flask>=2.2", "waitress>=2.1", "mcp>=1.2", "openpyxl>=3.0"]
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
edgeorluck = "edgeorluck.cli:main"
|
|
33
|
+
edgeorluck-mcp = "edgeorluck.mcp_server:main"
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://github.com/Sakke-code/edge-or-luck"
|
|
37
|
+
Source = "https://github.com/Sakke-code/edge-or-luck"
|
|
38
|
+
|
|
39
|
+
[tool.setuptools.dynamic]
|
|
40
|
+
version = { attr = "edgeorluck.judge.ENGINE_VERSION" }
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
where = ["src"]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.package-data]
|
|
46
|
+
edgeorluck = ["llms.txt", "web/static/*", "web/static/assets/*", "web/static/assets/**/*", "data/*.json"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
markers = ["slow: starts subprocesses (MCP over stdio)"]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""edge or luck: is a trading record a real edge, or luck?
|
|
2
|
+
|
|
3
|
+
import edgeorluck
|
|
4
|
+
result = edgeorluck.judge("trades.csv", variants_tried=40)
|
|
5
|
+
print(result["verdict"]["headline"])
|
|
6
|
+
print(result.to_markdown())
|
|
7
|
+
|
|
8
|
+
Pure: reads its input, writes nothing, and the same input always gives the
|
|
9
|
+
same result. Not financial advice: a statistical check of past trades.
|
|
10
|
+
"""
|
|
11
|
+
from .judge import CHANGELOG, ENGINE_VERSION, SCHEMA_VERSION
|
|
12
|
+
from .result import Result, judge
|
|
13
|
+
from .schema import schema
|
|
14
|
+
|
|
15
|
+
__version__ = ENGINE_VERSION
|
|
16
|
+
__all__ = ["judge", "schema", "Result", "ENGINE_VERSION", "SCHEMA_VERSION", "CHANGELOG", "__version__"]
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""The command line.
|
|
2
|
+
|
|
3
|
+
edgeorluck trades.csv [--tried 50] [--benchmark 34.5] [--json | --md]
|
|
4
|
+
edgeorluck live.csv --backtest backtest.csv
|
|
5
|
+
edgeorluck - # read the trades from stdin
|
|
6
|
+
edgeorluck --schema # the result's JSON Schema
|
|
7
|
+
edgeorluck --serve [--port 5078] # the web app (needs edgeorluck[web])
|
|
8
|
+
edgeorluck --single-file out.py # the judge in one file, for chatbots
|
|
9
|
+
|
|
10
|
+
Several files are judged one by one. Exit code 0 when judged, 2 when the
|
|
11
|
+
input needs something (status need_input), 1 on an error.
|
|
12
|
+
"""
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from typing import List, Optional
|
|
19
|
+
|
|
20
|
+
from .judge import ENGINE_VERSION
|
|
21
|
+
from .result import judge
|
|
22
|
+
from .schema import schema
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _parser() -> argparse.ArgumentParser:
|
|
26
|
+
p = argparse.ArgumentParser(prog="edgeorluck",
|
|
27
|
+
description="Is a trading record a real edge, or luck? Give it a trade export "
|
|
28
|
+
"(TradingView list of trades, exchange fills, any CSV with a P&L "
|
|
29
|
+
"column, XLSX, freqtrade JSON) or '-' for stdin.")
|
|
30
|
+
p.add_argument("files", nargs="*", help="trade exports to judge, one by one")
|
|
31
|
+
p.add_argument("--tried", "--variants-tried", dest="tried",
|
|
32
|
+
help="how many versions or settings were tried in all (raises the bar)")
|
|
33
|
+
p.add_argument("--benchmark", help="what buying and holding returned over the same dates, in percent")
|
|
34
|
+
p.add_argument("--fees", help="are fees included? yes or no")
|
|
35
|
+
p.add_argument("--source", help="backtest, paper or live")
|
|
36
|
+
p.add_argument("--unit", help="'%%' or a currency like USDT, when the file doesn't say")
|
|
37
|
+
p.add_argument("--kind", help="auto, trades, fills, tradingview or pnl")
|
|
38
|
+
p.add_argument("--backtest", help="the backtest's trades, to check live trades against")
|
|
39
|
+
out = p.add_mutually_exclusive_group()
|
|
40
|
+
out.add_argument("--json", action="store_true", help="print the JSON result")
|
|
41
|
+
out.add_argument("--md", action="store_true", help="print Markdown (the default)")
|
|
42
|
+
p.add_argument("--schema", action="store_true", help="print the result's JSON Schema and exit")
|
|
43
|
+
p.add_argument("--serve", action="store_true", help="run the web app with waitress")
|
|
44
|
+
p.add_argument("--host", default="127.0.0.1", help="web app host (default 127.0.0.1)")
|
|
45
|
+
p.add_argument("--port", type=int, default=None, help="web app port (default $PORT or 5078)")
|
|
46
|
+
p.add_argument("--single-file", nargs="?", const="edgeorluck_single.py", metavar="PATH",
|
|
47
|
+
help="write the one-file build and exit")
|
|
48
|
+
p.add_argument("--version", action="version", version=f"edgeorluck {ENGINE_VERSION}")
|
|
49
|
+
return p
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main(argv: Optional[List[str]] = None) -> int:
|
|
53
|
+
args = _parser().parse_args(argv)
|
|
54
|
+
if args.schema:
|
|
55
|
+
print(json.dumps(schema(), indent=2))
|
|
56
|
+
return 0
|
|
57
|
+
if args.single_file:
|
|
58
|
+
from .single import write
|
|
59
|
+
print(write(args.single_file))
|
|
60
|
+
return 0
|
|
61
|
+
if args.serve:
|
|
62
|
+
try:
|
|
63
|
+
from .web.app import serve
|
|
64
|
+
except ImportError:
|
|
65
|
+
print("The web app needs: pip install 'edgeorluck[web]'", file=sys.stderr)
|
|
66
|
+
return 1
|
|
67
|
+
serve(host=args.host, port=args.port)
|
|
68
|
+
return 0
|
|
69
|
+
if not args.files:
|
|
70
|
+
_parser().print_help()
|
|
71
|
+
return 2
|
|
72
|
+
worst = 0
|
|
73
|
+
for i, path in enumerate(args.files):
|
|
74
|
+
data = sys.stdin.buffer.read() if path == "-" else path
|
|
75
|
+
result = judge(data, variants_tried=args.tried, benchmark_return_pct=args.benchmark, kind=args.kind,
|
|
76
|
+
unit=args.unit, fees_included=args.fees, source=args.source, backtest=args.backtest)
|
|
77
|
+
if i:
|
|
78
|
+
print()
|
|
79
|
+
text = result.to_json() if args.json else result.to_markdown()
|
|
80
|
+
try:
|
|
81
|
+
print(text)
|
|
82
|
+
except UnicodeEncodeError:
|
|
83
|
+
sys.stdout.buffer.write((text + "\n").encode("utf-8"))
|
|
84
|
+
if result.status == "need_input":
|
|
85
|
+
worst = max(worst, 2)
|
|
86
|
+
elif result.status == "error":
|
|
87
|
+
worst = max(worst, 1) if worst != 2 else worst
|
|
88
|
+
return worst
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if __name__ == "__main__":
|
|
92
|
+
raise SystemExit(main())
|