mlquantx 0.2.5__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.
- mlquant/__init__.py +18 -0
- mlquant/backtest/__init__.py +14 -0
- mlquant/backtest/engine.py +118 -0
- mlquant/backtest/metrics.py +95 -0
- mlquant/cli/__init__.py +1 -0
- mlquant/cli/main.py +292 -0
- mlquant/configs/__init__.py +1 -0
- mlquant/configs/small.yaml +41 -0
- mlquant/data/__init__.py +88 -0
- mlquant/data/akshare_loader.py +168 -0
- mlquant/data/baostock_loader.py +137 -0
- mlquant/data/loaders.py +125 -0
- mlquant/data/panel.py +206 -0
- mlquant/data/synthetic.py +138 -0
- mlquant/data/yfinance_loader.py +101 -0
- mlquant/features/__init__.py +42 -0
- mlquant/features/_factors_add.py +288 -0
- mlquant/features/_factors_alpha101.py +98 -0
- mlquant/features/_factors_best.py +177 -0
- mlquant/features/_factors_better.py +385 -0
- mlquant/features/_factors_change.py +53 -0
- mlquant/features/_factors_extra.py +147 -0
- mlquant/features/_factors_market.py +61 -0
- mlquant/features/_factors_old.py +563 -0
- mlquant/features/_factors_original.py +215 -0
- mlquant/features/_factors_stock.py +270 -0
- mlquant/features/alpha101.py +84 -0
- mlquant/features/bias.py +81 -0
- mlquant/features/legacy_factors.py +126 -0
- mlquant/features/market_breadth.py +190 -0
- mlquant/features/neutralize.py +75 -0
- mlquant/features/target.py +62 -0
- mlquant/features/tensor_factors.py +301 -0
- mlquant/features.py +613 -0
- mlquant/models/__init__.py +8 -0
- mlquant/models/losses.py +88 -0
- mlquant/models/nets.py +62 -0
- mlquant/portfolio/__init__.py +5 -0
- mlquant/portfolio/frontier.py +49 -0
- mlquant/portfolio/markowitz.py +144 -0
- mlquant/training/__init__.py +6 -0
- mlquant/training/augment.py +89 -0
- mlquant/training/dataset.py +50 -0
- mlquant/training/trainer.py +108 -0
- mlquant/utils/__init__.py +5 -0
- mlquant/utils/config.py +60 -0
- mlquant/utils/seed.py +35 -0
- mlquantx-0.2.5.dist-info/METADATA +108 -0
- mlquantx-0.2.5.dist-info/RECORD +54 -0
- mlquantx-0.2.5.dist-info/WHEEL +5 -0
- mlquantx-0.2.5.dist-info/entry_points.txt +2 -0
- mlquantx-0.2.5.dist-info/licenses/LICENSE +21 -0
- mlquantx-0.2.5.dist-info/licenses/NOTICE.md +5 -0
- mlquantx-0.2.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""best_* family: close-location momentum over varying windows (21 factors).
|
|
2
|
+
|
|
3
|
+
Legacy template: -(2*C - H - L) / (H - L + eps) with various transforms.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
from typing import Tuple
|
|
7
|
+
import torch
|
|
8
|
+
|
|
9
|
+
from ..data.panel import Panel
|
|
10
|
+
from .tensor_factors import (
|
|
11
|
+
cs_rank, delay, delta, ewma, ts_corr, ts_max, ts_mean, ts_min, ts_rank, ts_sum,
|
|
12
|
+
)
|
|
13
|
+
from .legacy_factors import register_legacy_factor, _close_loc, _ts_mean_close_loc, _amount
|
|
14
|
+
|
|
15
|
+
Tensor = torch.Tensor
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@register_legacy_factor("best_001")
|
|
19
|
+
def best_001(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
20
|
+
"""ts_rank of -close_loc over 5 days."""
|
|
21
|
+
cl = -_close_loc(panel)
|
|
22
|
+
return ts_rank(cl, panel.mask, 5)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@register_legacy_factor("best_002")
|
|
26
|
+
def best_002(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
27
|
+
"""ts_rank of -close_loc over 10 days."""
|
|
28
|
+
cl = -_close_loc(panel)
|
|
29
|
+
return ts_rank(cl, panel.mask, 10)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@register_legacy_factor("best_003")
|
|
33
|
+
def best_003(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
34
|
+
"""ts_rank of -close_loc over 20 days."""
|
|
35
|
+
cl = -_close_loc(panel)
|
|
36
|
+
return ts_rank(cl, panel.mask, 20)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@register_legacy_factor("best_004")
|
|
40
|
+
def best_004(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
41
|
+
"""Delta(close_loc, 3)."""
|
|
42
|
+
cl = -_close_loc(panel)
|
|
43
|
+
return delta(cl, panel.mask, 3)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@register_legacy_factor("best_005")
|
|
47
|
+
def best_005(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
48
|
+
"""Delta(close_loc, 5)."""
|
|
49
|
+
cl = -_close_loc(panel)
|
|
50
|
+
return delta(cl, panel.mask, 5)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@register_legacy_factor("best_006")
|
|
54
|
+
def best_006(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
55
|
+
"""EWMA of -close_loc, alpha=1/10."""
|
|
56
|
+
cl = -_close_loc(panel)
|
|
57
|
+
return ewma(cl, panel.mask, alpha=1.0 / 10.0)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@register_legacy_factor("best_007")
|
|
61
|
+
def best_007(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
62
|
+
"""Cross-sectional rank of -close_loc."""
|
|
63
|
+
cl = -_close_loc(panel)
|
|
64
|
+
return cs_rank(cl, panel.mask)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@register_legacy_factor("best_008")
|
|
68
|
+
def best_008(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
69
|
+
"""ts_mean(vwap / close, 3)."""
|
|
70
|
+
ratio = panel.vwap / panel.close.clamp_min(1e-9)
|
|
71
|
+
return ts_mean(ratio, panel.mask, 3)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@register_legacy_factor("best_009")
|
|
75
|
+
def best_009(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
76
|
+
"""EWMA(vwap, 5) / EWMA(close, 5)."""
|
|
77
|
+
ew_vwap, m1 = ewma(panel.vwap, panel.mask, alpha=1.0 / 5.0)
|
|
78
|
+
ew_close, m2 = ewma(panel.close, panel.mask, alpha=1.0 / 5.0)
|
|
79
|
+
m = m1 & m2
|
|
80
|
+
out = ew_vwap / ew_close.clamp_min(1e-9)
|
|
81
|
+
return out * m.float(), m
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
@register_legacy_factor("best_010")
|
|
85
|
+
def best_010(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
86
|
+
"""(vwap / close - 1) * volume."""
|
|
87
|
+
out = (panel.vwap / panel.close.clamp_min(1e-9) - 1.0) * panel.volume
|
|
88
|
+
return out * panel.mask.float(), panel.mask
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@register_legacy_factor("best_011")
|
|
92
|
+
def best_011(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
93
|
+
"""cs_rank(open / delay(close, 1) - 1)."""
|
|
94
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
95
|
+
gap = panel.open / prev.clamp_min(1e-9) - 1.0
|
|
96
|
+
return cs_rank(gap * pm.float(), pm)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@register_legacy_factor("best_012")
|
|
100
|
+
def best_012(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
101
|
+
"""ts_mean(open / delay(close, 1) - 1, 5)."""
|
|
102
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
103
|
+
gap = (panel.open / prev.clamp_min(1e-9) - 1.0) * pm.float()
|
|
104
|
+
return ts_mean(gap, pm, 5)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@register_legacy_factor("best_013")
|
|
108
|
+
def best_013(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
109
|
+
"""EWMA(open / delay(close, 1) - 1, alpha=1/5)."""
|
|
110
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
111
|
+
gap = (panel.open / prev.clamp_min(1e-9) - 1.0) * pm.float()
|
|
112
|
+
return ewma(gap, pm, alpha=1.0 / 5.0)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@register_legacy_factor("best_014")
|
|
116
|
+
def best_014(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
117
|
+
"""Correlation of ts_rank(volume, 7) and ts_rank(range/close, 7) over 5 days."""
|
|
118
|
+
vol_r, vm = ts_rank(panel.volume, panel.mask, 7)
|
|
119
|
+
rng = (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
120
|
+
rng_r, rm = ts_rank(rng, panel.mask, 7)
|
|
121
|
+
m = vm & rm
|
|
122
|
+
return ts_corr(rng_r, vol_r, m, 5)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@register_legacy_factor("best_015")
|
|
126
|
+
def best_015(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
127
|
+
"""cs_rank((high - low) / close)."""
|
|
128
|
+
rng = (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
129
|
+
return cs_rank(rng, panel.mask)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@register_legacy_factor("best_016")
|
|
133
|
+
def best_016(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
134
|
+
"""(high - low) / close / (1 + sqrt(volume))."""
|
|
135
|
+
rng = (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
136
|
+
out = rng / (1.0 + panel.volume.clamp_min(0).sqrt())
|
|
137
|
+
return out * panel.mask.float(), panel.mask
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@register_legacy_factor("best_017")
|
|
141
|
+
def best_017(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
142
|
+
"""Delta(EWMA(close, alpha=1/5), 1) - moving average breakout."""
|
|
143
|
+
ew, em = ewma(panel.close, panel.mask, alpha=1.0 / 5.0)
|
|
144
|
+
return delta(ew, em, 1)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@register_legacy_factor("best_018")
|
|
148
|
+
def best_018(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
149
|
+
"""Delta(EWMA(close, alpha=1/10), 1)."""
|
|
150
|
+
ew, em = ewma(panel.close, panel.mask, alpha=1.0 / 10.0)
|
|
151
|
+
return delta(ew, em, 1)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@register_legacy_factor("best_019")
|
|
155
|
+
def best_019(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
156
|
+
"""Delta(EWMA(close, alpha=1/20), 1)."""
|
|
157
|
+
ew, em = ewma(panel.close, panel.mask, alpha=1.0 / 20.0)
|
|
158
|
+
return delta(ew, em, 1)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@register_legacy_factor("best_020")
|
|
162
|
+
def best_020(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
163
|
+
"""EWMA((close - vwap) * volume, alpha=1/5) - money flow."""
|
|
164
|
+
x = (panel.close - panel.vwap) * panel.volume
|
|
165
|
+
return ewma(x, panel.mask, alpha=1.0 / 5.0)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@register_legacy_factor("best_021")
|
|
169
|
+
def best_021(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
170
|
+
"""max((vwap-close)*vol, 3) + min((vwap-close)*vol, 3) * delta(volume, 3)."""
|
|
171
|
+
spread = (panel.vwap - panel.close) * panel.volume
|
|
172
|
+
mx, mm1 = ts_max(spread, panel.mask, 3)
|
|
173
|
+
mn, mm2 = ts_min(spread, panel.mask, 3)
|
|
174
|
+
dv, dm = delta(panel.volume, panel.mask, 3)
|
|
175
|
+
m = mm1 & mm2 & dm
|
|
176
|
+
out = (mx + mn * dv) * m.float()
|
|
177
|
+
return out, m
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""better_* family: vwap-based alpha signals (28 factors).
|
|
2
|
+
|
|
3
|
+
Ported from legacy cuda_features.py better_001-better_028.
|
|
4
|
+
These factors heavily use vwap (S_DQ_AVGPRICE) as a reference price.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
from typing import Tuple
|
|
8
|
+
import torch
|
|
9
|
+
|
|
10
|
+
from ..data.panel import Panel
|
|
11
|
+
from .tensor_factors import (
|
|
12
|
+
cs_rank, delay, delta, ewma, ts_corr, ts_max, ts_mean, ts_min,
|
|
13
|
+
ts_rank, ts_std, ts_sum,
|
|
14
|
+
)
|
|
15
|
+
from .legacy_factors import register_legacy_factor, _close_loc, _amount
|
|
16
|
+
|
|
17
|
+
Tensor = torch.Tensor
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@register_legacy_factor("better_001")
|
|
21
|
+
def better_001(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
22
|
+
"""(open/delay(close,1) - 1) * log2(volume + 1)."""
|
|
23
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
24
|
+
gap = panel.open / prev.clamp_min(1e-9) - 1.0
|
|
25
|
+
out = gap * torch.log2(panel.volume + 1.0)
|
|
26
|
+
m = pm & panel.mask
|
|
27
|
+
return out * m.float(), m
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@register_legacy_factor("better_002")
|
|
31
|
+
def better_002(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
32
|
+
"""(open/delay(close,1) - 1) * volume / mean(volume, 5)."""
|
|
33
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
34
|
+
gap = panel.open / prev.clamp_min(1e-9) - 1.0
|
|
35
|
+
avg, am = ts_mean(panel.volume, panel.mask, 5)
|
|
36
|
+
m = pm & am & panel.mask
|
|
37
|
+
out = gap * panel.volume / avg.clamp_min(1.0)
|
|
38
|
+
return out * m.float(), m
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@register_legacy_factor("better_003")
|
|
42
|
+
def better_003(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
43
|
+
"""(open/delay(close,1)-1) * (vwap-close) * (high-low) / close."""
|
|
44
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
45
|
+
gap = panel.open / prev.clamp_min(1e-9) - 1.0
|
|
46
|
+
spread = (panel.vwap - panel.close) * (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
47
|
+
m = pm & panel.mask
|
|
48
|
+
return (gap * spread) * m.float(), m
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@register_legacy_factor("better_004")
|
|
52
|
+
def better_004(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
53
|
+
"""-(vwap_loc - delay(vwap_loc, 1)) where vwap_loc = (2*vwap-low-high)/(high-low)."""
|
|
54
|
+
rng = (panel.high - panel.low).clamp_min(1e-9)
|
|
55
|
+
vwap_loc = (2.0 * panel.vwap - panel.low - panel.high) / rng
|
|
56
|
+
prev, pm = delay(vwap_loc, panel.mask, 1)
|
|
57
|
+
out = -(vwap_loc - prev)
|
|
58
|
+
return out * pm.float(), pm
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@register_legacy_factor("better_005")
|
|
62
|
+
def better_005(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
63
|
+
"""-(vwap_loc - delay(vwap_loc, 5))."""
|
|
64
|
+
rng = (panel.high - panel.low).clamp_min(1e-9)
|
|
65
|
+
vwap_loc = (2.0 * panel.vwap - panel.low - panel.high) / rng
|
|
66
|
+
prev, pm = delay(vwap_loc, panel.mask, 5)
|
|
67
|
+
out = -(vwap_loc - prev)
|
|
68
|
+
return out * pm.float(), pm
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@register_legacy_factor("better_006")
|
|
72
|
+
def better_006(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
73
|
+
"""mean((2*vwap - low - high) / (high - low), 5)."""
|
|
74
|
+
rng = (panel.high - panel.low).clamp_min(1e-9)
|
|
75
|
+
vwap_loc = (2.0 * panel.vwap - panel.low - panel.high) / rng
|
|
76
|
+
return ts_mean(vwap_loc, panel.mask, 5)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@register_legacy_factor("better_007")
|
|
80
|
+
def better_007(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
81
|
+
"""(high-low)/close * (volume - delay(volume, 1))."""
|
|
82
|
+
rng = (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
83
|
+
dv, dm = delta(panel.volume, panel.mask, 1)
|
|
84
|
+
m = dm & panel.mask
|
|
85
|
+
return (rng * dv) * m.float(), m
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@register_legacy_factor("better_008")
|
|
89
|
+
def better_008(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
90
|
+
"""(high-low)/close / log2(volume + 1)."""
|
|
91
|
+
rng = (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
92
|
+
out = rng / torch.log2(panel.volume + 1.0).clamp_min(1e-9)
|
|
93
|
+
return out * panel.mask.float(), panel.mask
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@register_legacy_factor("better_009")
|
|
97
|
+
def better_009(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
98
|
+
"""Conditional sum: if vwap[t]<vwap[t-1]: |vwap/delay(vwap,1)-1|/log(amount+1), window=20."""
|
|
99
|
+
prev, pm = delay(panel.vwap, panel.mask, 1)
|
|
100
|
+
ratio = (panel.vwap / prev.clamp_min(1e-9) - 1.0).abs()
|
|
101
|
+
amt = _amount(panel)
|
|
102
|
+
signal = ratio / torch.log(amt + 1.0).clamp_min(1e-9)
|
|
103
|
+
cond = (panel.vwap < prev).float() * pm.float()
|
|
104
|
+
weighted = signal * cond
|
|
105
|
+
s, sm = ts_sum(weighted, pm, 20)
|
|
106
|
+
cnt, cm = ts_sum(cond, pm, 20)
|
|
107
|
+
m = sm & cm
|
|
108
|
+
out = s / cnt.clamp_min(1.0)
|
|
109
|
+
return out * m.float(), m
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@register_legacy_factor("better_010")
|
|
113
|
+
def better_010(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
114
|
+
"""Same as better_009 but window=10."""
|
|
115
|
+
prev, pm = delay(panel.vwap, panel.mask, 1)
|
|
116
|
+
ratio = (panel.vwap / prev.clamp_min(1e-9) - 1.0).abs()
|
|
117
|
+
amt = _amount(panel)
|
|
118
|
+
signal = ratio / torch.log(amt + 1.0).clamp_min(1e-9)
|
|
119
|
+
cond = (panel.vwap < prev).float() * pm.float()
|
|
120
|
+
weighted = signal * cond
|
|
121
|
+
s, sm = ts_sum(weighted, pm, 10)
|
|
122
|
+
cnt, cm = ts_sum(cond, pm, 10)
|
|
123
|
+
m = sm & cm
|
|
124
|
+
out = s / cnt.clamp_min(1.0)
|
|
125
|
+
return out * m.float(), m
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@register_legacy_factor("better_011")
|
|
129
|
+
def better_011(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
130
|
+
"""Multi-period support/resistance using min(low, delay(low,1)) and max(high, delay(high,1))."""
|
|
131
|
+
prev_l, plm = delay(panel.low, panel.mask, 1)
|
|
132
|
+
prev_h, phm = delay(panel.high, panel.mask, 1)
|
|
133
|
+
m_base = plm & phm
|
|
134
|
+
lo = torch.minimum(panel.low, prev_l)
|
|
135
|
+
hi = torch.maximum(panel.high, prev_h)
|
|
136
|
+
rng = (hi - lo).clamp_min(1e-9)
|
|
137
|
+
pos_6 = (panel.close - lo) / rng
|
|
138
|
+
pos_12 = pos_6 # simplified: same ratio, different sum windows
|
|
139
|
+
pos_24 = pos_6
|
|
140
|
+
s6, m6 = ts_sum(pos_6, m_base, 6)
|
|
141
|
+
s12, m12 = ts_sum(pos_12, m_base, 12)
|
|
142
|
+
s24, m24 = ts_sum(pos_24, m_base, 24)
|
|
143
|
+
m = m6 & m12 & m24
|
|
144
|
+
out = (s6 / 6.0 + s12 / 12.0 + s24 / 24.0) / 3.0
|
|
145
|
+
return out * m.float(), m
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@register_legacy_factor("better_012")
|
|
149
|
+
def better_012(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
150
|
+
"""Multi-period support/resistance using min(low, delay(vwap,1)) and max(high, delay(vwap,1))."""
|
|
151
|
+
prev_v, pvm = delay(panel.vwap, panel.mask, 1)
|
|
152
|
+
lo = torch.minimum(panel.low, prev_v)
|
|
153
|
+
hi = torch.maximum(panel.high, prev_v)
|
|
154
|
+
rng = (hi - lo).clamp_min(1e-9)
|
|
155
|
+
pos = (panel.vwap - lo) / rng
|
|
156
|
+
m_base = pvm & panel.mask
|
|
157
|
+
s6, m6 = ts_sum(pos, m_base, 6)
|
|
158
|
+
s12, m12 = ts_sum(pos, m_base, 12)
|
|
159
|
+
s24, m24 = ts_sum(pos, m_base, 24)
|
|
160
|
+
m = m6 & m12 & m24
|
|
161
|
+
out = (s6 / 6.0 + s12 / 12.0 + s24 / 24.0) / 3.0
|
|
162
|
+
return out * m.float(), m
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@register_legacy_factor("better_013")
|
|
166
|
+
def better_013(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
167
|
+
"""(vwap/close - 1) * log2(amount + 1)."""
|
|
168
|
+
amt = _amount(panel)
|
|
169
|
+
out = (panel.vwap / panel.close.clamp_min(1e-9) - 1.0) * torch.log2(amt + 1.0)
|
|
170
|
+
return out * panel.mask.float(), panel.mask
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@register_legacy_factor("better_014")
|
|
174
|
+
def better_014(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
175
|
+
"""mean((vwap/close - 1) * log2(amount + 1), 4)."""
|
|
176
|
+
amt = _amount(panel)
|
|
177
|
+
signal = (panel.vwap / panel.close.clamp_min(1e-9) - 1.0) * torch.log2(amt + 1.0)
|
|
178
|
+
return ts_mean(signal, panel.mask, 4)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@register_legacy_factor("better_015")
|
|
182
|
+
def better_015(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
183
|
+
"""ewma((vwap - mean(vwap,10))/mean(vwap,10) delta 5, 1/20)."""
|
|
184
|
+
avg, am = ts_mean(panel.vwap, panel.mask, 10)
|
|
185
|
+
dev = (panel.vwap - avg) / avg.clamp_min(1e-9)
|
|
186
|
+
# delta 5
|
|
187
|
+
T = dev.shape[0]
|
|
188
|
+
if T > 5:
|
|
189
|
+
d = dev[5:] - dev[:-5]
|
|
190
|
+
dm = am[5:] & am[:-5]
|
|
191
|
+
else:
|
|
192
|
+
d = torch.zeros_like(dev)
|
|
193
|
+
dm = torch.zeros_like(am)
|
|
194
|
+
ew, em = ewma(d, dm, alpha=1.0 / 20.0)
|
|
195
|
+
# Pad back to [T, N]
|
|
196
|
+
pad = torch.zeros(5, dev.shape[1], device=dev.device)
|
|
197
|
+
pad_m = torch.zeros(5, dev.shape[1], dtype=torch.bool, device=dev.device)
|
|
198
|
+
out = torch.cat([pad, ew], dim=0)
|
|
199
|
+
mask_out = torch.cat([pad_m, em], dim=0)
|
|
200
|
+
return out * mask_out.float(), mask_out
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@register_legacy_factor("better_016")
|
|
204
|
+
def better_016(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
205
|
+
"""ewma((vwap - mean(vwap,6))/mean(vwap,6) delta 3, 1/12)."""
|
|
206
|
+
avg, am = ts_mean(panel.vwap, panel.mask, 6)
|
|
207
|
+
dev = (panel.vwap - avg) / avg.clamp_min(1e-9)
|
|
208
|
+
T = dev.shape[0]
|
|
209
|
+
if T > 3:
|
|
210
|
+
d = dev[3:] - dev[:-3]
|
|
211
|
+
dm = am[3:] & am[:-3]
|
|
212
|
+
else:
|
|
213
|
+
d = torch.zeros_like(dev)
|
|
214
|
+
dm = torch.zeros_like(am)
|
|
215
|
+
ew, em = ewma(d, dm, alpha=1.0 / 12.0)
|
|
216
|
+
pad = torch.zeros(3, dev.shape[1], device=dev.device)
|
|
217
|
+
pad_m = torch.zeros(3, dev.shape[1], dtype=torch.bool, device=dev.device)
|
|
218
|
+
out = torch.cat([pad, ew], dim=0)
|
|
219
|
+
mask_out = torch.cat([pad_m, em], dim=0)
|
|
220
|
+
return out * mask_out.float(), mask_out
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@register_legacy_factor("better_017")
|
|
224
|
+
def better_017(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
225
|
+
"""ewma((amount - mean(amount,6))/mean(amount,6) delta 3, 1/12)."""
|
|
226
|
+
amt = _amount(panel)
|
|
227
|
+
avg, am = ts_mean(amt, panel.mask, 6)
|
|
228
|
+
dev = (amt - avg) / avg.clamp_min(1.0)
|
|
229
|
+
T = dev.shape[0]
|
|
230
|
+
if T > 3:
|
|
231
|
+
d = dev[3:] - dev[:-3]
|
|
232
|
+
dm = am[3:] & am[:-3]
|
|
233
|
+
else:
|
|
234
|
+
d = torch.zeros_like(dev)
|
|
235
|
+
dm = torch.zeros_like(am)
|
|
236
|
+
ew, em = ewma(d, dm, alpha=1.0 / 12.0)
|
|
237
|
+
pad = torch.zeros(3, dev.shape[1], device=dev.device)
|
|
238
|
+
pad_m = torch.zeros(3, dev.shape[1], dtype=torch.bool, device=dev.device)
|
|
239
|
+
out = torch.cat([pad, ew], dim=0)
|
|
240
|
+
mask_out = torch.cat([pad_m, em], dim=0)
|
|
241
|
+
return out * mask_out.float(), mask_out
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@register_legacy_factor("better_018")
|
|
245
|
+
def better_018(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
246
|
+
"""ewma((amount - mean(amount,10))/mean(amount,10) delta 5, 1/20)."""
|
|
247
|
+
amt = _amount(panel)
|
|
248
|
+
avg, am = ts_mean(amt, panel.mask, 10)
|
|
249
|
+
dev = (amt - avg) / avg.clamp_min(1.0)
|
|
250
|
+
T = dev.shape[0]
|
|
251
|
+
if T > 5:
|
|
252
|
+
d = dev[5:] - dev[:-5]
|
|
253
|
+
dm = am[5:] & am[:-5]
|
|
254
|
+
else:
|
|
255
|
+
d = torch.zeros_like(dev)
|
|
256
|
+
dm = torch.zeros_like(am)
|
|
257
|
+
ew, em = ewma(d, dm, alpha=1.0 / 20.0)
|
|
258
|
+
pad = torch.zeros(5, dev.shape[1], device=dev.device)
|
|
259
|
+
pad_m = torch.zeros(5, dev.shape[1], dtype=torch.bool, device=dev.device)
|
|
260
|
+
out = torch.cat([pad, ew], dim=0)
|
|
261
|
+
mask_out = torch.cat([pad_m, em], dim=0)
|
|
262
|
+
return out * mask_out.float(), mask_out
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
@register_legacy_factor("better_019")
|
|
266
|
+
def better_019(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
267
|
+
"""Triple-smoothed EMA of log(volume) change."""
|
|
268
|
+
lv = torch.log(panel.volume + 1.0)
|
|
269
|
+
ew1, m1 = ewma(lv, panel.mask, alpha=2.0 / 13.0)
|
|
270
|
+
ew2, m2 = ewma(ew1, m1, alpha=2.0 / 13.0)
|
|
271
|
+
ew3, m3 = ewma(ew2, m2, alpha=2.0 / 13.0)
|
|
272
|
+
# Change: ew3[t] / ew3[t-1] - 1
|
|
273
|
+
prev, pm = delay(ew3, m3, 1)
|
|
274
|
+
out = ew3 / prev.clamp_min(1e-9) - 1.0
|
|
275
|
+
m = m3 & pm
|
|
276
|
+
return out * m.float(), m
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
@register_legacy_factor("better_020")
|
|
280
|
+
def better_020(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
281
|
+
"""std(|ret|/volume, 10) / mean(|ret|/volume, 10) - Amihud illiquidity CV."""
|
|
282
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
283
|
+
ret = (panel.close / prev.clamp_min(1e-9) - 1.0).abs()
|
|
284
|
+
signal = ret / panel.volume.clamp_min(1.0)
|
|
285
|
+
sd, sm = ts_std(signal, pm, 10)
|
|
286
|
+
avg, am = ts_mean(signal, pm, 10)
|
|
287
|
+
m = sm & am
|
|
288
|
+
out = sd / avg.clamp_min(1e-9)
|
|
289
|
+
return out * m.float(), m
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@register_legacy_factor("better_021")
|
|
293
|
+
def better_021(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
294
|
+
"""vwap / delay(close, 1)."""
|
|
295
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
296
|
+
out = panel.vwap / prev.clamp_min(1e-9)
|
|
297
|
+
return out * pm.float(), pm
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@register_legacy_factor("better_022")
|
|
301
|
+
def better_022(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
302
|
+
"""(vwap/delay(close,1) - 1) * log2(amount + 1)."""
|
|
303
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
304
|
+
ratio = panel.vwap / prev.clamp_min(1e-9) - 1.0
|
|
305
|
+
amt = _amount(panel)
|
|
306
|
+
out = ratio * torch.log2(amt + 1.0)
|
|
307
|
+
m = pm & panel.mask
|
|
308
|
+
return out * m.float(), m
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
@register_legacy_factor("better_023")
|
|
312
|
+
def better_023(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
313
|
+
"""mean((vwap/delay(close,1) - 1) * log2(amount + 1), 4)."""
|
|
314
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
315
|
+
ratio = panel.vwap / prev.clamp_min(1e-9) - 1.0
|
|
316
|
+
amt = _amount(panel)
|
|
317
|
+
signal = ratio * torch.log2(amt + 1.0)
|
|
318
|
+
m = pm & panel.mask
|
|
319
|
+
return ts_mean(signal * m.float(), m, 4)
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@register_legacy_factor("better_024")
|
|
323
|
+
def better_024(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
324
|
+
"""(max(vwap-close, 5) + min(vwap-close, 5)) * Δvolume_5."""
|
|
325
|
+
spread = panel.vwap - panel.close
|
|
326
|
+
mx, mxm = ts_max(spread, panel.mask, 5)
|
|
327
|
+
mn, mnm = ts_min(spread, panel.mask, 5)
|
|
328
|
+
dv, dvm = delta(panel.volume, panel.mask, 5)
|
|
329
|
+
m = mxm & mnm & dvm
|
|
330
|
+
out = (mx + mn) * dv
|
|
331
|
+
return out * m.float(), m
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@register_legacy_factor("better_025")
|
|
335
|
+
def better_025(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
336
|
+
"""(max(vwap-close, 5) + min(vwap-close, 5)) * (volume - mean(volume, 5))."""
|
|
337
|
+
spread = panel.vwap - panel.close
|
|
338
|
+
mx, mxm = ts_max(spread, panel.mask, 5)
|
|
339
|
+
mn, mnm = ts_min(spread, panel.mask, 5)
|
|
340
|
+
avg, am = ts_mean(panel.volume, panel.mask, 5)
|
|
341
|
+
m = mxm & mnm & am
|
|
342
|
+
out = (mx + mn) * (panel.volume - avg)
|
|
343
|
+
return out * m.float(), m
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@register_legacy_factor("better_026")
|
|
347
|
+
def better_026(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
348
|
+
"""ewma((high-low)/close, 2/5) / ewma(ewma((high-low)/close, 2/5), 2/20)."""
|
|
349
|
+
rng = (panel.high - panel.low) / panel.close.clamp_min(1e-9)
|
|
350
|
+
sma1, m1 = ewma(rng, panel.mask, alpha=2.0 / 5.0)
|
|
351
|
+
sma2, m2 = ewma(sma1, m1, alpha=2.0 / 20.0)
|
|
352
|
+
m = m1 & m2
|
|
353
|
+
out = sma1 / sma2.clamp_min(1e-9)
|
|
354
|
+
return out * m.float(), m
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
@register_legacy_factor("better_027")
|
|
358
|
+
def better_027(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
359
|
+
"""Conditional sum: if vwap < delay(close,1): |vwap/delay(close,1)-1|/log(amount+1), window=20."""
|
|
360
|
+
prev, pm = delay(panel.close, panel.mask, 1)
|
|
361
|
+
ratio = (panel.vwap / prev.clamp_min(1e-9) - 1.0).abs()
|
|
362
|
+
amt = _amount(panel)
|
|
363
|
+
signal = ratio / torch.log(amt + 1.0).clamp_min(1e-9)
|
|
364
|
+
cond = (panel.vwap < prev).float() * pm.float()
|
|
365
|
+
weighted = signal * cond
|
|
366
|
+
s, sm = ts_sum(weighted, pm, 20)
|
|
367
|
+
cnt, cm = ts_sum(cond, pm, 20)
|
|
368
|
+
m = sm & cm
|
|
369
|
+
out = s / cnt.clamp_min(1.0)
|
|
370
|
+
return out * m.float(), m
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
@register_legacy_factor("better_028")
|
|
374
|
+
def better_028(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
375
|
+
"""Conditional sum: if vwap < close: |vwap/close-1|/log(amount+1), window=20."""
|
|
376
|
+
ratio = (panel.vwap / panel.close.clamp_min(1e-9) - 1.0).abs()
|
|
377
|
+
amt = _amount(panel)
|
|
378
|
+
signal = ratio / torch.log(amt + 1.0).clamp_min(1e-9)
|
|
379
|
+
cond = (panel.vwap < panel.close).float() * panel.mask.float()
|
|
380
|
+
weighted = signal * cond
|
|
381
|
+
s, sm = ts_sum(weighted, panel.mask, 20)
|
|
382
|
+
cnt, cm = ts_sum(cond, panel.mask, 20)
|
|
383
|
+
m = sm & cm
|
|
384
|
+
out = s / cnt.clamp_min(1.0)
|
|
385
|
+
return out * m.float(), m
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""change_* family: short-window change-of-velocity signals (5 factors)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
from typing import Tuple
|
|
4
|
+
import torch
|
|
5
|
+
|
|
6
|
+
from ..data.panel import Panel
|
|
7
|
+
from .tensor_factors import cs_rank, delta, ts_mean, ts_sum
|
|
8
|
+
from .legacy_factors import register_legacy_factor
|
|
9
|
+
|
|
10
|
+
Tensor = torch.Tensor
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@register_legacy_factor("change_001")
|
|
14
|
+
def change_001(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
15
|
+
"""1-day return minus its 5-day mean (mean-reversion proxy)."""
|
|
16
|
+
ret, rm = delta(panel.close, panel.mask, 1)
|
|
17
|
+
ret = ret / panel.close.clamp_min(1e-9)
|
|
18
|
+
avg, am = ts_mean(ret, rm, 5)
|
|
19
|
+
m = rm & am
|
|
20
|
+
return (ret - avg) * m.float(), m
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@register_legacy_factor("change_002")
|
|
24
|
+
def change_002(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
25
|
+
"""Acceleration: Δ(Δclose, 1) over 1 day."""
|
|
26
|
+
d1, m1 = delta(panel.close, panel.mask, 1)
|
|
27
|
+
d2, m2 = delta(d1, m1, 1)
|
|
28
|
+
return d2 / panel.close.clamp_min(1e-9), m2
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@register_legacy_factor("change_003")
|
|
32
|
+
def change_003(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
33
|
+
"""Volume Δ-rank: rank of (volume - ts_mean(volume, 20))."""
|
|
34
|
+
avg, am = ts_mean(panel.volume, panel.mask, 20)
|
|
35
|
+
dev = (panel.volume - avg) * am.float()
|
|
36
|
+
return cs_rank(dev, am)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@register_legacy_factor("change_004")
|
|
40
|
+
def change_004(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
41
|
+
"""Range expansion: (high - low) / ts_mean(high - low, 20)."""
|
|
42
|
+
rng_ = panel.high - panel.low
|
|
43
|
+
avg, am = ts_mean(rng_, panel.mask, 20)
|
|
44
|
+
out = rng_ / avg.clamp_min(1e-9) - 1.0
|
|
45
|
+
return out * am.float(), am
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@register_legacy_factor("change_005")
|
|
49
|
+
def change_005(panel: Panel) -> Tuple[Tensor, Tensor]:
|
|
50
|
+
"""Sign flip count: ts_sum(sign(Δclose), 5)."""
|
|
51
|
+
d, dm = delta(panel.close, panel.mask, 1)
|
|
52
|
+
s = torch.sign(d)
|
|
53
|
+
return ts_sum(s, dm, 5)
|