tradepose-client 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tradepose-client might be problematic. Click here for more details.
- tradepose_client/__init__.py +156 -0
- tradepose_client/analysis.py +302 -0
- tradepose_client/api/__init__.py +8 -0
- tradepose_client/api/engine.py +59 -0
- tradepose_client/api/export.py +828 -0
- tradepose_client/api/health.py +70 -0
- tradepose_client/api/strategy.py +228 -0
- tradepose_client/client.py +58 -0
- tradepose_client/models.py +1836 -0
- tradepose_client/schema.py +186 -0
- tradepose_client/viz.py +762 -0
- tradepose_client-0.1.0.dist-info/METADATA +576 -0
- tradepose_client-0.1.0.dist-info/RECORD +15 -0
- tradepose_client-0.1.0.dist-info/WHEEL +4 -0
- tradepose_client-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""健康檢查 API"""
|
|
2
|
+
|
|
3
|
+
from typing import Dict
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HealthAPI:
|
|
8
|
+
"""健康檢查 API Mixin"""
|
|
9
|
+
|
|
10
|
+
def health(self) -> Dict:
|
|
11
|
+
"""健康檢查 - 簡單的存活探測
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
健康狀態 {"status": "ok"}
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
>>> status = client.health()
|
|
18
|
+
>>> print(status) # {"status": "ok"}
|
|
19
|
+
"""
|
|
20
|
+
response = requests.get(
|
|
21
|
+
f"{self.api_url}/health",
|
|
22
|
+
headers=self._get_headers()
|
|
23
|
+
)
|
|
24
|
+
response.raise_for_status()
|
|
25
|
+
return response.json()
|
|
26
|
+
|
|
27
|
+
def readiness(self) -> Dict:
|
|
28
|
+
"""就緒檢查 - 檢查所有依賴是否可用
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
就緒狀態
|
|
32
|
+
|
|
33
|
+
Raises:
|
|
34
|
+
requests.HTTPError: 503 (Service not ready)
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> try:
|
|
38
|
+
... status = client.readiness()
|
|
39
|
+
... print("Service is ready")
|
|
40
|
+
... except requests.HTTPError as e:
|
|
41
|
+
... if e.response.status_code == 503:
|
|
42
|
+
... print("Service not ready yet")
|
|
43
|
+
"""
|
|
44
|
+
response = requests.get(
|
|
45
|
+
f"{self.api_url}/ready",
|
|
46
|
+
headers=self._get_headers()
|
|
47
|
+
)
|
|
48
|
+
response.raise_for_status()
|
|
49
|
+
return response.json()
|
|
50
|
+
|
|
51
|
+
def system_status(self) -> Dict:
|
|
52
|
+
"""獲取完整系統狀態
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
系統狀態信息(包括緩存、策略等統計)
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
requests.HTTPError: 500
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
>>> status = client.system_status()
|
|
62
|
+
>>> print(f"Strategies: {status.get('strategies_count', 0)}")
|
|
63
|
+
>>> print(f"Cache usage: {status.get('cache_info', {})}")
|
|
64
|
+
"""
|
|
65
|
+
response = requests.get(
|
|
66
|
+
f"{self.api_url}/api/v1/status",
|
|
67
|
+
headers=self._get_headers()
|
|
68
|
+
)
|
|
69
|
+
response.raise_for_status()
|
|
70
|
+
return response.json()
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"""策略管理 API"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, List, Union
|
|
5
|
+
import requests
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class StrategyAPI:
|
|
9
|
+
"""策略管理 API Mixin"""
|
|
10
|
+
|
|
11
|
+
def list_strategies(
|
|
12
|
+
self, full: bool = False, instrument_id: Union[str, None] = None
|
|
13
|
+
) -> List[Dict]:
|
|
14
|
+
"""列出所有可用策略
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
full: True 返回完整配置,False 返回简化列表(默认)
|
|
18
|
+
instrument_id: 按商品 ID 过滤(可选)
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
策略列表(简化或完整格式)
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> # 获取简化列表
|
|
25
|
+
>>> strategies = client.list_strategies()
|
|
26
|
+
>>>
|
|
27
|
+
>>> # 获取完整配置
|
|
28
|
+
>>> full_configs = client.list_strategies(full=True)
|
|
29
|
+
>>>
|
|
30
|
+
>>> # 按商品过滤
|
|
31
|
+
>>> txf_strategies = client.list_strategies(instrument_id="TXF_M1_SHIOAJI_FUTURE")
|
|
32
|
+
>>>
|
|
33
|
+
>>> # 组合使用
|
|
34
|
+
>>> txf_full = client.list_strategies(
|
|
35
|
+
... full=True,
|
|
36
|
+
... instrument_id="TXF_M1_SHIOAJI_FUTURE"
|
|
37
|
+
... )
|
|
38
|
+
"""
|
|
39
|
+
params = {}
|
|
40
|
+
if full:
|
|
41
|
+
params["full"] = "true"
|
|
42
|
+
if instrument_id:
|
|
43
|
+
params["instrument_id"] = instrument_id
|
|
44
|
+
|
|
45
|
+
response = requests.get(
|
|
46
|
+
f"{self.api_url}/api/v1/strategies",
|
|
47
|
+
params=params,
|
|
48
|
+
headers=self._get_headers(),
|
|
49
|
+
)
|
|
50
|
+
response.raise_for_status()
|
|
51
|
+
return response.json()
|
|
52
|
+
|
|
53
|
+
def get_strategy_detail(self, strategy_name: str) -> Dict:
|
|
54
|
+
"""獲取策略詳情
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
strategy_name: 策略名稱
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
策略詳情
|
|
61
|
+
"""
|
|
62
|
+
response = requests.get(
|
|
63
|
+
f"{self.api_url}/api/v1/strategies/{strategy_name}",
|
|
64
|
+
headers=self._get_headers(),
|
|
65
|
+
)
|
|
66
|
+
response.raise_for_status()
|
|
67
|
+
return response.json()
|
|
68
|
+
|
|
69
|
+
def register_strategy(self, strategy_config, overwrite: bool = False) -> Dict:
|
|
70
|
+
"""註冊策略(支持單個或批量註冊)
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
strategy_config: StrategyConfig 實例或 List[StrategyConfig]
|
|
74
|
+
overwrite: 是否覆蓋已存在的策略(默認 False)
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
註冊結果(包含 succeeded/failed 或批量結果信息)
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
requests.HTTPError: 400 (Invalid request), 409 (Already exists), 500
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> from tradepose_client.models import StrategyConfig
|
|
84
|
+
>>>
|
|
85
|
+
>>> # 單個註冊
|
|
86
|
+
>>> strategy = StrategyConfig(...)
|
|
87
|
+
>>> response = client.register_strategy(strategy)
|
|
88
|
+
>>>
|
|
89
|
+
>>> # 覆蓋註冊
|
|
90
|
+
>>> response = client.register_strategy(strategy, overwrite=True)
|
|
91
|
+
>>>
|
|
92
|
+
>>> # 批量註冊
|
|
93
|
+
>>> strategies = [StrategyConfig(...), StrategyConfig(...)]
|
|
94
|
+
>>> response = client.register_strategy(strategies)
|
|
95
|
+
>>>
|
|
96
|
+
>>> # 批量覆蓋
|
|
97
|
+
>>> response = client.register_strategy(strategies, overwrite=True)
|
|
98
|
+
"""
|
|
99
|
+
from ..models import StrategyConfig
|
|
100
|
+
|
|
101
|
+
# 判斷是否為列表(批量)
|
|
102
|
+
if isinstance(strategy_config, list):
|
|
103
|
+
# 批量註冊:直接傳遞配置列表
|
|
104
|
+
configs = []
|
|
105
|
+
for cfg in strategy_config:
|
|
106
|
+
if isinstance(cfg, StrategyConfig):
|
|
107
|
+
configs.append(cfg.to_dict())
|
|
108
|
+
else:
|
|
109
|
+
configs.append(cfg)
|
|
110
|
+
payload = configs
|
|
111
|
+
else:
|
|
112
|
+
# 單個註冊:直接傳遞 strategy_config 對象(不需要 {"config": ...} 包裝)
|
|
113
|
+
assert isinstance(
|
|
114
|
+
strategy_config, StrategyConfig
|
|
115
|
+
), "strategy_config 必須是 StrategyConfig 實例或其列表"
|
|
116
|
+
payload = strategy_config.to_dict()
|
|
117
|
+
|
|
118
|
+
# 構建 URL(帶 overwrite 參數)
|
|
119
|
+
url = f"{self.api_url}/api/v1/strategies"
|
|
120
|
+
if overwrite:
|
|
121
|
+
url += "?overwrite=true"
|
|
122
|
+
|
|
123
|
+
response = requests.post(
|
|
124
|
+
url,
|
|
125
|
+
json=payload,
|
|
126
|
+
headers=self._get_headers(),
|
|
127
|
+
)
|
|
128
|
+
response.raise_for_status()
|
|
129
|
+
return response.json()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def delete_strategy(self, strategy_name: Union[str, List[str]]) -> Dict:
|
|
133
|
+
"""刪除策略(支持單個或批量刪除)
|
|
134
|
+
|
|
135
|
+
內部使用 POST /api/v1/strategies/delete 端點
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
strategy_name: 策略名稱(str)或策略名稱列表(List[str])
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
刪除結果(包含 deleted/not_found 信息)
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
>>> # 單個刪除
|
|
145
|
+
>>> client.delete_strategy("my_strategy")
|
|
146
|
+
>>>
|
|
147
|
+
>>> # 批量刪除
|
|
148
|
+
>>> client.delete_strategy(["strategy1", "strategy2", "strategy3"])
|
|
149
|
+
"""
|
|
150
|
+
# 判斷是否為列表
|
|
151
|
+
if isinstance(strategy_name, list):
|
|
152
|
+
names = strategy_name
|
|
153
|
+
else:
|
|
154
|
+
names = [strategy_name]
|
|
155
|
+
|
|
156
|
+
response = requests.post(
|
|
157
|
+
f"{self.api_url}/api/v1/strategies/delete",
|
|
158
|
+
json=names,
|
|
159
|
+
headers=self._get_headers(),
|
|
160
|
+
)
|
|
161
|
+
response.raise_for_status()
|
|
162
|
+
return response.json()
|
|
163
|
+
|
|
164
|
+
def clear_strategies(self) -> Dict:
|
|
165
|
+
"""清空所有策略(使用批量刪除端點)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
刪除結果(包含 deleted/not_found 信息)
|
|
169
|
+
"""
|
|
170
|
+
strategies = self.list_strategies()
|
|
171
|
+
if not strategies:
|
|
172
|
+
return {
|
|
173
|
+
"deleted": [],
|
|
174
|
+
"not_found": [],
|
|
175
|
+
"total": 0,
|
|
176
|
+
"deleted_count": 0,
|
|
177
|
+
"not_found_count": 0,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
strategy_names = [s["name"] for s in strategies]
|
|
181
|
+
return self.delete_strategy(strategy_names)
|
|
182
|
+
|
|
183
|
+
def add_blueprint(self, strategy_name: str, blueprint) -> Dict:
|
|
184
|
+
"""為策略添加進階 Blueprint
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
strategy_name: 策略名稱
|
|
188
|
+
blueprint: Blueprint 實例
|
|
189
|
+
|
|
190
|
+
Returns:
|
|
191
|
+
添加結果
|
|
192
|
+
|
|
193
|
+
Raises:
|
|
194
|
+
requests.HTTPError: 404 (Strategy not found), 500
|
|
195
|
+
|
|
196
|
+
Example:
|
|
197
|
+
>>> from tradepose_client.models import create_blueprint, create_trigger
|
|
198
|
+
>>> import polars as pl
|
|
199
|
+
>>>
|
|
200
|
+
>>> # 創建 blueprint
|
|
201
|
+
>>> entry = create_trigger(
|
|
202
|
+
... name="reversal_entry",
|
|
203
|
+
... conditions=[pl.col("rsi") < 30],
|
|
204
|
+
... price_expr=pl.col("open")
|
|
205
|
+
... )
|
|
206
|
+
>>> blueprint = create_blueprint(
|
|
207
|
+
... name="reversal_strategy",
|
|
208
|
+
... direction="Long",
|
|
209
|
+
... entry_triggers=[entry],
|
|
210
|
+
... exit_triggers=[]
|
|
211
|
+
... )
|
|
212
|
+
>>>
|
|
213
|
+
>>> # 添加到策略
|
|
214
|
+
>>> client.add_blueprint("my_strategy", blueprint)
|
|
215
|
+
"""
|
|
216
|
+
from ..models import Blueprint
|
|
217
|
+
|
|
218
|
+
assert isinstance(blueprint, Blueprint), "blueprint 必須是 Blueprint 實例"
|
|
219
|
+
|
|
220
|
+
payload = {"blueprint": blueprint.model_dump()}
|
|
221
|
+
|
|
222
|
+
response = requests.post(
|
|
223
|
+
f"{self.api_url}/api/v1/strategies/{strategy_name}/blueprints",
|
|
224
|
+
json=payload,
|
|
225
|
+
headers=self._get_headers(),
|
|
226
|
+
)
|
|
227
|
+
response.raise_for_status()
|
|
228
|
+
return response.json()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Tradepose API 客戶端"""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import redis
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from .api.strategy import StrategyAPI
|
|
8
|
+
from .api.export import ExportAPI
|
|
9
|
+
from .api.engine import EngineAPI
|
|
10
|
+
from .api.health import HealthAPI
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TradeposeClient(StrategyAPI, ExportAPI, EngineAPI, HealthAPI):
|
|
14
|
+
"""Tradepose API 客戶端
|
|
15
|
+
|
|
16
|
+
整合所有 API 功能的主客戶端類
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def __init__(
|
|
20
|
+
self,
|
|
21
|
+
api_url: str = "http://localhost:8080",
|
|
22
|
+
redis_url: str = "redis://:tradepose_password@localhost:6379",
|
|
23
|
+
api_token: Optional[str] = None,
|
|
24
|
+
):
|
|
25
|
+
"""初始化客戶端
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
api_url: Tradepose API 基礎 URL
|
|
29
|
+
redis_url: Redis 連接 URL
|
|
30
|
+
api_token: JWT Bearer Token (如果為 None,從 TRADEPOSE_API_TOKEN 環境變數讀取)
|
|
31
|
+
"""
|
|
32
|
+
self.api_url = api_url.rstrip("/")
|
|
33
|
+
self.redis_url = redis_url
|
|
34
|
+
self.api_token = api_token or os.getenv("TRADEPOSE_API_TOKEN")
|
|
35
|
+
self._redis_client = None
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def redis(self):
|
|
39
|
+
"""懶加載 Redis 客戶端"""
|
|
40
|
+
if self._redis_client is None:
|
|
41
|
+
self._redis_client = redis.from_url(self.redis_url, decode_responses=True)
|
|
42
|
+
return self._redis_client
|
|
43
|
+
|
|
44
|
+
def _get_headers(self, additional_headers: Optional[dict] = None) -> dict:
|
|
45
|
+
"""獲取包含認證信息的請求 headers
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
additional_headers: 額外的 headers (可選)
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
dict: 包含 Content-Type 和 Authorization 的 headers
|
|
52
|
+
"""
|
|
53
|
+
headers = {"Content-Type": "application/json"}
|
|
54
|
+
if self.api_token:
|
|
55
|
+
headers["Authorization"] = f"Bearer {self.api_token}"
|
|
56
|
+
if additional_headers:
|
|
57
|
+
headers.update(additional_headers)
|
|
58
|
+
return headers
|