tradepose-client 0.1.1__py3-none-any.whl → 0.1.2__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.
Potentially problematic release.
This version of tradepose-client might be problematic. Click here for more details.
- tradepose_client/builder/trading_context.py +7 -29
- tradepose_client/indicator_models.py +300 -0
- tradepose_client/models.py +266 -195
- {tradepose_client-0.1.1.dist-info → tradepose_client-0.1.2.dist-info}/METADATA +1 -1
- {tradepose_client-0.1.1.dist-info → tradepose_client-0.1.2.dist-info}/RECORD +7 -6
- {tradepose_client-0.1.1.dist-info → tradepose_client-0.1.2.dist-info}/WHEEL +0 -0
- {tradepose_client-0.1.1.dist-info → tradepose_client-0.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -33,9 +33,11 @@ class TradingContextProxy:
|
|
|
33
33
|
context_type: 上下文类型("base" 或 "advanced")
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
|
-
def __init__(
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
context_name: str,
|
|
39
|
+
):
|
|
37
40
|
self._context_name = context_name
|
|
38
|
-
self._context_type = context_type
|
|
39
41
|
|
|
40
42
|
@property
|
|
41
43
|
def entry_price(self) -> pl.Expr:
|
|
@@ -59,32 +61,8 @@ class TradingContextProxy:
|
|
|
59
61
|
- base_trading_context 使用 "bars_in_position"
|
|
60
62
|
- advanced 使用 "bars_since_advanced_entry"(请用 .bars_since_entry)
|
|
61
63
|
"""
|
|
62
|
-
if self._context_type != "base":
|
|
63
|
-
raise AttributeError(
|
|
64
|
-
f"'bars_in_position' only available for base context. "
|
|
65
|
-
f"Use '.bars_since_entry' for advanced contexts."
|
|
66
|
-
)
|
|
67
64
|
return pl.col(self._context_name).struct.field("bars_in_position")
|
|
68
65
|
|
|
69
|
-
@property
|
|
70
|
-
def bars_since_entry(self) -> pl.Expr:
|
|
71
|
-
"""
|
|
72
|
-
进场后 K 线数(仅适用于 advanced contexts)
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
pl.Expr: Polars 表达式
|
|
76
|
-
|
|
77
|
-
Note:
|
|
78
|
-
- advanced_entry/exit_trading_context 使用 "bars_since_advanced_entry"
|
|
79
|
-
- base 使用 "bars_in_position"(请用 .bars_in_position)
|
|
80
|
-
"""
|
|
81
|
-
if self._context_type == "base":
|
|
82
|
-
raise AttributeError(
|
|
83
|
-
f"'bars_since_entry' not available for base context. "
|
|
84
|
-
f"Use '.bars_in_position' for base context."
|
|
85
|
-
)
|
|
86
|
-
return pl.col(self._context_name).struct.field("bars_since_advanced_entry")
|
|
87
|
-
|
|
88
66
|
@property
|
|
89
67
|
def highest_since_entry(self) -> pl.Expr:
|
|
90
68
|
"""
|
|
@@ -151,9 +129,9 @@ class TradingContext:
|
|
|
151
129
|
timeout_condition = TradingContext.advanced_entry.bars_since_entry > 100
|
|
152
130
|
"""
|
|
153
131
|
|
|
154
|
-
base = TradingContextProxy("base_trading_context"
|
|
155
|
-
advanced_entry = TradingContextProxy("advanced_entry_trading_context"
|
|
156
|
-
advanced_exit = TradingContextProxy("advanced_exit_trading_context"
|
|
132
|
+
base = TradingContextProxy("base_trading_context")
|
|
133
|
+
advanced_entry = TradingContextProxy("advanced_entry_trading_context")
|
|
134
|
+
advanced_exit = TradingContextProxy("advanced_exit_trading_context")
|
|
157
135
|
|
|
158
136
|
|
|
159
137
|
# 向后兼容的别名
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Indicator Pydantic Models for Strong Typing
|
|
3
|
+
|
|
4
|
+
为每个指标类型创建强类型的 Pydantic 模型,提供:
|
|
5
|
+
- 编译时类型检查
|
|
6
|
+
- IDE 自动补全
|
|
7
|
+
- 参数验证
|
|
8
|
+
- 与 Rust backend 对齐的序列化
|
|
9
|
+
|
|
10
|
+
All models are aligned with Rust Indicator enum variants.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Any, Dict, List, Literal, Optional
|
|
14
|
+
from pydantic import BaseModel, Field
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# ============================================================================
|
|
18
|
+
# 移动平均类指标(Moving Average Indicators)
|
|
19
|
+
# ============================================================================
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SMAIndicator(BaseModel):
|
|
23
|
+
"""Simple Moving Average (SMA) 指标
|
|
24
|
+
|
|
25
|
+
对应 Rust: Indicator::SMA { period, column }
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
period: 计算周期(必须 > 0)
|
|
29
|
+
column: 计算列名(默认 "close")
|
|
30
|
+
|
|
31
|
+
Example:
|
|
32
|
+
>>> sma = SMAIndicator(period=20)
|
|
33
|
+
>>> sma = SMAIndicator(period=50, column="high")
|
|
34
|
+
"""
|
|
35
|
+
type: Literal["SMA"] = "SMA"
|
|
36
|
+
period: int = Field(gt=0, le=500, description="Period must be > 0 and <= 500")
|
|
37
|
+
column: str = Field(
|
|
38
|
+
default="close",
|
|
39
|
+
pattern="^(open|high|low|close|volume)$",
|
|
40
|
+
description="OHLCV column name"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class EMAIndicator(BaseModel):
|
|
45
|
+
"""Exponential Moving Average (EMA) 指标
|
|
46
|
+
|
|
47
|
+
对应 Rust: Indicator::EMA { period, column }
|
|
48
|
+
"""
|
|
49
|
+
type: Literal["EMA"] = "EMA"
|
|
50
|
+
period: int = Field(gt=0, le=500)
|
|
51
|
+
column: str = Field(default="close", pattern="^(open|high|low|close|volume)$")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class SMMAIndicator(BaseModel):
|
|
55
|
+
"""Smoothed Moving Average (SMMA) 指标
|
|
56
|
+
|
|
57
|
+
对应 Rust: Indicator::SMMA { period, column }
|
|
58
|
+
"""
|
|
59
|
+
type: Literal["SMMA"] = "SMMA"
|
|
60
|
+
period: int = Field(gt=0, le=500)
|
|
61
|
+
column: str = Field(default="close", pattern="^(open|high|low|close|volume)$")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class WMAIndicator(BaseModel):
|
|
65
|
+
"""Weighted Moving Average (WMA) 指标
|
|
66
|
+
|
|
67
|
+
对应 Rust: Indicator::WMA { period, column }
|
|
68
|
+
"""
|
|
69
|
+
type: Literal["WMA"] = "WMA"
|
|
70
|
+
period: int = Field(gt=0, le=500)
|
|
71
|
+
column: str = Field(default="close", pattern="^(open|high|low|close|volume)$")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# ============================================================================
|
|
75
|
+
# 波动率类指标(Volatility Indicators)
|
|
76
|
+
# ============================================================================
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ATRIndicator(BaseModel):
|
|
80
|
+
"""Average True Range (ATR) 指标
|
|
81
|
+
|
|
82
|
+
对应 Rust: Indicator::ATR { period }
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
单一数值字段(不是 struct)
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
>>> atr = ATRIndicator(period=14)
|
|
89
|
+
>>> atr = ATRIndicator(period=21)
|
|
90
|
+
"""
|
|
91
|
+
type: Literal["ATR"] = "ATR"
|
|
92
|
+
period: int = Field(gt=0, le=200, description="Period must be > 0 and <= 200")
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class ATRQuantileIndicator(BaseModel):
|
|
96
|
+
"""ATR Rolling Quantile 指标
|
|
97
|
+
|
|
98
|
+
对应 Rust: Indicator::AtrQuantile { atr_column, window, quantile }
|
|
99
|
+
|
|
100
|
+
计算 ATR 的滚动分位数,用于动态止损等场景。
|
|
101
|
+
|
|
102
|
+
注意:这是依赖指标,必须先定义 ATR 指标。
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
atr_column: 引用的 ATR 列名(如 "ATR|14")
|
|
106
|
+
window: 滚动窗口大小
|
|
107
|
+
quantile: 分位数值 (0, 1),0.5 表示中位数
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
单一数值字段(不是 struct)
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
>>> atr_q = ATRQuantileIndicator(
|
|
114
|
+
... atr_column="ATR|21",
|
|
115
|
+
... window=40,
|
|
116
|
+
... quantile=0.75 # 75th percentile
|
|
117
|
+
... )
|
|
118
|
+
"""
|
|
119
|
+
type: Literal["AtrQuantile"] = "AtrQuantile"
|
|
120
|
+
atr_column: str = Field(min_length=1, description="Reference to existing ATR column")
|
|
121
|
+
window: int = Field(gt=0, le=500, description="Rolling window size")
|
|
122
|
+
quantile: float = Field(gt=0, lt=1, description="Quantile value in (0, 1)")
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# ============================================================================
|
|
126
|
+
# 趋势类指标(Trend Indicators)
|
|
127
|
+
# ============================================================================
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class SuperTrendIndicator(BaseModel):
|
|
131
|
+
"""SuperTrend 指标
|
|
132
|
+
|
|
133
|
+
对应 Rust: Indicator::SuperTrend { multiplier, volatility_column, high, low, close }
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
multiplier: ATR 倍数(通常 2.0-3.0)
|
|
137
|
+
volatility_column: 引用的波动率列名(如 "ATR|14",注意是列名不是 struct 字段)
|
|
138
|
+
high: 高价列名(默认 "high")
|
|
139
|
+
low: 低价列名(默认 "low")
|
|
140
|
+
close: 收盘价列名(默认 "close")
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Struct { direction: i32, value: f64, trend: f64, ...}
|
|
144
|
+
|
|
145
|
+
Example:
|
|
146
|
+
>>> st = SuperTrendIndicator(
|
|
147
|
+
... multiplier=3.0,
|
|
148
|
+
... volatility_column="ATR|21"
|
|
149
|
+
... )
|
|
150
|
+
"""
|
|
151
|
+
type: Literal["SuperTrend"] = "SuperTrend"
|
|
152
|
+
multiplier: float = Field(gt=0, le=10, description="ATR multiplier (typically 2.0-3.0)")
|
|
153
|
+
volatility_column: str = Field(
|
|
154
|
+
min_length=1,
|
|
155
|
+
description="Reference to volatility column (e.g., 'ATR|14')"
|
|
156
|
+
)
|
|
157
|
+
high: str = Field(default="high", pattern="^(high|open|low|close)$")
|
|
158
|
+
low: str = Field(default="low", pattern="^(high|open|low|close)$")
|
|
159
|
+
close: str = Field(default="close", pattern="^(high|open|low|close)$")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class MACDIndicator(BaseModel):
|
|
163
|
+
"""Moving Average Convergence Divergence (MACD) 指标
|
|
164
|
+
|
|
165
|
+
对应 Rust: Indicator::MACD { fast_period, slow_period, signal_period, column, fields }
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
Struct { macd: f64, signal: f64, histogram: f64 }
|
|
169
|
+
"""
|
|
170
|
+
type: Literal["MACD"] = "MACD"
|
|
171
|
+
fast_period: int = Field(default=12, gt=0, le=100)
|
|
172
|
+
slow_period: int = Field(default=26, gt=0, le=200)
|
|
173
|
+
signal_period: int = Field(default=9, gt=0, le=50)
|
|
174
|
+
column: str = Field(default="close", pattern="^(open|high|low|close|volume)$")
|
|
175
|
+
fields: Optional[List[str]] = Field(
|
|
176
|
+
default=None,
|
|
177
|
+
description="Select specific fields: ['macd', 'signal', 'histogram']"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class ADXIndicator(BaseModel):
|
|
182
|
+
"""Average Directional Index (ADX) 指标
|
|
183
|
+
|
|
184
|
+
对应 Rust: Indicator::ADX { period, fields }
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Struct { adx: f64, plus_di: f64, minus_di: f64 }
|
|
188
|
+
"""
|
|
189
|
+
type: Literal["ADX"] = "ADX"
|
|
190
|
+
period: int = Field(default=14, gt=0, le=100)
|
|
191
|
+
fields: Optional[List[str]] = Field(
|
|
192
|
+
default=None,
|
|
193
|
+
description="Select specific fields: ['adx', 'plus_di', 'minus_di']"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
# ============================================================================
|
|
198
|
+
# 动量类指标(Momentum Indicators)
|
|
199
|
+
# ============================================================================
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class RSIIndicator(BaseModel):
|
|
203
|
+
"""Relative Strength Index (RSI) 指标
|
|
204
|
+
|
|
205
|
+
对应 Rust: Indicator::RSI { period, column }
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
单一数值字段(0-100 范围)
|
|
209
|
+
"""
|
|
210
|
+
type: Literal["RSI"] = "RSI"
|
|
211
|
+
period: int = Field(default=14, gt=0, le=100)
|
|
212
|
+
column: str = Field(default="close", pattern="^(open|high|low|close|volume)$")
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class CCIIndicator(BaseModel):
|
|
216
|
+
"""Commodity Channel Index (CCI) 指标
|
|
217
|
+
|
|
218
|
+
对应 Rust: Indicator::CCI { period }
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
单一数值字段
|
|
222
|
+
"""
|
|
223
|
+
type: Literal["CCI"] = "CCI"
|
|
224
|
+
period: int = Field(default=20, gt=0, le=100)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class StochasticIndicator(BaseModel):
|
|
228
|
+
"""Stochastic Oscillator 指标
|
|
229
|
+
|
|
230
|
+
对应 Rust: Indicator::Stochastic { k_period, d_period, fields }
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
Struct { k: f64, d: f64 }
|
|
234
|
+
"""
|
|
235
|
+
type: Literal["Stochastic"] = "Stochastic"
|
|
236
|
+
k_period: int = Field(default=14, gt=0, le=100, description="%K period")
|
|
237
|
+
d_period: int = Field(default=3, gt=0, le=50, description="%D period (smoothing)")
|
|
238
|
+
fields: Optional[List[str]] = Field(
|
|
239
|
+
default=None,
|
|
240
|
+
description="Select specific fields: ['k', 'd']"
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# ============================================================================
|
|
245
|
+
# 其他指标(Other Indicators)
|
|
246
|
+
# ============================================================================
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class BollingerBandsIndicator(BaseModel):
|
|
250
|
+
"""Bollinger Bands 指标
|
|
251
|
+
|
|
252
|
+
对应 Rust: Indicator::BollingerBands { period, num_std, column, fields }
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
Struct { upper: f64, middle: f64, lower: f64, bandwidth: f64 }
|
|
256
|
+
"""
|
|
257
|
+
type: Literal["BollingerBands"] = "BollingerBands"
|
|
258
|
+
period: int = Field(default=20, gt=0, le=200)
|
|
259
|
+
num_std: float = Field(default=2.0, gt=0, le=5.0, description="Standard deviation multiplier")
|
|
260
|
+
column: str = Field(default="close", pattern="^(open|high|low|close|volume)$")
|
|
261
|
+
fields: Optional[List[str]] = Field(
|
|
262
|
+
default=None,
|
|
263
|
+
description="Select specific fields: ['upper', 'middle', 'lower', 'bandwidth']"
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
class MarketProfileIndicator(BaseModel):
|
|
268
|
+
"""Market Profile 指标
|
|
269
|
+
|
|
270
|
+
对应 Rust: Indicator::MarketProfile { anchor_config, tick_size, value_area_pct, fields }
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Struct { poc: f64, vah: f64, val: f64, value_area: List[f64], total_tpo: i32, ...}
|
|
274
|
+
"""
|
|
275
|
+
type: Literal["MarketProfile"] = "MarketProfile"
|
|
276
|
+
anchor_config: Dict[str, Any] = Field(
|
|
277
|
+
...,
|
|
278
|
+
description="Anchor configuration (use create_daily_anchor or create_weekly_anchor)"
|
|
279
|
+
)
|
|
280
|
+
tick_size: float = Field(gt=0, description="Tick size for price levels")
|
|
281
|
+
value_area_pct: float = Field(default=0.7, gt=0, lt=1, description="Value area percentage")
|
|
282
|
+
fields: Optional[List[str]] = Field(
|
|
283
|
+
default=None,
|
|
284
|
+
description="Select specific fields: ['poc', 'vah', 'val', 'value_area', 'total_tpo']"
|
|
285
|
+
)
|
|
286
|
+
shape_config: Optional[Dict[str, Any]] = Field(
|
|
287
|
+
default=None,
|
|
288
|
+
description="Profile shape recognition config (advanced feature)"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class RawOhlcvIndicator(BaseModel):
|
|
293
|
+
"""Raw OHLCV Column 指标
|
|
294
|
+
|
|
295
|
+
对应 Rust: Indicator::RawOhlcv { column }
|
|
296
|
+
|
|
297
|
+
直接引用 OHLCV 列,用于需要原始数据的场景。
|
|
298
|
+
"""
|
|
299
|
+
type: Literal["RawOhlcv"] = "RawOhlcv"
|
|
300
|
+
column: str = Field(pattern="^(open|high|low|close|volume)$")
|
tradepose_client/models.py
CHANGED
|
@@ -9,14 +9,87 @@ Pydantic 模型用於策略配置 (V2 - 自動轉換版本)
|
|
|
9
9
|
import json
|
|
10
10
|
from enum import Enum
|
|
11
11
|
from io import StringIO
|
|
12
|
-
from typing import Any, Dict, List, Optional, Union
|
|
12
|
+
from typing import Annotated, Any, Dict, List, Optional, Union
|
|
13
13
|
|
|
14
14
|
import polars as pl
|
|
15
|
-
from pydantic import BaseModel, Field, field_serializer, field_validator
|
|
15
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
16
16
|
|
|
17
17
|
# Import enums from enums.py
|
|
18
18
|
from .enums import Freq, OrderStrategy, Weekday, TradeDirection, TrendType
|
|
19
19
|
|
|
20
|
+
# Import Indicator models
|
|
21
|
+
from .indicator_models import (
|
|
22
|
+
SMAIndicator,
|
|
23
|
+
EMAIndicator,
|
|
24
|
+
SMMAIndicator,
|
|
25
|
+
WMAIndicator,
|
|
26
|
+
ATRIndicator,
|
|
27
|
+
ATRQuantileIndicator,
|
|
28
|
+
SuperTrendIndicator,
|
|
29
|
+
MACDIndicator,
|
|
30
|
+
ADXIndicator,
|
|
31
|
+
RSIIndicator,
|
|
32
|
+
CCIIndicator,
|
|
33
|
+
StochasticIndicator,
|
|
34
|
+
BollingerBandsIndicator,
|
|
35
|
+
MarketProfileIndicator,
|
|
36
|
+
RawOhlcvIndicator,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# ============================================================================
|
|
41
|
+
# Indicator Discriminated Union (强类型 Indicator 配置)
|
|
42
|
+
# ============================================================================
|
|
43
|
+
|
|
44
|
+
IndicatorConfig = Annotated[
|
|
45
|
+
Union[
|
|
46
|
+
# 移动平均类
|
|
47
|
+
SMAIndicator,
|
|
48
|
+
EMAIndicator,
|
|
49
|
+
SMMAIndicator,
|
|
50
|
+
WMAIndicator,
|
|
51
|
+
# 波动率类
|
|
52
|
+
ATRIndicator,
|
|
53
|
+
ATRQuantileIndicator,
|
|
54
|
+
# 趋势类
|
|
55
|
+
SuperTrendIndicator,
|
|
56
|
+
MACDIndicator,
|
|
57
|
+
ADXIndicator,
|
|
58
|
+
# 动量类
|
|
59
|
+
RSIIndicator,
|
|
60
|
+
CCIIndicator,
|
|
61
|
+
StochasticIndicator,
|
|
62
|
+
# 其他
|
|
63
|
+
BollingerBandsIndicator,
|
|
64
|
+
MarketProfileIndicator,
|
|
65
|
+
RawOhlcvIndicator,
|
|
66
|
+
],
|
|
67
|
+
Field(discriminator='type')
|
|
68
|
+
]
|
|
69
|
+
"""
|
|
70
|
+
强类型 Indicator 配置
|
|
71
|
+
|
|
72
|
+
使用 Pydantic discriminated union,根据 'type' 字段自动选择正确的模型:
|
|
73
|
+
- type="SMA" → SMAIndicator
|
|
74
|
+
- type="ATR" → ATRIndicator
|
|
75
|
+
- type="SuperTrend" → SuperTrendIndicator
|
|
76
|
+
- ... etc
|
|
77
|
+
|
|
78
|
+
优点:
|
|
79
|
+
- 编译时类型检查
|
|
80
|
+
- IDE 自动补全
|
|
81
|
+
- 参数验证
|
|
82
|
+
- 自动 JSON 序列化/反序列化
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> # 方式 1: 直接创建强类型模型
|
|
86
|
+
>>> atr = ATRIndicator(period=21)
|
|
87
|
+
>>>
|
|
88
|
+
>>> # 方式 2: 从 Dict 自动转换(API 兼容)
|
|
89
|
+
>>> indicator: IndicatorConfig = {"type": "ATR", "period": 21}
|
|
90
|
+
>>> # Pydantic 自动识别为 ATRIndicator
|
|
91
|
+
"""
|
|
92
|
+
|
|
20
93
|
|
|
21
94
|
class PolarsExprField:
|
|
22
95
|
"""自定義 Polars Expr 字段處理"""
|
|
@@ -165,8 +238,8 @@ class Indicator:
|
|
|
165
238
|
"""
|
|
166
239
|
|
|
167
240
|
@staticmethod
|
|
168
|
-
def sma(period: int, column: str = "close") ->
|
|
169
|
-
"""創建 SMA
|
|
241
|
+
def sma(period: int, column: str = "close") -> SMAIndicator:
|
|
242
|
+
"""創建 SMA 指標配置(强类型)
|
|
170
243
|
|
|
171
244
|
對應 Rust: Indicator::SMA { period, column }
|
|
172
245
|
|
|
@@ -175,72 +248,42 @@ class Indicator:
|
|
|
175
248
|
column: 計算欄位(預設 "close")
|
|
176
249
|
|
|
177
250
|
Returns:
|
|
178
|
-
|
|
251
|
+
SMAIndicator 強類型模型
|
|
179
252
|
|
|
180
253
|
Example:
|
|
181
|
-
>>> Indicator.sma(period=20)
|
|
182
|
-
>>>
|
|
254
|
+
>>> atr = Indicator.sma(period=20)
|
|
255
|
+
>>> assert isinstance(atr, SMAIndicator)
|
|
256
|
+
>>> atr_high = Indicator.sma(period=20, column="high")
|
|
183
257
|
"""
|
|
184
|
-
return
|
|
258
|
+
return SMAIndicator(period=period, column=column)
|
|
185
259
|
|
|
186
260
|
@staticmethod
|
|
187
|
-
def ema(period: int, column: str = "close") ->
|
|
188
|
-
"""創建 EMA
|
|
261
|
+
def ema(period: int, column: str = "close") -> EMAIndicator:
|
|
262
|
+
"""創建 EMA 指標配置(强类型)
|
|
189
263
|
|
|
190
264
|
對應 Rust: Indicator::EMA { period, column }
|
|
191
|
-
|
|
192
|
-
Args:
|
|
193
|
-
period: 週期
|
|
194
|
-
column: 計算欄位(預設 "close")
|
|
195
|
-
|
|
196
|
-
Returns:
|
|
197
|
-
EMA 指標配置 dict
|
|
198
|
-
|
|
199
|
-
Example:
|
|
200
|
-
>>> Indicator.ema(period=12)
|
|
201
|
-
>>> Indicator.ema(period=12, column="low")
|
|
202
265
|
"""
|
|
203
|
-
return
|
|
266
|
+
return EMAIndicator(period=period, column=column)
|
|
204
267
|
|
|
205
268
|
@staticmethod
|
|
206
|
-
def smma(period: int, column: str = "close") ->
|
|
207
|
-
"""創建 SMMA
|
|
269
|
+
def smma(period: int, column: str = "close") -> SMMAIndicator:
|
|
270
|
+
"""創建 SMMA 指標配置(强类型)
|
|
208
271
|
|
|
209
272
|
對應 Rust: Indicator::SMMA { period, column }
|
|
210
|
-
|
|
211
|
-
Args:
|
|
212
|
-
period: 週期
|
|
213
|
-
column: 計算欄位(預設 "close")
|
|
214
|
-
|
|
215
|
-
Returns:
|
|
216
|
-
SMMA 指標配置 dict
|
|
217
|
-
|
|
218
|
-
Example:
|
|
219
|
-
>>> Indicator.smma(period=14)
|
|
220
273
|
"""
|
|
221
|
-
return
|
|
274
|
+
return SMMAIndicator(period=period, column=column)
|
|
222
275
|
|
|
223
276
|
@staticmethod
|
|
224
|
-
def wma(period: int, column: str = "close") ->
|
|
225
|
-
"""創建 WMA
|
|
277
|
+
def wma(period: int, column: str = "close") -> WMAIndicator:
|
|
278
|
+
"""創建 WMA 指標配置(强类型)
|
|
226
279
|
|
|
227
280
|
對應 Rust: Indicator::WMA { period, column }
|
|
228
|
-
|
|
229
|
-
Args:
|
|
230
|
-
period: 週期
|
|
231
|
-
column: 計算欄位(預設 "close")
|
|
232
|
-
|
|
233
|
-
Returns:
|
|
234
|
-
WMA 指標配置 dict
|
|
235
|
-
|
|
236
|
-
Example:
|
|
237
|
-
>>> Indicator.wma(period=10)
|
|
238
281
|
"""
|
|
239
|
-
return
|
|
282
|
+
return WMAIndicator(period=period, column=column)
|
|
240
283
|
|
|
241
284
|
@staticmethod
|
|
242
|
-
def atr(period: int) ->
|
|
243
|
-
"""創建 ATR
|
|
285
|
+
def atr(period: int) -> ATRIndicator:
|
|
286
|
+
"""創建 ATR 指標配置(强类型)
|
|
244
287
|
|
|
245
288
|
對應 Rust: Indicator::ATR { period }
|
|
246
289
|
|
|
@@ -270,15 +313,15 @@ class Indicator:
|
|
|
270
313
|
... (pl.col("close") - 2 * pl.col("ATR|14")).alias("stop_loss")
|
|
271
314
|
... ])
|
|
272
315
|
"""
|
|
273
|
-
return
|
|
316
|
+
return ATRIndicator(period=period)
|
|
274
317
|
|
|
275
318
|
@staticmethod
|
|
276
319
|
def atr_quantile(
|
|
277
320
|
atr_column: str,
|
|
278
321
|
window: int,
|
|
279
322
|
quantile: float,
|
|
280
|
-
) ->
|
|
281
|
-
"""創建 AtrQuantile
|
|
323
|
+
) -> ATRQuantileIndicator:
|
|
324
|
+
"""創建 AtrQuantile 指標配置(强类型)
|
|
282
325
|
|
|
283
326
|
對應 Rust: Indicator::AtrQuantile { atr_column, window, quantile }
|
|
284
327
|
|
|
@@ -350,12 +393,11 @@ class Indicator:
|
|
|
350
393
|
... order_strategy="ImmediateEntry"
|
|
351
394
|
... )
|
|
352
395
|
"""
|
|
353
|
-
return
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
396
|
+
return ATRQuantileIndicator(
|
|
397
|
+
atr_column=atr_column,
|
|
398
|
+
window=window,
|
|
399
|
+
quantile=quantile,
|
|
400
|
+
)
|
|
359
401
|
|
|
360
402
|
@staticmethod
|
|
361
403
|
def supertrend(
|
|
@@ -365,8 +407,8 @@ class Indicator:
|
|
|
365
407
|
low: str = "low",
|
|
366
408
|
close: str = "close",
|
|
367
409
|
fields: Optional[List[str]] = None,
|
|
368
|
-
) ->
|
|
369
|
-
"""創建 SuperTrend
|
|
410
|
+
) -> SuperTrendIndicator:
|
|
411
|
+
"""創建 SuperTrend 指標配置(强类型)
|
|
370
412
|
|
|
371
413
|
對應 Rust: Indicator::SuperTrend { multiplier, volatility_column, high, low, close, fields }
|
|
372
414
|
|
|
@@ -408,17 +450,14 @@ class Indicator:
|
|
|
408
450
|
... fields=["direction", "supertrend"]
|
|
409
451
|
... )
|
|
410
452
|
"""
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if fields is not None:
|
|
420
|
-
config["fields"] = fields
|
|
421
|
-
return config
|
|
453
|
+
return SuperTrendIndicator(
|
|
454
|
+
multiplier=float(multiplier),
|
|
455
|
+
volatility_column=volatility_column,
|
|
456
|
+
high=high,
|
|
457
|
+
low=low,
|
|
458
|
+
close=close,
|
|
459
|
+
fields=fields,
|
|
460
|
+
)
|
|
422
461
|
|
|
423
462
|
@staticmethod
|
|
424
463
|
def market_profile(
|
|
@@ -427,8 +466,8 @@ class Indicator:
|
|
|
427
466
|
value_area_pct: float = 0.7,
|
|
428
467
|
fields: Optional[List[str]] = None,
|
|
429
468
|
shape_config: Optional[Dict[str, Any]] = None,
|
|
430
|
-
) ->
|
|
431
|
-
"""創建 Market Profile
|
|
469
|
+
) -> MarketProfileIndicator:
|
|
470
|
+
"""創建 Market Profile 指標配置(强类型)
|
|
432
471
|
|
|
433
472
|
對應 Rust: Indicator::MarketProfile { anchor_config, tick_size, ... }
|
|
434
473
|
|
|
@@ -486,21 +525,17 @@ class Indicator:
|
|
|
486
525
|
... shape_config=shape_cfg.model_dump()
|
|
487
526
|
... )
|
|
488
527
|
"""
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
config["fields"] = fields
|
|
497
|
-
if shape_config is not None:
|
|
498
|
-
config["shape_config"] = shape_config
|
|
499
|
-
return config
|
|
528
|
+
return MarketProfileIndicator(
|
|
529
|
+
anchor_config=anchor_config,
|
|
530
|
+
tick_size=tick_size,
|
|
531
|
+
value_area_pct=value_area_pct,
|
|
532
|
+
fields=fields,
|
|
533
|
+
shape_config=shape_config,
|
|
534
|
+
)
|
|
500
535
|
|
|
501
536
|
@staticmethod
|
|
502
|
-
def cci(period: int = 20) ->
|
|
503
|
-
"""創建 CCI
|
|
537
|
+
def cci(period: int = 20) -> CCIIndicator:
|
|
538
|
+
"""創建 CCI 指標配置(强类型)
|
|
504
539
|
|
|
505
540
|
對應 Rust: Indicator::CCI { period }
|
|
506
541
|
|
|
@@ -535,11 +570,11 @@ class Indicator:
|
|
|
535
570
|
>>> overbought = pl.col("1h_CCI|14") > 100
|
|
536
571
|
>>> oversold = pl.col("1h_CCI|14") < -100
|
|
537
572
|
"""
|
|
538
|
-
return
|
|
573
|
+
return CCIIndicator(period=period)
|
|
539
574
|
|
|
540
575
|
@staticmethod
|
|
541
|
-
def rsi(period: int = 14, column: str = "close") ->
|
|
542
|
-
"""創建 RSI
|
|
576
|
+
def rsi(period: int = 14, column: str = "close") -> RSIIndicator:
|
|
577
|
+
"""創建 RSI 指標配置(强类型)
|
|
543
578
|
|
|
544
579
|
對應 Rust: Indicator::RSI { period, column }
|
|
545
580
|
|
|
@@ -579,7 +614,7 @@ class Indicator:
|
|
|
579
614
|
>>> entry_long = pl.col("15min_RSI|14") < 30 # RSI < 30 做多
|
|
580
615
|
>>> entry_short = pl.col("15min_RSI|14") > 70 # RSI > 70 做空
|
|
581
616
|
"""
|
|
582
|
-
return
|
|
617
|
+
return RSIIndicator(period=period, column=column)
|
|
583
618
|
|
|
584
619
|
@staticmethod
|
|
585
620
|
def bollinger_bands(
|
|
@@ -587,8 +622,8 @@ class Indicator:
|
|
|
587
622
|
num_std: float = 2.0,
|
|
588
623
|
column: str = "close",
|
|
589
624
|
fields: Optional[List[str]] = None,
|
|
590
|
-
) ->
|
|
591
|
-
"""創建 Bollinger Bands
|
|
625
|
+
) -> BollingerBandsIndicator:
|
|
626
|
+
"""創建 Bollinger Bands 指標配置(强类型)
|
|
592
627
|
|
|
593
628
|
對應 Rust: Indicator::BollingerBands { period, num_std, column, fields }
|
|
594
629
|
|
|
@@ -645,15 +680,12 @@ class Indicator:
|
|
|
645
680
|
>>> breakout_up = pl.col("close") > upper
|
|
646
681
|
>>> breakout_down = pl.col("close") < lower
|
|
647
682
|
"""
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if fields is not None:
|
|
655
|
-
config["fields"] = fields
|
|
656
|
-
return config
|
|
683
|
+
return BollingerBandsIndicator(
|
|
684
|
+
period=period,
|
|
685
|
+
num_std=float(num_std),
|
|
686
|
+
column=column,
|
|
687
|
+
fields=fields,
|
|
688
|
+
)
|
|
657
689
|
|
|
658
690
|
@staticmethod
|
|
659
691
|
def macd(
|
|
@@ -662,8 +694,8 @@ class Indicator:
|
|
|
662
694
|
signal_period: int = 9,
|
|
663
695
|
column: str = "close",
|
|
664
696
|
fields: Optional[List[str]] = None,
|
|
665
|
-
) ->
|
|
666
|
-
"""創建 MACD
|
|
697
|
+
) -> MACDIndicator:
|
|
698
|
+
"""創建 MACD 指標配置(强类型)
|
|
667
699
|
|
|
668
700
|
對應 Rust: Indicator::MACD { fast_period, slow_period, signal_period, column, fields }
|
|
669
701
|
|
|
@@ -720,24 +752,21 @@ class Indicator:
|
|
|
720
752
|
>>> golden_cross = macd_line > signal_line # 金叉(做多)
|
|
721
753
|
>>> death_cross = macd_line < signal_line # 死叉(做空)
|
|
722
754
|
"""
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if fields is not None:
|
|
731
|
-
config["fields"] = fields
|
|
732
|
-
return config
|
|
755
|
+
return MACDIndicator(
|
|
756
|
+
fast_period=fast_period,
|
|
757
|
+
slow_period=slow_period,
|
|
758
|
+
signal_period=signal_period,
|
|
759
|
+
column=column,
|
|
760
|
+
fields=fields,
|
|
761
|
+
)
|
|
733
762
|
|
|
734
763
|
@staticmethod
|
|
735
764
|
def stochastic(
|
|
736
765
|
k_period: int = 14,
|
|
737
766
|
d_period: int = 3,
|
|
738
767
|
fields: Optional[List[str]] = None,
|
|
739
|
-
) ->
|
|
740
|
-
"""創建 Stochastic
|
|
768
|
+
) -> StochasticIndicator:
|
|
769
|
+
"""創建 Stochastic 指標配置(强类型)
|
|
741
770
|
|
|
742
771
|
對應 Rust: Indicator::Stochastic { k_period, d_period, fields }
|
|
743
772
|
|
|
@@ -790,18 +819,15 @@ class Indicator:
|
|
|
790
819
|
>>> golden_cross = k_line > d_line # 金叉(做多)
|
|
791
820
|
>>> death_cross = k_line < d_line # 死叉(做空)
|
|
792
821
|
"""
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
if fields is not None:
|
|
799
|
-
config["fields"] = fields
|
|
800
|
-
return config
|
|
822
|
+
return StochasticIndicator(
|
|
823
|
+
k_period=k_period,
|
|
824
|
+
d_period=d_period,
|
|
825
|
+
fields=fields,
|
|
826
|
+
)
|
|
801
827
|
|
|
802
828
|
@staticmethod
|
|
803
|
-
def adx(period: int = 14, fields: Optional[List[str]] = None) ->
|
|
804
|
-
"""創建 ADX
|
|
829
|
+
def adx(period: int = 14, fields: Optional[List[str]] = None) -> ADXIndicator:
|
|
830
|
+
"""創建 ADX 指標配置(强类型)
|
|
805
831
|
|
|
806
832
|
對應 Rust: Indicator::ADX { period, fields }
|
|
807
833
|
|
|
@@ -864,17 +890,14 @@ class Indicator:
|
|
|
864
890
|
>>> uptrend = (plus_di > minus_di) & (adx_value > 25) # 上升趨勢 + 強度足夠
|
|
865
891
|
>>> downtrend = (plus_di < minus_di) & (adx_value > 25) # 下降趨勢 + 強度足夠
|
|
866
892
|
"""
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
if fields is not None:
|
|
872
|
-
config["fields"] = fields
|
|
873
|
-
return config
|
|
893
|
+
return ADXIndicator(
|
|
894
|
+
period=period,
|
|
895
|
+
fields=fields,
|
|
896
|
+
)
|
|
874
897
|
|
|
875
898
|
@staticmethod
|
|
876
|
-
def raw_ohlcv(column: str) ->
|
|
877
|
-
"""創建 RawOhlcv
|
|
899
|
+
def raw_ohlcv(column: str) -> RawOhlcvIndicator:
|
|
900
|
+
"""創建 RawOhlcv 指標配置(强类型)
|
|
878
901
|
|
|
879
902
|
對應 Rust: Indicator::RawOhlcv { column }
|
|
880
903
|
|
|
@@ -913,7 +936,7 @@ class Indicator:
|
|
|
913
936
|
>>> # 輸出列名: "TXF_M1_SHIOAJI_FUTURE_open_1D"
|
|
914
937
|
>>> # 策略條件: if price > daily_open.col(): ...
|
|
915
938
|
"""
|
|
916
|
-
return
|
|
939
|
+
return RawOhlcvIndicator(column=column)
|
|
917
940
|
|
|
918
941
|
|
|
919
942
|
# ============================================================================
|
|
@@ -1075,17 +1098,47 @@ def create_profile_shape_config(
|
|
|
1075
1098
|
|
|
1076
1099
|
|
|
1077
1100
|
class IndicatorSpec(BaseModel):
|
|
1078
|
-
"""
|
|
1101
|
+
"""指標規範(强类型版本)"""
|
|
1079
1102
|
|
|
1080
1103
|
instrument_id: Optional[str] = Field(
|
|
1081
1104
|
None, description="交易標的 ID(可選,用於多商品場景)"
|
|
1082
1105
|
)
|
|
1083
|
-
freq:
|
|
1106
|
+
freq: Freq = Field(..., description="頻率 (Freq enum)")
|
|
1084
1107
|
shift: int = Field(1, description="位移(預設 1)")
|
|
1085
|
-
indicator:
|
|
1086
|
-
..., description="
|
|
1108
|
+
indicator: IndicatorConfig = Field(
|
|
1109
|
+
..., description="指標配置(强类型 Pydantic 模型)"
|
|
1087
1110
|
)
|
|
1088
1111
|
|
|
1112
|
+
@field_validator("freq", mode="before")
|
|
1113
|
+
@classmethod
|
|
1114
|
+
def convert_freq(cls, v: Any) -> Freq:
|
|
1115
|
+
"""自動轉換字串為 Freq enum(保持 API 兼容性)"""
|
|
1116
|
+
if isinstance(v, str):
|
|
1117
|
+
try:
|
|
1118
|
+
return Freq(v)
|
|
1119
|
+
except ValueError:
|
|
1120
|
+
valid_values = [e.value for e in Freq]
|
|
1121
|
+
raise ValueError(
|
|
1122
|
+
f"Invalid freq: '{v}'. "
|
|
1123
|
+
f"Valid values: {', '.join(valid_values)}"
|
|
1124
|
+
)
|
|
1125
|
+
return v
|
|
1126
|
+
|
|
1127
|
+
@field_validator("indicator", mode="before")
|
|
1128
|
+
@classmethod
|
|
1129
|
+
def convert_indicator(cls, v: Any) -> IndicatorConfig:
|
|
1130
|
+
"""自動轉換 Dict 為強類型 Indicator 模型(保持 API 兼容性)
|
|
1131
|
+
|
|
1132
|
+
接受:
|
|
1133
|
+
- Dict[str, Any]: 從 API 返回或舊代碼(自動轉換)
|
|
1134
|
+
- IndicatorConfig: 新代碼使用強類型模型
|
|
1135
|
+
|
|
1136
|
+
Pydantic 會根據 'type' 字段自動選擇正確的模型。
|
|
1137
|
+
"""
|
|
1138
|
+
# Dict 會被 Pydantic 自動轉換為對應的 Indicator 模型
|
|
1139
|
+
# 強類型模型直接返回
|
|
1140
|
+
return v
|
|
1141
|
+
|
|
1089
1142
|
def short_name(self) -> str:
|
|
1090
1143
|
"""生成指標簡短名稱(與 Rust 的 short_name 一致)
|
|
1091
1144
|
|
|
@@ -1101,53 +1154,39 @@ class IndicatorSpec(BaseModel):
|
|
|
1101
1154
|
Raises:
|
|
1102
1155
|
ValueError: 未知的指標類型
|
|
1103
1156
|
"""
|
|
1104
|
-
indicator_type = self.indicator.
|
|
1157
|
+
indicator_type = self.indicator.type
|
|
1105
1158
|
|
|
1106
1159
|
if indicator_type == "SMA":
|
|
1107
|
-
|
|
1108
|
-
column = self.indicator.get("column", "close")
|
|
1109
|
-
return f"SMA|{period}.{column}"
|
|
1160
|
+
return f"SMA|{self.indicator.period}.{self.indicator.column}"
|
|
1110
1161
|
|
|
1111
1162
|
elif indicator_type == "EMA":
|
|
1112
|
-
|
|
1113
|
-
column = self.indicator.get("column", "close")
|
|
1114
|
-
return f"EMA|{period}.{column}"
|
|
1163
|
+
return f"EMA|{self.indicator.period}.{self.indicator.column}"
|
|
1115
1164
|
|
|
1116
1165
|
elif indicator_type == "SMMA":
|
|
1117
|
-
|
|
1118
|
-
column = self.indicator.get("column", "close")
|
|
1119
|
-
return f"SMMA|{period}.{column}"
|
|
1166
|
+
return f"SMMA|{self.indicator.period}.{self.indicator.column}"
|
|
1120
1167
|
|
|
1121
1168
|
elif indicator_type == "WMA":
|
|
1122
|
-
|
|
1123
|
-
column = self.indicator.get("column", "close")
|
|
1124
|
-
return f"WMA|{period}.{column}"
|
|
1169
|
+
return f"WMA|{self.indicator.period}.{self.indicator.column}"
|
|
1125
1170
|
|
|
1126
1171
|
elif indicator_type == "ATR":
|
|
1127
|
-
period = self.indicator.get("period")
|
|
1128
1172
|
# ATR 始終返回單一數值欄位,無 quantile
|
|
1129
|
-
return f"ATR|{period}"
|
|
1173
|
+
return f"ATR|{self.indicator.period}"
|
|
1130
1174
|
|
|
1131
1175
|
elif indicator_type == "AtrQuantile":
|
|
1132
|
-
atr_column = self.indicator.get("atr_column")
|
|
1133
|
-
window = self.indicator.get("window")
|
|
1134
|
-
quantile = self.indicator.get("quantile")
|
|
1135
1176
|
# 將 quantile 轉為百分比(例如 0.5 -> 50)
|
|
1136
|
-
quantile_pct = int(quantile * 100)
|
|
1177
|
+
quantile_pct = int(self.indicator.quantile * 100)
|
|
1137
1178
|
# 格式:ATRQ|{atr_column}_Q{quantile%}_{window}
|
|
1138
|
-
return f"ATRQ|{atr_column}_Q{quantile_pct}_{window}"
|
|
1179
|
+
return f"ATRQ|{self.indicator.atr_column}_Q{quantile_pct}_{self.indicator.window}"
|
|
1139
1180
|
|
|
1140
1181
|
elif indicator_type == "SuperTrend":
|
|
1141
|
-
|
|
1142
|
-
volatility_column = self.indicator.get("volatility_column")
|
|
1143
|
-
return f"ST|{multiplier}x_{volatility_column}"
|
|
1182
|
+
return f"ST|{self.indicator.multiplier}x_{self.indicator.volatility_column}"
|
|
1144
1183
|
|
|
1145
1184
|
elif indicator_type == "MarketProfile":
|
|
1146
1185
|
# 提取 anchor_config 資訊
|
|
1147
|
-
anchor_config = self.indicator.
|
|
1186
|
+
anchor_config = self.indicator.anchor_config
|
|
1148
1187
|
end_rule = anchor_config.get("end_rule", {})
|
|
1149
1188
|
lookback_days = anchor_config.get("lookback_days", 1)
|
|
1150
|
-
tick_size = self.indicator.
|
|
1189
|
+
tick_size = self.indicator.tick_size
|
|
1151
1190
|
|
|
1152
1191
|
# 格式化時間字串(支持新舊兩種格式)
|
|
1153
1192
|
rule_type = end_rule.get("type")
|
|
@@ -1212,37 +1251,26 @@ class IndicatorSpec(BaseModel):
|
|
|
1212
1251
|
return f"MP|{time_str}_{lookback_days}_{tick_size}"
|
|
1213
1252
|
|
|
1214
1253
|
elif indicator_type == "CCI":
|
|
1215
|
-
|
|
1216
|
-
return f"CCI|{period}"
|
|
1254
|
+
return f"CCI|{self.indicator.period}"
|
|
1217
1255
|
|
|
1218
1256
|
elif indicator_type == "RSI":
|
|
1219
|
-
|
|
1220
|
-
return f"RSI|{period}"
|
|
1257
|
+
return f"RSI|{self.indicator.period}"
|
|
1221
1258
|
|
|
1222
1259
|
elif indicator_type == "BollingerBands":
|
|
1223
|
-
|
|
1224
|
-
num_std = self.indicator.get("num_std")
|
|
1225
|
-
return f"BB|{period}_{num_std}"
|
|
1260
|
+
return f"BB|{self.indicator.period}_{self.indicator.num_std}"
|
|
1226
1261
|
|
|
1227
1262
|
elif indicator_type == "MACD":
|
|
1228
|
-
|
|
1229
|
-
slow_period = self.indicator.get("slow_period")
|
|
1230
|
-
signal_period = self.indicator.get("signal_period")
|
|
1231
|
-
return f"MACD|{fast_period}_{slow_period}_{signal_period}"
|
|
1263
|
+
return f"MACD|{self.indicator.fast_period}_{self.indicator.slow_period}_{self.indicator.signal_period}"
|
|
1232
1264
|
|
|
1233
1265
|
elif indicator_type == "Stochastic":
|
|
1234
|
-
|
|
1235
|
-
d_period = self.indicator.get("d_period")
|
|
1236
|
-
return f"STOCH|{k_period}_{d_period}"
|
|
1266
|
+
return f"STOCH|{self.indicator.k_period}_{self.indicator.d_period}"
|
|
1237
1267
|
|
|
1238
1268
|
elif indicator_type == "ADX":
|
|
1239
|
-
|
|
1240
|
-
return f"ADX|{period}"
|
|
1269
|
+
return f"ADX|{self.indicator.period}"
|
|
1241
1270
|
|
|
1242
1271
|
elif indicator_type == "RawOhlcv":
|
|
1243
1272
|
# RawOhlcv 只返回 column 名稱
|
|
1244
|
-
|
|
1245
|
-
return column
|
|
1273
|
+
return self.indicator.column
|
|
1246
1274
|
|
|
1247
1275
|
else:
|
|
1248
1276
|
raise ValueError(f"未知的指標類型: {indicator_type}")
|
|
@@ -1312,8 +1340,8 @@ class Trigger(BaseModel):
|
|
|
1312
1340
|
"""
|
|
1313
1341
|
|
|
1314
1342
|
name: str = Field(..., description="觸發器名稱")
|
|
1315
|
-
order_strategy:
|
|
1316
|
-
..., description="訂單策略(OrderStrategy enum
|
|
1343
|
+
order_strategy: OrderStrategy = Field(
|
|
1344
|
+
..., description="訂單策略(OrderStrategy enum)"
|
|
1317
1345
|
)
|
|
1318
1346
|
priority: int = Field(..., description="優先級")
|
|
1319
1347
|
note: Optional[str] = Field(None, description="備註")
|
|
@@ -1322,6 +1350,21 @@ class Trigger(BaseModel):
|
|
|
1322
1350
|
conditions: List[pl.Expr] = Field(..., description="條件列表(Polars Expr)")
|
|
1323
1351
|
price_expr: pl.Expr = Field(..., description="價格表達式(Polars Expr)")
|
|
1324
1352
|
|
|
1353
|
+
@field_validator("order_strategy", mode="before")
|
|
1354
|
+
@classmethod
|
|
1355
|
+
def convert_order_strategy(cls, v: Any) -> OrderStrategy:
|
|
1356
|
+
"""自動轉換字串為 OrderStrategy enum(保持 API 兼容性)"""
|
|
1357
|
+
if isinstance(v, str):
|
|
1358
|
+
try:
|
|
1359
|
+
return OrderStrategy(v)
|
|
1360
|
+
except ValueError:
|
|
1361
|
+
valid_values = [e.value for e in OrderStrategy]
|
|
1362
|
+
raise ValueError(
|
|
1363
|
+
f"Invalid order_strategy: '{v}'. "
|
|
1364
|
+
f"Valid values: {', '.join(valid_values)}"
|
|
1365
|
+
)
|
|
1366
|
+
return v
|
|
1367
|
+
|
|
1325
1368
|
@field_validator("conditions", mode="before")
|
|
1326
1369
|
@classmethod
|
|
1327
1370
|
def validate_conditions(cls, v: Any) -> List[pl.Expr]:
|
|
@@ -1350,25 +1393,53 @@ class Trigger(BaseModel):
|
|
|
1350
1393
|
"""序列化 price_expr 為 dict(與服務器格式一致)"""
|
|
1351
1394
|
return PolarsExprField.serialize(price_expr)
|
|
1352
1395
|
|
|
1353
|
-
|
|
1354
|
-
# 允許 pl.Expr 這種自定義類型
|
|
1355
|
-
|
|
1396
|
+
model_config = ConfigDict(
|
|
1397
|
+
arbitrary_types_allowed=True # 允許 pl.Expr 這種自定義類型
|
|
1398
|
+
)
|
|
1356
1399
|
|
|
1357
1400
|
|
|
1358
1401
|
class Blueprint(BaseModel):
|
|
1359
1402
|
"""策略藍圖"""
|
|
1360
1403
|
|
|
1361
1404
|
name: str = Field(..., description="藍圖名稱")
|
|
1362
|
-
direction:
|
|
1363
|
-
trend_type:
|
|
1364
|
-
..., description="趨勢類型"
|
|
1365
|
-
)
|
|
1405
|
+
direction: TradeDirection = Field(..., description="方向")
|
|
1406
|
+
trend_type: TrendType = Field(..., description="趨勢類型")
|
|
1366
1407
|
entry_first: bool = Field(..., description="是否優先進場")
|
|
1367
1408
|
note: str = Field(..., description="備註")
|
|
1368
1409
|
|
|
1369
1410
|
entry_triggers: List[Trigger] = Field(..., description="進場觸發器列表")
|
|
1370
1411
|
exit_triggers: List[Trigger] = Field(..., description="出場觸發器列表")
|
|
1371
1412
|
|
|
1413
|
+
@field_validator("direction", mode="before")
|
|
1414
|
+
@classmethod
|
|
1415
|
+
def convert_direction(cls, v: Any) -> TradeDirection:
|
|
1416
|
+
"""自動轉換字串為 TradeDirection enum(保持 API 兼容性)"""
|
|
1417
|
+
if isinstance(v, str):
|
|
1418
|
+
try:
|
|
1419
|
+
return TradeDirection(v)
|
|
1420
|
+
except ValueError:
|
|
1421
|
+
valid_values = [e.value for e in TradeDirection]
|
|
1422
|
+
raise ValueError(
|
|
1423
|
+
f"Invalid direction: '{v}'. "
|
|
1424
|
+
f"Valid values: {', '.join(valid_values)}"
|
|
1425
|
+
)
|
|
1426
|
+
return v
|
|
1427
|
+
|
|
1428
|
+
@field_validator("trend_type", mode="before")
|
|
1429
|
+
@classmethod
|
|
1430
|
+
def convert_trend_type(cls, v: Any) -> TrendType:
|
|
1431
|
+
"""自動轉換字串為 TrendType enum(保持 API 兼容性)"""
|
|
1432
|
+
if isinstance(v, str):
|
|
1433
|
+
try:
|
|
1434
|
+
return TrendType(v)
|
|
1435
|
+
except ValueError:
|
|
1436
|
+
valid_values = [e.value for e in TrendType]
|
|
1437
|
+
raise ValueError(
|
|
1438
|
+
f"Invalid trend_type: '{v}'. "
|
|
1439
|
+
f"Valid values: {', '.join(valid_values)}"
|
|
1440
|
+
)
|
|
1441
|
+
return v
|
|
1442
|
+
|
|
1372
1443
|
|
|
1373
1444
|
class StrategyConfig(BaseModel):
|
|
1374
1445
|
"""完整策略配置"""
|
|
@@ -1536,7 +1607,7 @@ def create_trigger(
|
|
|
1536
1607
|
name: str,
|
|
1537
1608
|
conditions: List[pl.Expr],
|
|
1538
1609
|
price_expr: pl.Expr,
|
|
1539
|
-
order_strategy:
|
|
1610
|
+
order_strategy: OrderStrategy = OrderStrategy.IMMEDIATE_ENTRY,
|
|
1540
1611
|
priority: int = 1,
|
|
1541
1612
|
note: Optional[str] = None,
|
|
1542
1613
|
) -> Trigger:
|
|
@@ -1588,10 +1659,10 @@ def create_trigger(
|
|
|
1588
1659
|
|
|
1589
1660
|
def create_blueprint(
|
|
1590
1661
|
name: str,
|
|
1591
|
-
direction:
|
|
1662
|
+
direction: TradeDirection,
|
|
1592
1663
|
entry_triggers: List[Trigger],
|
|
1593
1664
|
exit_triggers: List[Trigger],
|
|
1594
|
-
trend_type:
|
|
1665
|
+
trend_type: TrendType = TrendType.TREND,
|
|
1595
1666
|
entry_first: bool = True,
|
|
1596
1667
|
note: str = "",
|
|
1597
1668
|
) -> Blueprint:
|
|
@@ -2,7 +2,8 @@ tradepose_client/__init__.py,sha256=51JWgNgRTfKzm52NVCb9VT7rQcGd20f2DoLuF6RzReA,
|
|
|
2
2
|
tradepose_client/analysis.py,sha256=RNpk3i9_1uUXM_-K-xYbG3Ckg-MoOwQ6gSExua2N4N8,7563
|
|
3
3
|
tradepose_client/client.py,sha256=1qD4pnIjCXKgE3VXeB6EgkNuCXvDJEbmPldIjIzAVmk,1798
|
|
4
4
|
tradepose_client/enums.py,sha256=Dhvwj_diX8r_ByerwZpDSqAsquRI5FMOctm7RxQ1CDA,4887
|
|
5
|
-
tradepose_client/
|
|
5
|
+
tradepose_client/indicator_models.py,sha256=PKJAPYgRhlWGUgK346Mlyv7ZKN_Wi_8Hc5hhPKl3QUA,9727
|
|
6
|
+
tradepose_client/models.py,sha256=47D7kkmpBjxvLwLrk2a_Crvuh2BKLx0z10Q46p7FiLs,66669
|
|
6
7
|
tradepose_client/schema.py,sha256=lPcJU-1xKcGFRoeuvqovrg4LUiOLO5bRZiMVyDkKHUQ,6357
|
|
7
8
|
tradepose_client/viz.py,sha256=qu9zyAXa_Q7D4rxdgN55JyYiY24ZD3r0jf2SWtqGZYg,22989
|
|
8
9
|
tradepose_client/api/__init__.py,sha256=ezeMxOBrUDRhlqZNO-X0X9KbNJGccDseKQ95zAvyhFY,218
|
|
@@ -14,8 +15,8 @@ tradepose_client/builder/__init__.py,sha256=PIrsZcxP5cEa2_K1Zzpcf2FVc62iavKCNT6D
|
|
|
14
15
|
tradepose_client/builder/blueprint_builder.py,sha256=7ssa3J3AMezZlWEyGbkaSfwhIyGPIzgl1bOKY7owZg4,9347
|
|
15
16
|
tradepose_client/builder/indicator_wrapper.py,sha256=zfVZE1fX-2emUg8bSjlukP9rF47j8s7h2efo_fSVWWM,3581
|
|
16
17
|
tradepose_client/builder/strategy_builder.py,sha256=gEeNytZepy3t4jmPKFcZZv3lGSBC0SMQ1QCTcv-qOO0,10433
|
|
17
|
-
tradepose_client/builder/trading_context.py,sha256=
|
|
18
|
-
tradepose_client-0.1.
|
|
19
|
-
tradepose_client-0.1.
|
|
20
|
-
tradepose_client-0.1.
|
|
21
|
-
tradepose_client-0.1.
|
|
18
|
+
tradepose_client/builder/trading_context.py,sha256=6KQEFlMxh8IVT25XoEc-uU65zHpwjCRmkU1NHiy9cck,4340
|
|
19
|
+
tradepose_client-0.1.2.dist-info/METADATA,sha256=Y8jH2qNVBYs1brHfwbExMQQOd-1U92MeTT8IVyqrd4g,14056
|
|
20
|
+
tradepose_client-0.1.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
21
|
+
tradepose_client-0.1.2.dist-info/licenses/LICENSE,sha256=6qYWNkUbxmJl1iN7c4DoM1_8ASsZyXemFO1D0wXtiHk,1071
|
|
22
|
+
tradepose_client-0.1.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|