tradepose-models 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. tradepose_models/__init__.py +44 -0
  2. tradepose_models/auth/__init__.py +13 -0
  3. tradepose_models/auth/api_keys.py +52 -0
  4. tradepose_models/auth/auth.py +20 -0
  5. tradepose_models/base.py +57 -0
  6. tradepose_models/billing/__init__.py +33 -0
  7. tradepose_models/billing/checkout.py +17 -0
  8. tradepose_models/billing/plans.py +32 -0
  9. tradepose_models/billing/subscriptions.py +34 -0
  10. tradepose_models/billing/usage.py +71 -0
  11. tradepose_models/broker/__init__.py +34 -0
  12. tradepose_models/broker/account_config.py +93 -0
  13. tradepose_models/broker/account_models.py +61 -0
  14. tradepose_models/broker/binding.py +54 -0
  15. tradepose_models/broker/connection_status.py +14 -0
  16. tradepose_models/commands/__init__.py +8 -0
  17. tradepose_models/commands/trader_command.py +80 -0
  18. tradepose_models/datafeed/__init__.py +19 -0
  19. tradepose_models/datafeed/events.py +132 -0
  20. tradepose_models/enums/__init__.py +47 -0
  21. tradepose_models/enums/account_source.py +42 -0
  22. tradepose_models/enums/broker_type.py +21 -0
  23. tradepose_models/enums/currency.py +17 -0
  24. tradepose_models/enums/engagement_phase.py +47 -0
  25. tradepose_models/enums/execution_mode.py +16 -0
  26. tradepose_models/enums/export_type.py +23 -0
  27. tradepose_models/enums/freq.py +32 -0
  28. tradepose_models/enums/indicator_type.py +46 -0
  29. tradepose_models/enums/operation_type.py +19 -0
  30. tradepose_models/enums/order_strategy.py +47 -0
  31. tradepose_models/enums/orderbook_event_type.py +29 -0
  32. tradepose_models/enums/persist_mode.py +28 -0
  33. tradepose_models/enums/stream.py +14 -0
  34. tradepose_models/enums/task_status.py +23 -0
  35. tradepose_models/enums/trade_direction.py +42 -0
  36. tradepose_models/enums/trend_type.py +22 -0
  37. tradepose_models/enums/weekday.py +30 -0
  38. tradepose_models/enums.py +32 -0
  39. tradepose_models/events/__init__.py +11 -0
  40. tradepose_models/events/order_events.py +79 -0
  41. tradepose_models/export/__init__.py +19 -0
  42. tradepose_models/export/request.py +52 -0
  43. tradepose_models/export/requests.py +75 -0
  44. tradepose_models/export/task_metadata.py +97 -0
  45. tradepose_models/gateway/__init__.py +19 -0
  46. tradepose_models/gateway/responses.py +37 -0
  47. tradepose_models/indicators/__init__.py +56 -0
  48. tradepose_models/indicators/base.py +42 -0
  49. tradepose_models/indicators/factory.py +254 -0
  50. tradepose_models/indicators/market_profile.md +60 -0
  51. tradepose_models/indicators/market_profile.py +333 -0
  52. tradepose_models/indicators/market_profile_developer.md +1782 -0
  53. tradepose_models/indicators/market_profile_trading.md +1060 -0
  54. tradepose_models/indicators/momentum.py +53 -0
  55. tradepose_models/indicators/moving_average.py +63 -0
  56. tradepose_models/indicators/other.py +40 -0
  57. tradepose_models/indicators/trend.py +80 -0
  58. tradepose_models/indicators/volatility.py +57 -0
  59. tradepose_models/instruments/__init__.py +13 -0
  60. tradepose_models/instruments/instrument.py +87 -0
  61. tradepose_models/scheduler/__init__.py +9 -0
  62. tradepose_models/scheduler/results.py +49 -0
  63. tradepose_models/schemas/__init__.py +15 -0
  64. tradepose_models/schemas/enhanced_ohlcv.py +111 -0
  65. tradepose_models/schemas/performance.py +40 -0
  66. tradepose_models/schemas/trades.py +64 -0
  67. tradepose_models/schemas.py +34 -0
  68. tradepose_models/shared.py +15 -0
  69. tradepose_models/strategy/__init__.py +52 -0
  70. tradepose_models/strategy/base.py +56 -0
  71. tradepose_models/strategy/blueprint.py +55 -0
  72. tradepose_models/strategy/config.py +142 -0
  73. tradepose_models/strategy/entities.py +104 -0
  74. tradepose_models/strategy/helpers.py +173 -0
  75. tradepose_models/strategy/indicator_spec.py +531 -0
  76. tradepose_models/strategy/performance.py +66 -0
  77. tradepose_models/strategy/portfolio.py +171 -0
  78. tradepose_models/strategy/registry.py +249 -0
  79. tradepose_models/strategy/requests.py +33 -0
  80. tradepose_models/strategy/trigger.py +77 -0
  81. tradepose_models/trading/__init__.py +55 -0
  82. tradepose_models/trading/engagement.py +160 -0
  83. tradepose_models/trading/orderbook.py +73 -0
  84. tradepose_models/trading/orders.py +137 -0
  85. tradepose_models/trading/positions.py +78 -0
  86. tradepose_models/trading/trader_commands.py +138 -0
  87. tradepose_models/trading/trades_execution.py +27 -0
  88. tradepose_models/types.py +35 -0
  89. tradepose_models/utils/__init__.py +13 -0
  90. tradepose_models/utils/rate_converter.py +112 -0
  91. tradepose_models/validators.py +32 -0
  92. tradepose_models-1.1.0.dist-info/METADATA +633 -0
  93. tradepose_models-1.1.0.dist-info/RECORD +94 -0
  94. tradepose_models-1.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,66 @@
1
+ """
2
+ Strategy Performance Model
3
+
4
+ Provides StrategyPerformance for caching strategy metrics in Redis.
5
+ Used for position size calculation and performance monitoring.
6
+ """
7
+
8
+ from datetime import datetime
9
+ from decimal import Decimal
10
+ from uuid import UUID
11
+
12
+ from pydantic import BaseModel, Field
13
+
14
+
15
+ class StrategyPerformance(BaseModel):
16
+ """Strategy performance metrics (Redis cache).
17
+
18
+ Stores calculated performance metrics for a strategy+blueprint combination.
19
+ Used by TradingDecisionJob for position size calculation.
20
+
21
+ Redis Key Pattern: performance:{user_id}:{strategy_name}:{blueprint_name}
22
+ """
23
+
24
+ strategy_name: str = Field(..., description="Strategy name")
25
+ blueprint_name: str = Field(..., description="Blueprint name")
26
+ user_id: UUID = Field(..., description="User UUID")
27
+ instrument: str = Field(..., description="Trading instrument (e.g., XAUUSD)")
28
+
29
+ # Performance metrics
30
+ win_rate: float = Field(..., description="Win rate (0-1)")
31
+ avg_pnl_pct: float = Field(..., description="Average PnL percentage")
32
+ mae_q90: float = Field(..., description="Maximum Adverse Excursion 90th percentile (%)")
33
+ mfe_q90: float = Field(..., description="Maximum Favorable Excursion 90th percentile (%)")
34
+ recovery_factor: float = Field(..., description="Recovery factor")
35
+
36
+ # Position size calculation
37
+ expected_loss_per_contract: Decimal = Field(
38
+ ...,
39
+ description="Expected loss per contract in quote currency (e.g., 40000 USD)",
40
+ )
41
+ quote_currency: str = Field(..., description="Quote currency (e.g., USD)")
42
+ contract_size: Decimal = Field(..., description="Contract size per lot")
43
+
44
+ # Cache metadata
45
+ updated_at: datetime = Field(..., description="Last update timestamp")
46
+
47
+ def calculate_position_size(self, risk_capital: Decimal) -> Decimal:
48
+ """Calculate position size based on risk capital.
49
+
50
+ Args:
51
+ risk_capital: User's risk capital in portfolio currency
52
+
53
+ Returns:
54
+ Number of contracts/lots to trade
55
+
56
+ Example:
57
+ >>> perf = StrategyPerformance(
58
+ ... expected_loss_per_contract=Decimal("40000"),
59
+ ... ...
60
+ ... )
61
+ >>> qty = perf.calculate_position_size(Decimal("80000"))
62
+ >>> # qty = 80000 / 40000 = 2 contracts
63
+ """
64
+ if self.expected_loss_per_contract <= 0:
65
+ return Decimal("0")
66
+ return risk_capital / self.expected_loss_per_contract
@@ -0,0 +1,171 @@
1
+ """
2
+ Portfolio Module
3
+
4
+ Provides Portfolio class as a selection view for indexing backtest results.
5
+ """
6
+
7
+ from decimal import Decimal
8
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ from tradepose_models.enums import BrokerType
13
+
14
+ from .registry import BlueprintSelection
15
+
16
+ if TYPE_CHECKING:
17
+ from .config import StrategyConfig
18
+ from .registry import StrategyRegistry
19
+
20
+
21
+ class Portfolio(BaseModel):
22
+ """
23
+ Portfolio - 選取視圖,用於索引回測結果
24
+
25
+ Portfolio 儲存 BlueprintSelection 引用(而非資料複製),
26
+ 需要 StrategyRegistry 來解析實際的 StrategyConfig。
27
+
28
+ Example:
29
+ >>> portfolio = registry.select(
30
+ ... name="Q1_Portfolio",
31
+ ... selections=[("VA_Breakout", "va_long"), ("VA_Breakout", "va_short")],
32
+ ... capital=100000,
33
+ ... )
34
+ >>> configs = portfolio.get_configs(registry)
35
+ >>> batch = tester.submit(strategies=configs, periods=[...])
36
+ """
37
+
38
+ name: str = Field(..., description="Portfolio 名稱")
39
+ selections: List[BlueprintSelection] = Field(
40
+ default_factory=list, description="選取的 (strategy_name, blueprint_name) 列表"
41
+ )
42
+
43
+ # 帳戶配置
44
+ capital: Decimal = Field(default=Decimal("0"), description="資金")
45
+ currency: str = Field(default="USD", description="貨幣")
46
+ account_source: Optional[str] = Field(None, description="帳戶來源")
47
+ broker_type: Optional[BrokerType] = Field(None, description="交易平台/Broker 類型")
48
+ instrument_mapping: Optional[Dict[str, str]] = Field(
49
+ None, description="商品映射 (base_instrument → target_symbol),例如 {'BTC': 'BTCUSDT'}"
50
+ )
51
+
52
+ def get_configs(self, registry: "StrategyRegistry") -> List["StrategyConfig"]:
53
+ """
54
+ 取得選取的 configs(用於傳給 BatchTester)
55
+
56
+ Args:
57
+ registry: StrategyRegistry 實例
58
+
59
+ Returns:
60
+ 單一 blueprint 的 StrategyConfig 列表
61
+
62
+ Raises:
63
+ KeyError: 如果任何選取不存在於 registry
64
+ """
65
+ configs = []
66
+ for sel in self.selections:
67
+ entry = registry.get(sel.strategy_name, sel.blueprint_name)
68
+ if entry is None:
69
+ raise KeyError(
70
+ f"Selection not found in registry: "
71
+ f"strategy='{sel.strategy_name}', blueprint='{sel.blueprint_name}'"
72
+ )
73
+ configs.append(entry.to_single_blueprint_config())
74
+ return configs
75
+
76
+ def add_selection(
77
+ self,
78
+ strategy_name: str,
79
+ blueprint_name: str,
80
+ ) -> "Portfolio":
81
+ """
82
+ 新增選取(返回新實例)
83
+
84
+ Args:
85
+ strategy_name: 策略名稱
86
+ blueprint_name: Blueprint 名稱
87
+
88
+ Returns:
89
+ 新的 Portfolio 實例
90
+ """
91
+ selection = BlueprintSelection(strategy_name=strategy_name, blueprint_name=blueprint_name)
92
+ if selection in self.selections:
93
+ return self
94
+ return self.model_copy(update={"selections": [*self.selections, selection]})
95
+
96
+ def remove_selection(
97
+ self,
98
+ strategy_name: str,
99
+ blueprint_name: str,
100
+ ) -> "Portfolio":
101
+ """
102
+ 移除選取(返回新實例)
103
+
104
+ Args:
105
+ strategy_name: 策略名稱
106
+ blueprint_name: Blueprint 名稱
107
+
108
+ Returns:
109
+ 新的 Portfolio 實例
110
+ """
111
+ selection = BlueprintSelection(strategy_name=strategy_name, blueprint_name=blueprint_name)
112
+ if selection not in self.selections:
113
+ return self
114
+ return self.model_copy(
115
+ update={"selections": [s for s in self.selections if s != selection]}
116
+ )
117
+
118
+ @property
119
+ def strategy_names(self) -> List[str]:
120
+ """取得所有策略名稱(去重)"""
121
+ return list(set(sel.strategy_name for sel in self.selections))
122
+
123
+ @property
124
+ def selection_count(self) -> int:
125
+ """選取數量"""
126
+ return len(self.selections)
127
+
128
+ def to_dict(self) -> Dict[str, Any]:
129
+ """轉換為字典"""
130
+ return self.model_dump(mode="json", exclude_none=True)
131
+
132
+ def to_json(self, indent: int = 2) -> str:
133
+ """序列化為 JSON 字串"""
134
+ return self.model_dump_json(indent=indent, exclude_none=True)
135
+
136
+ def save(self, filepath: str) -> None:
137
+ """儲存至 JSON 檔案"""
138
+ with open(filepath, "w", encoding="utf-8") as f:
139
+ f.write(self.to_json())
140
+
141
+ @classmethod
142
+ def load(cls, filepath: str) -> "Portfolio":
143
+ """從 JSON 檔案載入"""
144
+ with open(filepath, "r", encoding="utf-8") as f:
145
+ return cls.model_validate_json(f.read())
146
+
147
+ @classmethod
148
+ def from_api(cls, api_response: Union[Dict[str, Any], str]) -> "Portfolio":
149
+ """從 API 響應建立"""
150
+ if isinstance(api_response, str):
151
+ return cls.model_validate_json(api_response)
152
+ return cls.model_validate(api_response)
153
+
154
+ def __len__(self) -> int:
155
+ """選取數量"""
156
+ return len(self.selections)
157
+
158
+ def __contains__(self, item: Tuple[str, str]) -> bool:
159
+ """檢查選取是否存在"""
160
+ selection = BlueprintSelection(strategy_name=item[0], blueprint_name=item[1])
161
+ return selection in self.selections
162
+
163
+ def __iter__(self):
164
+ """迭代選取"""
165
+ return iter(self.selections)
166
+
167
+ def __repr__(self) -> str:
168
+ return (
169
+ f"Portfolio(name={self.name!r}, selections={len(self.selections)}, "
170
+ f"capital={self.capital}, currency={self.currency!r})"
171
+ )
@@ -0,0 +1,249 @@
1
+ """
2
+ Strategy Registry Module
3
+
4
+ Provides StrategyRegistry for managing strategies with (strategy_name, blueprint_name)
5
+ as unique keys. Low-coupling design - does not call external objects directly.
6
+ """
7
+
8
+ from typing import TYPE_CHECKING, Dict, Iterator, List, Optional, Tuple
9
+
10
+ from pydantic import BaseModel, PrivateAttr
11
+
12
+ from .blueprint import Blueprint
13
+ from .config import StrategyConfig
14
+
15
+ if TYPE_CHECKING:
16
+ from .portfolio import Portfolio
17
+
18
+
19
+ class BlueprintSelection(BaseModel):
20
+ """選取鍵,代表 Registry 中的一個 entry"""
21
+
22
+ strategy_name: str
23
+ blueprint_name: str
24
+
25
+ def to_tuple(self) -> Tuple[str, str]:
26
+ """轉換為 tuple"""
27
+ return (self.strategy_name, self.blueprint_name)
28
+
29
+ @classmethod
30
+ def from_tuple(cls, t: Tuple[str, str]) -> "BlueprintSelection":
31
+ """從 tuple 建立"""
32
+ return cls(strategy_name=t[0], blueprint_name=t[1])
33
+
34
+ def __hash__(self) -> int:
35
+ return hash(self.to_tuple())
36
+
37
+ def __eq__(self, other: object) -> bool:
38
+ if isinstance(other, BlueprintSelection):
39
+ return self.to_tuple() == other.to_tuple()
40
+ return False
41
+
42
+ def __repr__(self) -> str:
43
+ return f"BlueprintSelection({self.strategy_name!r}, {self.blueprint_name!r})"
44
+
45
+
46
+ class RegistryEntry(BaseModel):
47
+ """Registry entry,儲存對原始 StrategyConfig 的引用"""
48
+
49
+ strategy: StrategyConfig
50
+ blueprint_name: str
51
+
52
+ model_config = {"arbitrary_types_allowed": True}
53
+
54
+ @property
55
+ def key(self) -> BlueprintSelection:
56
+ """取得選取鍵"""
57
+ return BlueprintSelection(
58
+ strategy_name=self.strategy.name, blueprint_name=self.blueprint_name
59
+ )
60
+
61
+ @property
62
+ def blueprint(self) -> Blueprint:
63
+ """取得此 entry 對應的 Blueprint"""
64
+ if self.strategy.base_blueprint.name == self.blueprint_name:
65
+ return self.strategy.base_blueprint
66
+ for bp in self.strategy.advanced_blueprints:
67
+ if bp.name == self.blueprint_name:
68
+ return bp
69
+ raise ValueError(
70
+ f"Blueprint '{self.blueprint_name}' not found in strategy '{self.strategy.name}'"
71
+ )
72
+
73
+ def to_single_blueprint_config(self) -> StrategyConfig:
74
+ """轉換為單一 blueprint 的 StrategyConfig"""
75
+ return StrategyConfig(
76
+ name=f"{self.strategy.name}__{self.blueprint_name}",
77
+ base_instrument=self.strategy.base_instrument,
78
+ base_freq=self.strategy.base_freq,
79
+ note=self.strategy.note,
80
+ volatility_indicator=self.strategy.volatility_indicator,
81
+ indicators=self.strategy.indicators,
82
+ base_blueprint=self.blueprint,
83
+ advanced_blueprints=[],
84
+ )
85
+
86
+ def __repr__(self) -> str:
87
+ return f"RegistryEntry({self.strategy.name!r}, {self.blueprint_name!r})"
88
+
89
+
90
+ class StrategyRegistry(BaseModel):
91
+ """
92
+ 策略註冊表 - 低耦合設計,只負責儲存和保證唯一性
93
+
94
+ 使用 (strategy_name, blueprint_name) 作為唯一 key。
95
+ 當加入有多個 blueprints 的 StrategyConfig 時,自動拆成多個 entries。
96
+
97
+ Example:
98
+ >>> registry = StrategyRegistry()
99
+ >>> strategy = StrategyConfig.load("va_breakout.json")
100
+ >>> registry.add(strategy) # 自動拆成多個 entries
101
+ >>> configs = registry.get_configs() # 取得所有 configs
102
+ >>> # 使用者自己傳給 BatchTester
103
+ >>> batch = tester.submit(strategies=configs, periods=[...])
104
+ """
105
+
106
+ _entries: Dict[Tuple[str, str], RegistryEntry] = PrivateAttr(default_factory=dict)
107
+ _strategies: Dict[str, StrategyConfig] = PrivateAttr(default_factory=dict)
108
+
109
+ def add(self, strategy: StrategyConfig) -> List[BlueprintSelection]:
110
+ """
111
+ 加入策略,自動拆分所有 blueprints
112
+
113
+ Args:
114
+ strategy: 要加入的 StrategyConfig
115
+
116
+ Returns:
117
+ 建立的 BlueprintSelection 列表
118
+
119
+ Raises:
120
+ ValueError: 如果任何 (strategy_name, blueprint_name) 已存在
121
+ """
122
+ all_bps = [strategy.base_blueprint] + list(strategy.advanced_blueprints)
123
+
124
+ # 檢查重複
125
+ for bp in all_bps:
126
+ key = (strategy.name, bp.name)
127
+ if key in self._entries:
128
+ raise ValueError(
129
+ f"Entry already exists: strategy='{strategy.name}', blueprint='{bp.name}'"
130
+ )
131
+
132
+ # 儲存原始策略
133
+ self._strategies[strategy.name] = strategy
134
+
135
+ # 建立 entries
136
+ created = []
137
+ for bp in all_bps:
138
+ key = (strategy.name, bp.name)
139
+ self._entries[key] = RegistryEntry(strategy=strategy, blueprint_name=bp.name)
140
+ created.append(BlueprintSelection(strategy_name=strategy.name, blueprint_name=bp.name))
141
+
142
+ return created
143
+
144
+ def add_or_replace(self, strategy: StrategyConfig) -> List[BlueprintSelection]:
145
+ """加入或取代策略"""
146
+ if strategy.name in self._strategies:
147
+ self.remove(strategy.name)
148
+ return self.add(strategy)
149
+
150
+ def get(self, strategy_name: str, blueprint_name: str) -> Optional[RegistryEntry]:
151
+ """取得特定 entry"""
152
+ return self._entries.get((strategy_name, blueprint_name))
153
+
154
+ def get_strategy(self, strategy_name: str) -> Optional[StrategyConfig]:
155
+ """取得原始 StrategyConfig(包含所有 blueprints)"""
156
+ return self._strategies.get(strategy_name)
157
+
158
+ def get_configs(self) -> List[StrategyConfig]:
159
+ """取得所有單一 blueprint configs(用於傳給 BatchTester)"""
160
+ return [entry.to_single_blueprint_config() for entry in self._entries.values()]
161
+
162
+ def remove(self, strategy_name: str) -> int:
163
+ """
164
+ 移除策略的所有 entries
165
+
166
+ Returns:
167
+ 移除的 entry 數量
168
+ """
169
+ keys = [k for k in self._entries if k[0] == strategy_name]
170
+ for k in keys:
171
+ del self._entries[k]
172
+ self._strategies.pop(strategy_name, None)
173
+ return len(keys)
174
+
175
+ def select(
176
+ self,
177
+ name: str,
178
+ selections: List[Tuple[str, str]],
179
+ capital: float = 0,
180
+ currency: str = "USD",
181
+ account_source: Optional[str] = None,
182
+ platform: Optional[str] = None,
183
+ ) -> "Portfolio":
184
+ """
185
+ 建立 Portfolio
186
+
187
+ Args:
188
+ name: Portfolio 名稱
189
+ selections: 選取的 (strategy_name, blueprint_name) 列表
190
+ capital: 資金
191
+ currency: 貨幣
192
+ account_source: 帳戶來源
193
+ platform: 交易平台
194
+
195
+ Returns:
196
+ Portfolio 實例
197
+
198
+ Raises:
199
+ KeyError: 如果任何選取不存在
200
+ """
201
+ for sel in selections:
202
+ if sel not in self._entries:
203
+ raise KeyError(f"Selection not found: strategy='{sel[0]}', blueprint='{sel[1]}'")
204
+
205
+ from .portfolio import Portfolio
206
+
207
+ return Portfolio(
208
+ name=name,
209
+ selections=[
210
+ BlueprintSelection(strategy_name=s[0], blueprint_name=s[1]) for s in selections
211
+ ],
212
+ capital=capital,
213
+ currency=currency,
214
+ account_source=account_source,
215
+ platform=platform,
216
+ )
217
+
218
+ def keys(self) -> List[BlueprintSelection]:
219
+ """取得所有選取鍵"""
220
+ return [BlueprintSelection(strategy_name=k[0], blueprint_name=k[1]) for k in self._entries]
221
+
222
+ def strategy_names(self) -> List[str]:
223
+ """取得所有策略名稱"""
224
+ return list(self._strategies.keys())
225
+
226
+ def blueprint_names(self, strategy_name: str) -> List[str]:
227
+ """取得特定策略的所有 blueprint 名稱"""
228
+ return [k[1] for k in self._entries if k[0] == strategy_name]
229
+
230
+ def __len__(self) -> int:
231
+ """Entry 數量"""
232
+ return len(self._entries)
233
+
234
+ def __contains__(self, key: Tuple[str, str]) -> bool:
235
+ """檢查 entry 是否存在"""
236
+ return key in self._entries
237
+
238
+ def __getitem__(self, key: Tuple[str, str]) -> RegistryEntry:
239
+ """取得 entry: registry[('strategy', 'blueprint')]"""
240
+ if key not in self._entries:
241
+ raise KeyError(f"Entry not found: strategy='{key[0]}', blueprint='{key[1]}'")
242
+ return self._entries[key]
243
+
244
+ def __iter__(self) -> Iterator[RegistryEntry]:
245
+ """迭代所有 entries"""
246
+ return iter(self._entries.values())
247
+
248
+ def __repr__(self) -> str:
249
+ return f"StrategyRegistry(strategies={len(self._strategies)}, entries={len(self._entries)})"
@@ -0,0 +1,33 @@
1
+ """Strategy API request/response models."""
2
+
3
+ from uuid import UUID
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class RegisterStrategyRequest(BaseModel):
9
+ """Request model for registering a strategy."""
10
+
11
+ strategy_code: str = Field(..., description="Strategy configuration Python code")
12
+ overwrite: bool = Field(default=False, description="Overwrite if strategy exists")
13
+
14
+
15
+ class RegisterStrategyResponse(BaseModel):
16
+ """Response model for strategy registration."""
17
+
18
+ task_id: UUID
19
+ message: str
20
+
21
+
22
+ class ListStrategiesRequest(BaseModel):
23
+ """Request model for listing strategies."""
24
+
25
+ full: bool = Field(default=False, description="Return full configs or summary")
26
+ instrument_id: str | None = Field(default=None, description="Filter by instrument ID")
27
+
28
+
29
+ class ListStrategiesResponse(BaseModel):
30
+ """Response model for listing strategies."""
31
+
32
+ task_id: UUID
33
+ message: str
@@ -0,0 +1,77 @@
1
+ """
2
+ Trigger Model
3
+
4
+ Provides the Trigger class for entry/exit triggers with conditions and price expressions.
5
+ """
6
+
7
+ from typing import Any, List, Optional
8
+
9
+ import polars as pl
10
+ from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
11
+
12
+ from ..enums import OrderStrategy
13
+ from ..indicators import PolarsExprField
14
+
15
+
16
+ class Trigger(BaseModel):
17
+ """進出場觸發器
18
+
19
+ 使用方式:
20
+ - 讀取:trigger.conditions 直接得到 List[pl.Expr]
21
+ - 寫入:可直接賦值 pl.col("close") > 100
22
+ """
23
+
24
+ name: str = Field(..., description="觸發器名稱")
25
+ order_strategy: OrderStrategy = Field(..., description="訂單策略(OrderStrategy enum)")
26
+ priority: int = Field(..., description="優先級")
27
+ note: Optional[str] = Field(None, description="備註")
28
+
29
+ # 直接使用 pl.Expr 類型
30
+ conditions: List[pl.Expr] = Field(..., description="條件列表(Polars Expr)")
31
+ price_expr: pl.Expr = Field(..., description="價格表達式(Polars Expr)")
32
+
33
+ @field_validator("order_strategy", mode="before")
34
+ @classmethod
35
+ def convert_order_strategy(cls, v: Any) -> OrderStrategy:
36
+ """自動轉換字串為 OrderStrategy enum(保持 API 兼容性)"""
37
+ if isinstance(v, str):
38
+ try:
39
+ return OrderStrategy(v)
40
+ except ValueError:
41
+ valid_values = [e.value for e in OrderStrategy]
42
+ raise ValueError(
43
+ f"Invalid order_strategy: '{v}'. Valid values: {', '.join(valid_values)}"
44
+ )
45
+ return v
46
+
47
+ @field_validator("conditions", mode="before")
48
+ @classmethod
49
+ def validate_conditions(cls, v: Any) -> List[pl.Expr]:
50
+ """自動轉換 conditions 為 List[pl.Expr]"""
51
+ if not v:
52
+ return []
53
+
54
+ result = []
55
+ for item in v:
56
+ result.append(PolarsExprField.deserialize(item))
57
+ return result
58
+
59
+ @field_validator("price_expr", mode="before")
60
+ @classmethod
61
+ def validate_price_expr(cls, v: Any) -> pl.Expr:
62
+ """自動轉換 price_expr 為 pl.Expr"""
63
+ return PolarsExprField.deserialize(v)
64
+
65
+ @field_serializer("conditions")
66
+ def serialize_conditions(self, conditions: List[pl.Expr]) -> List[dict]:
67
+ """序列化 conditions 為 dict 列表(與服務器格式一致)"""
68
+ return [PolarsExprField.serialize(expr) for expr in conditions]
69
+
70
+ @field_serializer("price_expr")
71
+ def serialize_price_expr(self, price_expr: pl.Expr) -> dict:
72
+ """序列化 price_expr 為 dict(與服務器格式一致)"""
73
+ return PolarsExprField.serialize(price_expr)
74
+
75
+ model_config = ConfigDict(
76
+ arbitrary_types_allowed=True # 允許 pl.Expr 這種自定義類型
77
+ )
@@ -0,0 +1,55 @@
1
+ """Trading-related models (orders, positions, executions, engagements)."""
2
+
3
+ from tradepose_models.trading.engagement import Engagement
4
+ from tradepose_models.trading.orderbook import OrderbookEntry
5
+ from tradepose_models.trading.orders import (
6
+ ExecutionReport,
7
+ Order,
8
+ OrderSide,
9
+ OrderStatus,
10
+ OrderStrategy,
11
+ OrderSubmitRequest,
12
+ OrderType,
13
+ TimeInForce,
14
+ )
15
+ from tradepose_models.trading.positions import (
16
+ ClosedPosition,
17
+ Position,
18
+ PositionSide,
19
+ )
20
+ from tradepose_models.trading.trader_commands import (
21
+ BaseTraderCommand,
22
+ CancelOrderCommand,
23
+ ExecuteOrderCommand,
24
+ ModifyOrderCommand,
25
+ SyncBrokerStatusCommand,
26
+ )
27
+ from tradepose_models.trading.trades_execution import TradeExecution
28
+
29
+ __all__ = [
30
+ # Orders
31
+ "Order",
32
+ "OrderSubmitRequest",
33
+ "ExecutionReport",
34
+ "OrderSide",
35
+ "OrderType",
36
+ "OrderStatus",
37
+ "TimeInForce",
38
+ "OrderStrategy",
39
+ # Positions
40
+ "Position",
41
+ "ClosedPosition",
42
+ "PositionSide",
43
+ # Trade Executions
44
+ "TradeExecution",
45
+ # Engagements
46
+ "Engagement",
47
+ # Orderbook
48
+ "OrderbookEntry",
49
+ # Trader Commands
50
+ "BaseTraderCommand",
51
+ "ExecuteOrderCommand",
52
+ "CancelOrderCommand",
53
+ "ModifyOrderCommand",
54
+ "SyncBrokerStatusCommand",
55
+ ]