tradepose-client 0.1.0__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/__init__.py +24 -4
- tradepose_client/builder/__init__.py +22 -0
- tradepose_client/builder/blueprint_builder.py +276 -0
- tradepose_client/builder/indicator_wrapper.py +126 -0
- tradepose_client/builder/strategy_builder.py +312 -0
- tradepose_client/builder/trading_context.py +140 -0
- tradepose_client/enums.py +176 -0
- tradepose_client/indicator_models.py +300 -0
- tradepose_client/models.py +266 -261
- {tradepose_client-0.1.0.dist-info → tradepose_client-0.1.2.dist-info}/METADATA +1 -1
- tradepose_client-0.1.2.dist-info/RECORD +22 -0
- tradepose_client-0.1.0.dist-info/RECORD +0 -15
- {tradepose_client-0.1.0.dist-info → tradepose_client-0.1.2.dist-info}/WHEEL +0 -0
- {tradepose_client-0.1.0.dist-info → tradepose_client-0.1.2.dist-info}/licenses/LICENSE +0 -0
tradepose_client/models.py
CHANGED
|
@@ -9,79 +9,86 @@ 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,
|
|
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
|
+
|
|
17
|
+
# Import enums from enums.py
|
|
18
|
+
from .enums import Freq, OrderStrategy, Weekday, TradeDirection, TrendType
|
|
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
|
+
)
|
|
16
38
|
|
|
17
39
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
對應 Rust 的 Freq enum
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
MIN_1 = "1min"
|
|
25
|
-
MIN_5 = "5min"
|
|
26
|
-
MIN_15 = "15min"
|
|
27
|
-
MIN_30 = "30min"
|
|
28
|
-
HOUR_1 = "1h"
|
|
29
|
-
HOUR_4 = "4h"
|
|
30
|
-
DAY_1 = "1D"
|
|
31
|
-
WEEK_1 = "1W"
|
|
32
|
-
MONTH_1 = "1M"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
class OrderStrategy(str, Enum):
|
|
36
|
-
"""訂單策略枚舉(與 Rust OrderStrategy enum 一致)
|
|
37
|
-
|
|
38
|
-
對應 Rust 的 OrderStrategy enum,使用字符串值以便 JSON 序列化
|
|
39
|
-
|
|
40
|
-
Rust 對應關係:
|
|
41
|
-
- Rust: OrderStrategy::ImmediateEntry → Python: OrderStrategy.IMMEDIATE_ENTRY (u32: 0)
|
|
42
|
-
- Rust: OrderStrategy::FavorableDelayEntry → Python: OrderStrategy.FAVORABLE_DELAY_ENTRY (u32: 1)
|
|
43
|
-
- Rust: OrderStrategy::AdverseDelayEntry → Python: OrderStrategy.ADVERSE_DELAY_ENTRY (u32: 2)
|
|
44
|
-
- Rust: OrderStrategy::ImmediateExit → Python: OrderStrategy.IMMEDIATE_EXIT (u32: 3)
|
|
45
|
-
- Rust: OrderStrategy::StopLoss → Python: OrderStrategy.STOP_LOSS (u32: 4)
|
|
46
|
-
- Rust: OrderStrategy::TakeProfit → Python: OrderStrategy.TAKE_PROFIT (u32: 5)
|
|
47
|
-
- Rust: OrderStrategy::TrailingStop → Python: OrderStrategy.TRAILING_STOP (u32: 6)
|
|
48
|
-
- Rust: OrderStrategy::Breakeven → Python: OrderStrategy.BREAKEVEN (u32: 7)
|
|
49
|
-
- Rust: OrderStrategy::TimeoutExit → Python: OrderStrategy.TIMEOUT_EXIT (u32: 8)
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
IMMEDIATE_ENTRY = "ImmediateEntry"
|
|
53
|
-
FAVORABLE_DELAY_ENTRY = "FavorableDelayEntry"
|
|
54
|
-
ADVERSE_DELAY_ENTRY = "AdverseDelayEntry"
|
|
55
|
-
IMMEDIATE_EXIT = "ImmediateExit"
|
|
56
|
-
STOP_LOSS = "StopLoss"
|
|
57
|
-
TAKE_PROFIT = "TakeProfit"
|
|
58
|
-
TRAILING_STOP = "TrailingStop"
|
|
59
|
-
BREAKEVEN = "Breakeven"
|
|
60
|
-
TIMEOUT_EXIT = "TimeoutExit"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
class Weekday(str, Enum):
|
|
64
|
-
"""星期列舉(與 Rust Weekday enum 一致)
|
|
65
|
-
|
|
66
|
-
用於 Market Profile 的 WeeklyTime 配置
|
|
67
|
-
|
|
68
|
-
對應關係:
|
|
69
|
-
- 0 (週一) → "Mon"
|
|
70
|
-
- 1 (週二) → "Tue"
|
|
71
|
-
- 2 (週三) → "Wed"
|
|
72
|
-
- 3 (週四) → "Thu"
|
|
73
|
-
- 4 (週五) → "Fri"
|
|
74
|
-
- 5 (週六) → "Sat"
|
|
75
|
-
- 6 (週日) → "Sun"
|
|
76
|
-
"""
|
|
40
|
+
# ============================================================================
|
|
41
|
+
# Indicator Discriminated Union (强类型 Indicator 配置)
|
|
42
|
+
# ============================================================================
|
|
77
43
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
+
"""
|
|
85
92
|
|
|
86
93
|
|
|
87
94
|
class PolarsExprField:
|
|
@@ -231,8 +238,8 @@ class Indicator:
|
|
|
231
238
|
"""
|
|
232
239
|
|
|
233
240
|
@staticmethod
|
|
234
|
-
def sma(period: int, column: str = "close") ->
|
|
235
|
-
"""創建 SMA
|
|
241
|
+
def sma(period: int, column: str = "close") -> SMAIndicator:
|
|
242
|
+
"""創建 SMA 指標配置(强类型)
|
|
236
243
|
|
|
237
244
|
對應 Rust: Indicator::SMA { period, column }
|
|
238
245
|
|
|
@@ -241,72 +248,42 @@ class Indicator:
|
|
|
241
248
|
column: 計算欄位(預設 "close")
|
|
242
249
|
|
|
243
250
|
Returns:
|
|
244
|
-
|
|
251
|
+
SMAIndicator 強類型模型
|
|
245
252
|
|
|
246
253
|
Example:
|
|
247
|
-
>>> Indicator.sma(period=20)
|
|
248
|
-
>>>
|
|
254
|
+
>>> atr = Indicator.sma(period=20)
|
|
255
|
+
>>> assert isinstance(atr, SMAIndicator)
|
|
256
|
+
>>> atr_high = Indicator.sma(period=20, column="high")
|
|
249
257
|
"""
|
|
250
|
-
return
|
|
258
|
+
return SMAIndicator(period=period, column=column)
|
|
251
259
|
|
|
252
260
|
@staticmethod
|
|
253
|
-
def ema(period: int, column: str = "close") ->
|
|
254
|
-
"""創建 EMA
|
|
261
|
+
def ema(period: int, column: str = "close") -> EMAIndicator:
|
|
262
|
+
"""創建 EMA 指標配置(强类型)
|
|
255
263
|
|
|
256
264
|
對應 Rust: Indicator::EMA { period, column }
|
|
257
|
-
|
|
258
|
-
Args:
|
|
259
|
-
period: 週期
|
|
260
|
-
column: 計算欄位(預設 "close")
|
|
261
|
-
|
|
262
|
-
Returns:
|
|
263
|
-
EMA 指標配置 dict
|
|
264
|
-
|
|
265
|
-
Example:
|
|
266
|
-
>>> Indicator.ema(period=12)
|
|
267
|
-
>>> Indicator.ema(period=12, column="low")
|
|
268
265
|
"""
|
|
269
|
-
return
|
|
266
|
+
return EMAIndicator(period=period, column=column)
|
|
270
267
|
|
|
271
268
|
@staticmethod
|
|
272
|
-
def smma(period: int, column: str = "close") ->
|
|
273
|
-
"""創建 SMMA
|
|
269
|
+
def smma(period: int, column: str = "close") -> SMMAIndicator:
|
|
270
|
+
"""創建 SMMA 指標配置(强类型)
|
|
274
271
|
|
|
275
272
|
對應 Rust: Indicator::SMMA { period, column }
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
period: 週期
|
|
279
|
-
column: 計算欄位(預設 "close")
|
|
280
|
-
|
|
281
|
-
Returns:
|
|
282
|
-
SMMA 指標配置 dict
|
|
283
|
-
|
|
284
|
-
Example:
|
|
285
|
-
>>> Indicator.smma(period=14)
|
|
286
273
|
"""
|
|
287
|
-
return
|
|
274
|
+
return SMMAIndicator(period=period, column=column)
|
|
288
275
|
|
|
289
276
|
@staticmethod
|
|
290
|
-
def wma(period: int, column: str = "close") ->
|
|
291
|
-
"""創建 WMA
|
|
277
|
+
def wma(period: int, column: str = "close") -> WMAIndicator:
|
|
278
|
+
"""創建 WMA 指標配置(强类型)
|
|
292
279
|
|
|
293
280
|
對應 Rust: Indicator::WMA { period, column }
|
|
294
|
-
|
|
295
|
-
Args:
|
|
296
|
-
period: 週期
|
|
297
|
-
column: 計算欄位(預設 "close")
|
|
298
|
-
|
|
299
|
-
Returns:
|
|
300
|
-
WMA 指標配置 dict
|
|
301
|
-
|
|
302
|
-
Example:
|
|
303
|
-
>>> Indicator.wma(period=10)
|
|
304
281
|
"""
|
|
305
|
-
return
|
|
282
|
+
return WMAIndicator(period=period, column=column)
|
|
306
283
|
|
|
307
284
|
@staticmethod
|
|
308
|
-
def atr(period: int) ->
|
|
309
|
-
"""創建 ATR
|
|
285
|
+
def atr(period: int) -> ATRIndicator:
|
|
286
|
+
"""創建 ATR 指標配置(强类型)
|
|
310
287
|
|
|
311
288
|
對應 Rust: Indicator::ATR { period }
|
|
312
289
|
|
|
@@ -336,15 +313,15 @@ class Indicator:
|
|
|
336
313
|
... (pl.col("close") - 2 * pl.col("ATR|14")).alias("stop_loss")
|
|
337
314
|
... ])
|
|
338
315
|
"""
|
|
339
|
-
return
|
|
316
|
+
return ATRIndicator(period=period)
|
|
340
317
|
|
|
341
318
|
@staticmethod
|
|
342
319
|
def atr_quantile(
|
|
343
320
|
atr_column: str,
|
|
344
321
|
window: int,
|
|
345
322
|
quantile: float,
|
|
346
|
-
) ->
|
|
347
|
-
"""創建 AtrQuantile
|
|
323
|
+
) -> ATRQuantileIndicator:
|
|
324
|
+
"""創建 AtrQuantile 指標配置(强类型)
|
|
348
325
|
|
|
349
326
|
對應 Rust: Indicator::AtrQuantile { atr_column, window, quantile }
|
|
350
327
|
|
|
@@ -416,12 +393,11 @@ class Indicator:
|
|
|
416
393
|
... order_strategy="ImmediateEntry"
|
|
417
394
|
... )
|
|
418
395
|
"""
|
|
419
|
-
return
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
}
|
|
396
|
+
return ATRQuantileIndicator(
|
|
397
|
+
atr_column=atr_column,
|
|
398
|
+
window=window,
|
|
399
|
+
quantile=quantile,
|
|
400
|
+
)
|
|
425
401
|
|
|
426
402
|
@staticmethod
|
|
427
403
|
def supertrend(
|
|
@@ -431,8 +407,8 @@ class Indicator:
|
|
|
431
407
|
low: str = "low",
|
|
432
408
|
close: str = "close",
|
|
433
409
|
fields: Optional[List[str]] = None,
|
|
434
|
-
) ->
|
|
435
|
-
"""創建 SuperTrend
|
|
410
|
+
) -> SuperTrendIndicator:
|
|
411
|
+
"""創建 SuperTrend 指標配置(强类型)
|
|
436
412
|
|
|
437
413
|
對應 Rust: Indicator::SuperTrend { multiplier, volatility_column, high, low, close, fields }
|
|
438
414
|
|
|
@@ -474,17 +450,14 @@ class Indicator:
|
|
|
474
450
|
... fields=["direction", "supertrend"]
|
|
475
451
|
... )
|
|
476
452
|
"""
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
if fields is not None:
|
|
486
|
-
config["fields"] = fields
|
|
487
|
-
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
|
+
)
|
|
488
461
|
|
|
489
462
|
@staticmethod
|
|
490
463
|
def market_profile(
|
|
@@ -493,8 +466,8 @@ class Indicator:
|
|
|
493
466
|
value_area_pct: float = 0.7,
|
|
494
467
|
fields: Optional[List[str]] = None,
|
|
495
468
|
shape_config: Optional[Dict[str, Any]] = None,
|
|
496
|
-
) ->
|
|
497
|
-
"""創建 Market Profile
|
|
469
|
+
) -> MarketProfileIndicator:
|
|
470
|
+
"""創建 Market Profile 指標配置(强类型)
|
|
498
471
|
|
|
499
472
|
對應 Rust: Indicator::MarketProfile { anchor_config, tick_size, ... }
|
|
500
473
|
|
|
@@ -552,21 +525,17 @@ class Indicator:
|
|
|
552
525
|
... shape_config=shape_cfg.model_dump()
|
|
553
526
|
... )
|
|
554
527
|
"""
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
config["fields"] = fields
|
|
563
|
-
if shape_config is not None:
|
|
564
|
-
config["shape_config"] = shape_config
|
|
565
|
-
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
|
+
)
|
|
566
535
|
|
|
567
536
|
@staticmethod
|
|
568
|
-
def cci(period: int = 20) ->
|
|
569
|
-
"""創建 CCI
|
|
537
|
+
def cci(period: int = 20) -> CCIIndicator:
|
|
538
|
+
"""創建 CCI 指標配置(强类型)
|
|
570
539
|
|
|
571
540
|
對應 Rust: Indicator::CCI { period }
|
|
572
541
|
|
|
@@ -601,11 +570,11 @@ class Indicator:
|
|
|
601
570
|
>>> overbought = pl.col("1h_CCI|14") > 100
|
|
602
571
|
>>> oversold = pl.col("1h_CCI|14") < -100
|
|
603
572
|
"""
|
|
604
|
-
return
|
|
573
|
+
return CCIIndicator(period=period)
|
|
605
574
|
|
|
606
575
|
@staticmethod
|
|
607
|
-
def rsi(period: int = 14, column: str = "close") ->
|
|
608
|
-
"""創建 RSI
|
|
576
|
+
def rsi(period: int = 14, column: str = "close") -> RSIIndicator:
|
|
577
|
+
"""創建 RSI 指標配置(强类型)
|
|
609
578
|
|
|
610
579
|
對應 Rust: Indicator::RSI { period, column }
|
|
611
580
|
|
|
@@ -645,7 +614,7 @@ class Indicator:
|
|
|
645
614
|
>>> entry_long = pl.col("15min_RSI|14") < 30 # RSI < 30 做多
|
|
646
615
|
>>> entry_short = pl.col("15min_RSI|14") > 70 # RSI > 70 做空
|
|
647
616
|
"""
|
|
648
|
-
return
|
|
617
|
+
return RSIIndicator(period=period, column=column)
|
|
649
618
|
|
|
650
619
|
@staticmethod
|
|
651
620
|
def bollinger_bands(
|
|
@@ -653,8 +622,8 @@ class Indicator:
|
|
|
653
622
|
num_std: float = 2.0,
|
|
654
623
|
column: str = "close",
|
|
655
624
|
fields: Optional[List[str]] = None,
|
|
656
|
-
) ->
|
|
657
|
-
"""創建 Bollinger Bands
|
|
625
|
+
) -> BollingerBandsIndicator:
|
|
626
|
+
"""創建 Bollinger Bands 指標配置(强类型)
|
|
658
627
|
|
|
659
628
|
對應 Rust: Indicator::BollingerBands { period, num_std, column, fields }
|
|
660
629
|
|
|
@@ -711,15 +680,12 @@ class Indicator:
|
|
|
711
680
|
>>> breakout_up = pl.col("close") > upper
|
|
712
681
|
>>> breakout_down = pl.col("close") < lower
|
|
713
682
|
"""
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
if fields is not None:
|
|
721
|
-
config["fields"] = fields
|
|
722
|
-
return config
|
|
683
|
+
return BollingerBandsIndicator(
|
|
684
|
+
period=period,
|
|
685
|
+
num_std=float(num_std),
|
|
686
|
+
column=column,
|
|
687
|
+
fields=fields,
|
|
688
|
+
)
|
|
723
689
|
|
|
724
690
|
@staticmethod
|
|
725
691
|
def macd(
|
|
@@ -728,8 +694,8 @@ class Indicator:
|
|
|
728
694
|
signal_period: int = 9,
|
|
729
695
|
column: str = "close",
|
|
730
696
|
fields: Optional[List[str]] = None,
|
|
731
|
-
) ->
|
|
732
|
-
"""創建 MACD
|
|
697
|
+
) -> MACDIndicator:
|
|
698
|
+
"""創建 MACD 指標配置(强类型)
|
|
733
699
|
|
|
734
700
|
對應 Rust: Indicator::MACD { fast_period, slow_period, signal_period, column, fields }
|
|
735
701
|
|
|
@@ -786,24 +752,21 @@ class Indicator:
|
|
|
786
752
|
>>> golden_cross = macd_line > signal_line # 金叉(做多)
|
|
787
753
|
>>> death_cross = macd_line < signal_line # 死叉(做空)
|
|
788
754
|
"""
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if fields is not None:
|
|
797
|
-
config["fields"] = fields
|
|
798
|
-
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
|
+
)
|
|
799
762
|
|
|
800
763
|
@staticmethod
|
|
801
764
|
def stochastic(
|
|
802
765
|
k_period: int = 14,
|
|
803
766
|
d_period: int = 3,
|
|
804
767
|
fields: Optional[List[str]] = None,
|
|
805
|
-
) ->
|
|
806
|
-
"""創建 Stochastic
|
|
768
|
+
) -> StochasticIndicator:
|
|
769
|
+
"""創建 Stochastic 指標配置(强类型)
|
|
807
770
|
|
|
808
771
|
對應 Rust: Indicator::Stochastic { k_period, d_period, fields }
|
|
809
772
|
|
|
@@ -856,18 +819,15 @@ class Indicator:
|
|
|
856
819
|
>>> golden_cross = k_line > d_line # 金叉(做多)
|
|
857
820
|
>>> death_cross = k_line < d_line # 死叉(做空)
|
|
858
821
|
"""
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if fields is not None:
|
|
865
|
-
config["fields"] = fields
|
|
866
|
-
return config
|
|
822
|
+
return StochasticIndicator(
|
|
823
|
+
k_period=k_period,
|
|
824
|
+
d_period=d_period,
|
|
825
|
+
fields=fields,
|
|
826
|
+
)
|
|
867
827
|
|
|
868
828
|
@staticmethod
|
|
869
|
-
def adx(period: int = 14, fields: Optional[List[str]] = None) ->
|
|
870
|
-
"""創建 ADX
|
|
829
|
+
def adx(period: int = 14, fields: Optional[List[str]] = None) -> ADXIndicator:
|
|
830
|
+
"""創建 ADX 指標配置(强类型)
|
|
871
831
|
|
|
872
832
|
對應 Rust: Indicator::ADX { period, fields }
|
|
873
833
|
|
|
@@ -930,17 +890,14 @@ class Indicator:
|
|
|
930
890
|
>>> uptrend = (plus_di > minus_di) & (adx_value > 25) # 上升趨勢 + 強度足夠
|
|
931
891
|
>>> downtrend = (plus_di < minus_di) & (adx_value > 25) # 下降趨勢 + 強度足夠
|
|
932
892
|
"""
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
if fields is not None:
|
|
938
|
-
config["fields"] = fields
|
|
939
|
-
return config
|
|
893
|
+
return ADXIndicator(
|
|
894
|
+
period=period,
|
|
895
|
+
fields=fields,
|
|
896
|
+
)
|
|
940
897
|
|
|
941
898
|
@staticmethod
|
|
942
|
-
def raw_ohlcv(column: str) ->
|
|
943
|
-
"""創建 RawOhlcv
|
|
899
|
+
def raw_ohlcv(column: str) -> RawOhlcvIndicator:
|
|
900
|
+
"""創建 RawOhlcv 指標配置(强类型)
|
|
944
901
|
|
|
945
902
|
對應 Rust: Indicator::RawOhlcv { column }
|
|
946
903
|
|
|
@@ -979,7 +936,7 @@ class Indicator:
|
|
|
979
936
|
>>> # 輸出列名: "TXF_M1_SHIOAJI_FUTURE_open_1D"
|
|
980
937
|
>>> # 策略條件: if price > daily_open.col(): ...
|
|
981
938
|
"""
|
|
982
|
-
return
|
|
939
|
+
return RawOhlcvIndicator(column=column)
|
|
983
940
|
|
|
984
941
|
|
|
985
942
|
# ============================================================================
|
|
@@ -1141,17 +1098,47 @@ def create_profile_shape_config(
|
|
|
1141
1098
|
|
|
1142
1099
|
|
|
1143
1100
|
class IndicatorSpec(BaseModel):
|
|
1144
|
-
"""
|
|
1101
|
+
"""指標規範(强类型版本)"""
|
|
1145
1102
|
|
|
1146
1103
|
instrument_id: Optional[str] = Field(
|
|
1147
1104
|
None, description="交易標的 ID(可選,用於多商品場景)"
|
|
1148
1105
|
)
|
|
1149
|
-
freq:
|
|
1106
|
+
freq: Freq = Field(..., description="頻率 (Freq enum)")
|
|
1150
1107
|
shift: int = Field(1, description="位移(預設 1)")
|
|
1151
|
-
indicator:
|
|
1152
|
-
..., description="
|
|
1108
|
+
indicator: IndicatorConfig = Field(
|
|
1109
|
+
..., description="指標配置(强类型 Pydantic 模型)"
|
|
1153
1110
|
)
|
|
1154
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
|
+
|
|
1155
1142
|
def short_name(self) -> str:
|
|
1156
1143
|
"""生成指標簡短名稱(與 Rust 的 short_name 一致)
|
|
1157
1144
|
|
|
@@ -1167,53 +1154,39 @@ class IndicatorSpec(BaseModel):
|
|
|
1167
1154
|
Raises:
|
|
1168
1155
|
ValueError: 未知的指標類型
|
|
1169
1156
|
"""
|
|
1170
|
-
indicator_type = self.indicator.
|
|
1157
|
+
indicator_type = self.indicator.type
|
|
1171
1158
|
|
|
1172
1159
|
if indicator_type == "SMA":
|
|
1173
|
-
|
|
1174
|
-
column = self.indicator.get("column", "close")
|
|
1175
|
-
return f"SMA|{period}.{column}"
|
|
1160
|
+
return f"SMA|{self.indicator.period}.{self.indicator.column}"
|
|
1176
1161
|
|
|
1177
1162
|
elif indicator_type == "EMA":
|
|
1178
|
-
|
|
1179
|
-
column = self.indicator.get("column", "close")
|
|
1180
|
-
return f"EMA|{period}.{column}"
|
|
1163
|
+
return f"EMA|{self.indicator.period}.{self.indicator.column}"
|
|
1181
1164
|
|
|
1182
1165
|
elif indicator_type == "SMMA":
|
|
1183
|
-
|
|
1184
|
-
column = self.indicator.get("column", "close")
|
|
1185
|
-
return f"SMMA|{period}.{column}"
|
|
1166
|
+
return f"SMMA|{self.indicator.period}.{self.indicator.column}"
|
|
1186
1167
|
|
|
1187
1168
|
elif indicator_type == "WMA":
|
|
1188
|
-
|
|
1189
|
-
column = self.indicator.get("column", "close")
|
|
1190
|
-
return f"WMA|{period}.{column}"
|
|
1169
|
+
return f"WMA|{self.indicator.period}.{self.indicator.column}"
|
|
1191
1170
|
|
|
1192
1171
|
elif indicator_type == "ATR":
|
|
1193
|
-
period = self.indicator.get("period")
|
|
1194
1172
|
# ATR 始終返回單一數值欄位,無 quantile
|
|
1195
|
-
return f"ATR|{period}"
|
|
1173
|
+
return f"ATR|{self.indicator.period}"
|
|
1196
1174
|
|
|
1197
1175
|
elif indicator_type == "AtrQuantile":
|
|
1198
|
-
atr_column = self.indicator.get("atr_column")
|
|
1199
|
-
window = self.indicator.get("window")
|
|
1200
|
-
quantile = self.indicator.get("quantile")
|
|
1201
1176
|
# 將 quantile 轉為百分比(例如 0.5 -> 50)
|
|
1202
|
-
quantile_pct = int(quantile * 100)
|
|
1177
|
+
quantile_pct = int(self.indicator.quantile * 100)
|
|
1203
1178
|
# 格式:ATRQ|{atr_column}_Q{quantile%}_{window}
|
|
1204
|
-
return f"ATRQ|{atr_column}_Q{quantile_pct}_{window}"
|
|
1179
|
+
return f"ATRQ|{self.indicator.atr_column}_Q{quantile_pct}_{self.indicator.window}"
|
|
1205
1180
|
|
|
1206
1181
|
elif indicator_type == "SuperTrend":
|
|
1207
|
-
|
|
1208
|
-
volatility_column = self.indicator.get("volatility_column")
|
|
1209
|
-
return f"ST|{multiplier}x_{volatility_column}"
|
|
1182
|
+
return f"ST|{self.indicator.multiplier}x_{self.indicator.volatility_column}"
|
|
1210
1183
|
|
|
1211
1184
|
elif indicator_type == "MarketProfile":
|
|
1212
1185
|
# 提取 anchor_config 資訊
|
|
1213
|
-
anchor_config = self.indicator.
|
|
1186
|
+
anchor_config = self.indicator.anchor_config
|
|
1214
1187
|
end_rule = anchor_config.get("end_rule", {})
|
|
1215
1188
|
lookback_days = anchor_config.get("lookback_days", 1)
|
|
1216
|
-
tick_size = self.indicator.
|
|
1189
|
+
tick_size = self.indicator.tick_size
|
|
1217
1190
|
|
|
1218
1191
|
# 格式化時間字串(支持新舊兩種格式)
|
|
1219
1192
|
rule_type = end_rule.get("type")
|
|
@@ -1278,37 +1251,26 @@ class IndicatorSpec(BaseModel):
|
|
|
1278
1251
|
return f"MP|{time_str}_{lookback_days}_{tick_size}"
|
|
1279
1252
|
|
|
1280
1253
|
elif indicator_type == "CCI":
|
|
1281
|
-
|
|
1282
|
-
return f"CCI|{period}"
|
|
1254
|
+
return f"CCI|{self.indicator.period}"
|
|
1283
1255
|
|
|
1284
1256
|
elif indicator_type == "RSI":
|
|
1285
|
-
|
|
1286
|
-
return f"RSI|{period}"
|
|
1257
|
+
return f"RSI|{self.indicator.period}"
|
|
1287
1258
|
|
|
1288
1259
|
elif indicator_type == "BollingerBands":
|
|
1289
|
-
|
|
1290
|
-
num_std = self.indicator.get("num_std")
|
|
1291
|
-
return f"BB|{period}_{num_std}"
|
|
1260
|
+
return f"BB|{self.indicator.period}_{self.indicator.num_std}"
|
|
1292
1261
|
|
|
1293
1262
|
elif indicator_type == "MACD":
|
|
1294
|
-
|
|
1295
|
-
slow_period = self.indicator.get("slow_period")
|
|
1296
|
-
signal_period = self.indicator.get("signal_period")
|
|
1297
|
-
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}"
|
|
1298
1264
|
|
|
1299
1265
|
elif indicator_type == "Stochastic":
|
|
1300
|
-
|
|
1301
|
-
d_period = self.indicator.get("d_period")
|
|
1302
|
-
return f"STOCH|{k_period}_{d_period}"
|
|
1266
|
+
return f"STOCH|{self.indicator.k_period}_{self.indicator.d_period}"
|
|
1303
1267
|
|
|
1304
1268
|
elif indicator_type == "ADX":
|
|
1305
|
-
|
|
1306
|
-
return f"ADX|{period}"
|
|
1269
|
+
return f"ADX|{self.indicator.period}"
|
|
1307
1270
|
|
|
1308
1271
|
elif indicator_type == "RawOhlcv":
|
|
1309
1272
|
# RawOhlcv 只返回 column 名稱
|
|
1310
|
-
|
|
1311
|
-
return column
|
|
1273
|
+
return self.indicator.column
|
|
1312
1274
|
|
|
1313
1275
|
else:
|
|
1314
1276
|
raise ValueError(f"未知的指標類型: {indicator_type}")
|
|
@@ -1378,8 +1340,8 @@ class Trigger(BaseModel):
|
|
|
1378
1340
|
"""
|
|
1379
1341
|
|
|
1380
1342
|
name: str = Field(..., description="觸發器名稱")
|
|
1381
|
-
order_strategy:
|
|
1382
|
-
..., description="訂單策略(OrderStrategy enum
|
|
1343
|
+
order_strategy: OrderStrategy = Field(
|
|
1344
|
+
..., description="訂單策略(OrderStrategy enum)"
|
|
1383
1345
|
)
|
|
1384
1346
|
priority: int = Field(..., description="優先級")
|
|
1385
1347
|
note: Optional[str] = Field(None, description="備註")
|
|
@@ -1388,6 +1350,21 @@ class Trigger(BaseModel):
|
|
|
1388
1350
|
conditions: List[pl.Expr] = Field(..., description="條件列表(Polars Expr)")
|
|
1389
1351
|
price_expr: pl.Expr = Field(..., description="價格表達式(Polars Expr)")
|
|
1390
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
|
+
|
|
1391
1368
|
@field_validator("conditions", mode="before")
|
|
1392
1369
|
@classmethod
|
|
1393
1370
|
def validate_conditions(cls, v: Any) -> List[pl.Expr]:
|
|
@@ -1416,25 +1393,53 @@ class Trigger(BaseModel):
|
|
|
1416
1393
|
"""序列化 price_expr 為 dict(與服務器格式一致)"""
|
|
1417
1394
|
return PolarsExprField.serialize(price_expr)
|
|
1418
1395
|
|
|
1419
|
-
|
|
1420
|
-
# 允許 pl.Expr 這種自定義類型
|
|
1421
|
-
|
|
1396
|
+
model_config = ConfigDict(
|
|
1397
|
+
arbitrary_types_allowed=True # 允許 pl.Expr 這種自定義類型
|
|
1398
|
+
)
|
|
1422
1399
|
|
|
1423
1400
|
|
|
1424
1401
|
class Blueprint(BaseModel):
|
|
1425
1402
|
"""策略藍圖"""
|
|
1426
1403
|
|
|
1427
1404
|
name: str = Field(..., description="藍圖名稱")
|
|
1428
|
-
direction:
|
|
1429
|
-
trend_type:
|
|
1430
|
-
..., description="趨勢類型"
|
|
1431
|
-
)
|
|
1405
|
+
direction: TradeDirection = Field(..., description="方向")
|
|
1406
|
+
trend_type: TrendType = Field(..., description="趨勢類型")
|
|
1432
1407
|
entry_first: bool = Field(..., description="是否優先進場")
|
|
1433
1408
|
note: str = Field(..., description="備註")
|
|
1434
1409
|
|
|
1435
1410
|
entry_triggers: List[Trigger] = Field(..., description="進場觸發器列表")
|
|
1436
1411
|
exit_triggers: List[Trigger] = Field(..., description="出場觸發器列表")
|
|
1437
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
|
+
|
|
1438
1443
|
|
|
1439
1444
|
class StrategyConfig(BaseModel):
|
|
1440
1445
|
"""完整策略配置"""
|
|
@@ -1602,7 +1607,7 @@ def create_trigger(
|
|
|
1602
1607
|
name: str,
|
|
1603
1608
|
conditions: List[pl.Expr],
|
|
1604
1609
|
price_expr: pl.Expr,
|
|
1605
|
-
order_strategy:
|
|
1610
|
+
order_strategy: OrderStrategy = OrderStrategy.IMMEDIATE_ENTRY,
|
|
1606
1611
|
priority: int = 1,
|
|
1607
1612
|
note: Optional[str] = None,
|
|
1608
1613
|
) -> Trigger:
|
|
@@ -1654,10 +1659,10 @@ def create_trigger(
|
|
|
1654
1659
|
|
|
1655
1660
|
def create_blueprint(
|
|
1656
1661
|
name: str,
|
|
1657
|
-
direction:
|
|
1662
|
+
direction: TradeDirection,
|
|
1658
1663
|
entry_triggers: List[Trigger],
|
|
1659
1664
|
exit_triggers: List[Trigger],
|
|
1660
|
-
trend_type:
|
|
1665
|
+
trend_type: TrendType = TrendType.TREND,
|
|
1661
1666
|
entry_first: bool = True,
|
|
1662
1667
|
note: str = "",
|
|
1663
1668
|
) -> Blueprint:
|