alphaengine 0.1.0__py3-none-any.whl
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.
- alphaengine/__init__.py +47 -0
- alphaengine/_version.py +15 -0
- alphaengine/core/__init__.py +63 -0
- alphaengine/core/backtest.py +676 -0
- alphaengine/core/factors.py +136 -0
- alphaengine/core/pairs.py +514 -0
- alphaengine/core/performance.py +108 -0
- alphaengine/core/risk.py +127 -0
- alphaengine/core/technical.py +213 -0
- alphaengine/core/validation.py +361 -0
- alphaengine/py.typed +0 -0
- alphaengine/study/__init__.py +32 -0
- alphaengine/study/schema.py +146 -0
- alphaengine/sweep/__init__.py +18 -0
- alphaengine/sweep/runner.py +321 -0
- alphaengine-0.1.0.dist-info/METADATA +157 -0
- alphaengine-0.1.0.dist-info/RECORD +19 -0
- alphaengine-0.1.0.dist-info/WHEEL +4 -0
- alphaengine-0.1.0.dist-info/licenses/LICENSE +202 -0
alphaengine/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""AlphaEngine, validated research tooling for investment strategies.
|
|
2
|
+
|
|
3
|
+
Answers one question honestly: **is this result an edge, or did you try enough
|
|
4
|
+
things that something was bound to look good?**
|
|
5
|
+
|
|
6
|
+
from alphaengine import sweep
|
|
7
|
+
|
|
8
|
+
r = sweep(backtest_fn, grid, data=prices) # runs every combination
|
|
9
|
+
r.surface() # stable plateau, or a knife edge
|
|
10
|
+
r.verdict() # deflated for the trials you ran
|
|
11
|
+
r.save("study.json") # the artifact, on your disk
|
|
12
|
+
|
|
13
|
+
WHY A SWEEP AND NOT A CALCULATOR
|
|
14
|
+
The correction that makes a Sharpe ratio honest needs to know how many
|
|
15
|
+
variants you tested. Ask a person for that number and you get the number
|
|
16
|
+
that flatters them, not from dishonesty, but because nobody counts the
|
|
17
|
+
thing they threw away. Run the grid and the count is `len(grid)`, so the
|
|
18
|
+
question never has to be asked.
|
|
19
|
+
|
|
20
|
+
WHAT THIS LIBRARY IS NOT
|
|
21
|
+
Not a backtester. `sweep()` takes YOUR function. We orchestrate and measure;
|
|
22
|
+
you simulate. The engine you already trust stays the engine you trust.
|
|
23
|
+
|
|
24
|
+
OFFLINE BY CONSTRUCTION
|
|
25
|
+
Importing this module makes no network call and needs no account. numpy and
|
|
26
|
+
scipy, nothing else. Everything above runs on a laptop with the wifi off,
|
|
27
|
+
and your data never leaves the machine.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from ._version import __version__
|
|
31
|
+
from .study import SCHEMA_VERSION, Study, load, save
|
|
32
|
+
from .sweep import SweepResult, sweep
|
|
33
|
+
|
|
34
|
+
# `sweep` is bound here to the FUNCTION, deliberately shadowing the subpackage
|
|
35
|
+
# of the same name. The documented entry point is `from alphaengine import
|
|
36
|
+
# sweep`, and a user who writes that and gets a module back has hit a bug on
|
|
37
|
+
# their first line. `from alphaengine.sweep import ...` still resolves through
|
|
38
|
+
# sys.modules for anyone who wants the module explicitly.
|
|
39
|
+
__all__ = [
|
|
40
|
+
"__version__",
|
|
41
|
+
"sweep",
|
|
42
|
+
"SweepResult",
|
|
43
|
+
"Study",
|
|
44
|
+
"save",
|
|
45
|
+
"load",
|
|
46
|
+
"SCHEMA_VERSION",
|
|
47
|
+
]
|
alphaengine/_version.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Single source of the version.
|
|
2
|
+
|
|
3
|
+
Read by hatchling at build time (see [tool.hatch.version] in pyproject.toml), so
|
|
4
|
+
this file and the published artifact can never disagree.
|
|
5
|
+
|
|
6
|
+
SEMVER, WITH ONE LOCAL RULE: a MAJOR bump is required whenever a computed value
|
|
7
|
+
changes. Once this is on PyPI the numbers are a public contract, somebody's
|
|
8
|
+
saved study has to reproduce in two years, so a different result from the same
|
|
9
|
+
inputs is a breaking change even when the signature is untouched.
|
|
10
|
+
|
|
11
|
+
While the leading digit is 0, the MINOR position carries that rule: 0.1 -> 0.2
|
|
12
|
+
is what a changed figure costs. The API may still move underneath it.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Deterministic quantitative primitives.
|
|
2
|
+
|
|
3
|
+
Pure functions over arrays. No I/O, no network, no global state, no clock, the
|
|
4
|
+
same inputs return the same outputs on every machine, which is the property
|
|
5
|
+
everything else here is built on. numpy and scipy only.
|
|
6
|
+
|
|
7
|
+
These are published methods, not proprietary formulae:
|
|
8
|
+
|
|
9
|
+
probabilistic / deflated Sharpe Bailey & Lopez de Prado (2014)
|
|
10
|
+
PBO via CSCV Bailey, Borwein, Lopez de Prado & Zhu (2017)
|
|
11
|
+
CPCV purged, embargoed cross-validation
|
|
12
|
+
minimum track record length how long a record must be before skill is
|
|
13
|
+
distinguishable from luck
|
|
14
|
+
|
|
15
|
+
That they are published is the point. A referee whose reasoning you cannot
|
|
16
|
+
inspect is not a referee.
|
|
17
|
+
|
|
18
|
+
THE RETURN VALUES ARE A PUBLIC CONTRACT. A study written today has to reproduce
|
|
19
|
+
in two years, so a changed number is a breaking change requiring a major version
|
|
20
|
+
bump even when the signature is untouched. See tests/test_goldens.py.
|
|
21
|
+
|
|
22
|
+
Exports are listed explicitly rather than star-imported: on a published package
|
|
23
|
+
the difference between the API and an implementation detail should be a
|
|
24
|
+
decision, not an accident of which names happen to be module-level.
|
|
25
|
+
|
|
26
|
+
`factors` and `pairs` are NOT imported here. They need statsmodels, which is an
|
|
27
|
+
optional extra, and importing them eagerly would make the core install fail for
|
|
28
|
+
everyone who does not need them:
|
|
29
|
+
|
|
30
|
+
from alphaengine.core.factors import decompose_factors # pip install 'alphaengine[factors]'
|
|
31
|
+
from alphaengine.core.pairs import find_cointegrated_pairs
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from .backtest import run_backtest, score_backtest
|
|
35
|
+
from .performance import performance_report
|
|
36
|
+
from .risk import compute_var_cvar
|
|
37
|
+
from .technical import technical_features
|
|
38
|
+
from .validation import (
|
|
39
|
+
cpcv_score,
|
|
40
|
+
deflated_sharpe,
|
|
41
|
+
expected_max_sharpe,
|
|
42
|
+
min_track_record_length,
|
|
43
|
+
pbo_cscv,
|
|
44
|
+
probabilistic_sharpe_ratio,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
# validation, the honesty layer
|
|
49
|
+
"deflated_sharpe",
|
|
50
|
+
"probabilistic_sharpe_ratio",
|
|
51
|
+
"expected_max_sharpe",
|
|
52
|
+
"min_track_record_length",
|
|
53
|
+
"pbo_cscv",
|
|
54
|
+
"cpcv_score",
|
|
55
|
+
# performance and risk
|
|
56
|
+
"performance_report",
|
|
57
|
+
"compute_var_cvar",
|
|
58
|
+
# simulation and scoring
|
|
59
|
+
"run_backtest",
|
|
60
|
+
"score_backtest",
|
|
61
|
+
# features
|
|
62
|
+
"technical_features",
|
|
63
|
+
]
|