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.
- tradepose_models/__init__.py +44 -0
- tradepose_models/auth/__init__.py +13 -0
- tradepose_models/auth/api_keys.py +52 -0
- tradepose_models/auth/auth.py +20 -0
- tradepose_models/base.py +57 -0
- tradepose_models/billing/__init__.py +33 -0
- tradepose_models/billing/checkout.py +17 -0
- tradepose_models/billing/plans.py +32 -0
- tradepose_models/billing/subscriptions.py +34 -0
- tradepose_models/billing/usage.py +71 -0
- tradepose_models/broker/__init__.py +34 -0
- tradepose_models/broker/account_config.py +93 -0
- tradepose_models/broker/account_models.py +61 -0
- tradepose_models/broker/binding.py +54 -0
- tradepose_models/broker/connection_status.py +14 -0
- tradepose_models/commands/__init__.py +8 -0
- tradepose_models/commands/trader_command.py +80 -0
- tradepose_models/datafeed/__init__.py +19 -0
- tradepose_models/datafeed/events.py +132 -0
- tradepose_models/enums/__init__.py +47 -0
- tradepose_models/enums/account_source.py +42 -0
- tradepose_models/enums/broker_type.py +21 -0
- tradepose_models/enums/currency.py +17 -0
- tradepose_models/enums/engagement_phase.py +47 -0
- tradepose_models/enums/execution_mode.py +16 -0
- tradepose_models/enums/export_type.py +23 -0
- tradepose_models/enums/freq.py +32 -0
- tradepose_models/enums/indicator_type.py +46 -0
- tradepose_models/enums/operation_type.py +19 -0
- tradepose_models/enums/order_strategy.py +47 -0
- tradepose_models/enums/orderbook_event_type.py +29 -0
- tradepose_models/enums/persist_mode.py +28 -0
- tradepose_models/enums/stream.py +14 -0
- tradepose_models/enums/task_status.py +23 -0
- tradepose_models/enums/trade_direction.py +42 -0
- tradepose_models/enums/trend_type.py +22 -0
- tradepose_models/enums/weekday.py +30 -0
- tradepose_models/enums.py +32 -0
- tradepose_models/events/__init__.py +11 -0
- tradepose_models/events/order_events.py +79 -0
- tradepose_models/export/__init__.py +19 -0
- tradepose_models/export/request.py +52 -0
- tradepose_models/export/requests.py +75 -0
- tradepose_models/export/task_metadata.py +97 -0
- tradepose_models/gateway/__init__.py +19 -0
- tradepose_models/gateway/responses.py +37 -0
- tradepose_models/indicators/__init__.py +56 -0
- tradepose_models/indicators/base.py +42 -0
- tradepose_models/indicators/factory.py +254 -0
- tradepose_models/indicators/market_profile.md +60 -0
- tradepose_models/indicators/market_profile.py +333 -0
- tradepose_models/indicators/market_profile_developer.md +1782 -0
- tradepose_models/indicators/market_profile_trading.md +1060 -0
- tradepose_models/indicators/momentum.py +53 -0
- tradepose_models/indicators/moving_average.py +63 -0
- tradepose_models/indicators/other.py +40 -0
- tradepose_models/indicators/trend.py +80 -0
- tradepose_models/indicators/volatility.py +57 -0
- tradepose_models/instruments/__init__.py +13 -0
- tradepose_models/instruments/instrument.py +87 -0
- tradepose_models/scheduler/__init__.py +9 -0
- tradepose_models/scheduler/results.py +49 -0
- tradepose_models/schemas/__init__.py +15 -0
- tradepose_models/schemas/enhanced_ohlcv.py +111 -0
- tradepose_models/schemas/performance.py +40 -0
- tradepose_models/schemas/trades.py +64 -0
- tradepose_models/schemas.py +34 -0
- tradepose_models/shared.py +15 -0
- tradepose_models/strategy/__init__.py +52 -0
- tradepose_models/strategy/base.py +56 -0
- tradepose_models/strategy/blueprint.py +55 -0
- tradepose_models/strategy/config.py +142 -0
- tradepose_models/strategy/entities.py +104 -0
- tradepose_models/strategy/helpers.py +173 -0
- tradepose_models/strategy/indicator_spec.py +531 -0
- tradepose_models/strategy/performance.py +66 -0
- tradepose_models/strategy/portfolio.py +171 -0
- tradepose_models/strategy/registry.py +249 -0
- tradepose_models/strategy/requests.py +33 -0
- tradepose_models/strategy/trigger.py +77 -0
- tradepose_models/trading/__init__.py +55 -0
- tradepose_models/trading/engagement.py +160 -0
- tradepose_models/trading/orderbook.py +73 -0
- tradepose_models/trading/orders.py +137 -0
- tradepose_models/trading/positions.py +78 -0
- tradepose_models/trading/trader_commands.py +138 -0
- tradepose_models/trading/trades_execution.py +27 -0
- tradepose_models/types.py +35 -0
- tradepose_models/utils/__init__.py +13 -0
- tradepose_models/utils/rate_converter.py +112 -0
- tradepose_models/validators.py +32 -0
- tradepose_models-1.1.0.dist-info/METADATA +633 -0
- tradepose_models-1.1.0.dist-info/RECORD +94 -0
- tradepose_models-1.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task status enumeration for export tasks
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TaskStatus(int, Enum):
|
|
9
|
+
"""任務狀態枚舉(與 Worker TaskStatus enum 一致)
|
|
10
|
+
|
|
11
|
+
對應 Worker 的 TaskStatus enum,用於追蹤 export 任務的執行狀態
|
|
12
|
+
|
|
13
|
+
Rust 對應關係(#[repr(i16)]):
|
|
14
|
+
- Rust: TaskStatus::Pending = 0 → Python: TaskStatus.PENDING = 0
|
|
15
|
+
- Rust: TaskStatus::Processing = 1 → Python: TaskStatus.PROCESSING = 1
|
|
16
|
+
- Rust: TaskStatus::Completed = 2 → Python: TaskStatus.COMPLETED = 2
|
|
17
|
+
- Rust: TaskStatus::Failed = 3 → Python: TaskStatus.FAILED = 3
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
PENDING = 0 # 待處理(Gateway 初始狀態)
|
|
21
|
+
PROCESSING = 1 # 處理中(Worker 開始執行)
|
|
22
|
+
COMPLETED = 2 # 已完成(Worker 成功完成)
|
|
23
|
+
FAILED = 3 # 失敗(Worker 執行失敗或超時)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Trade direction enumeration."""
|
|
2
|
+
|
|
3
|
+
from enum import Enum
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TradeDirection(str, Enum):
|
|
7
|
+
"""Trade direction enum.
|
|
8
|
+
|
|
9
|
+
Used to specify the trading direction in Blueprint.
|
|
10
|
+
String values match Rust's TradeDirection enum.
|
|
11
|
+
|
|
12
|
+
Values:
|
|
13
|
+
LONG: Long trades only (multiplier: 1)
|
|
14
|
+
SHORT: Short trades only (multiplier: -1)
|
|
15
|
+
BOTH: Both long and short trades (currently not fully supported)
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
LONG = "Long"
|
|
19
|
+
SHORT = "Short"
|
|
20
|
+
BOTH = "Both"
|
|
21
|
+
|
|
22
|
+
def to_int(self) -> int:
|
|
23
|
+
"""Convert to integer multiplier (1=Long, -1=Short).
|
|
24
|
+
|
|
25
|
+
Matches Rust's direction_multiplier() function.
|
|
26
|
+
"""
|
|
27
|
+
if self == TradeDirection.LONG:
|
|
28
|
+
return 1
|
|
29
|
+
elif self == TradeDirection.SHORT:
|
|
30
|
+
return -1
|
|
31
|
+
else:
|
|
32
|
+
raise ValueError(f"Cannot convert {self} to int multiplier")
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def from_int(cls, value: int) -> "TradeDirection":
|
|
36
|
+
"""Create from integer multiplier (1=Long, -1=Short)."""
|
|
37
|
+
if value == 1:
|
|
38
|
+
return cls.LONG
|
|
39
|
+
elif value == -1:
|
|
40
|
+
return cls.SHORT
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(f"Invalid direction int: {value}. Expected 1 or -1.")
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trend type enumeration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TrendType(str, Enum):
|
|
9
|
+
"""
|
|
10
|
+
Trend type enum
|
|
11
|
+
|
|
12
|
+
Used to categorize strategy trading style.
|
|
13
|
+
|
|
14
|
+
Values:
|
|
15
|
+
TREND: Trend-following strategies
|
|
16
|
+
RANGE: Range-bound/mean-reversion strategies
|
|
17
|
+
REVERSAL: Reversal/counter-trend strategies
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
TREND = "Trend"
|
|
21
|
+
RANGE = "Range"
|
|
22
|
+
REVERSAL = "Reversal"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Weekday enumeration
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Weekday(str, Enum):
|
|
9
|
+
"""
|
|
10
|
+
Weekday enum (aligned with Rust Weekday enum)
|
|
11
|
+
|
|
12
|
+
Used for Market Profile WeeklyTime configuration.
|
|
13
|
+
|
|
14
|
+
Mapping:
|
|
15
|
+
MON (Monday) = 0
|
|
16
|
+
TUE (Tuesday) = 1
|
|
17
|
+
WED (Wednesday) = 2
|
|
18
|
+
THU (Thursday) = 3
|
|
19
|
+
FRI (Friday) = 4
|
|
20
|
+
SAT (Saturday) = 5
|
|
21
|
+
SUN (Sunday) = 6
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
MON = "Mon"
|
|
25
|
+
TUE = "Tue"
|
|
26
|
+
WED = "Wed"
|
|
27
|
+
THU = "Thu"
|
|
28
|
+
FRI = "Fri"
|
|
29
|
+
SAT = "Sat"
|
|
30
|
+
SUN = "Sun"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Shared enumerations (共用枚舉).
|
|
2
|
+
|
|
3
|
+
This module contains enum types that are used across multiple packages
|
|
4
|
+
to ensure consistency and type safety.
|
|
5
|
+
|
|
6
|
+
這個模組包含跨多個包使用的枚舉類型,以確保一致性和類型安全。
|
|
7
|
+
|
|
8
|
+
Example usage:
|
|
9
|
+
from tradepose_models.enums import TaskStatus, PlanTier
|
|
10
|
+
|
|
11
|
+
# Use in gateway
|
|
12
|
+
task.status = TaskStatus.PROCESSING
|
|
13
|
+
|
|
14
|
+
# Use in client
|
|
15
|
+
if response.status == TaskStatus.COMPLETED:
|
|
16
|
+
print("Task finished!")
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Placeholder for shared enums
|
|
21
|
+
# Models will be migrated here from gateway as needed
|
|
22
|
+
# 共用枚舉將根據需要從 gateway 遷移到這裡
|
|
23
|
+
|
|
24
|
+
# Example structure:
|
|
25
|
+
# class TaskStatus(str, Enum):
|
|
26
|
+
# """Task execution status."""
|
|
27
|
+
# PENDING = "pending"
|
|
28
|
+
# PROCESSING = "processing"
|
|
29
|
+
# COMPLETED = "completed"
|
|
30
|
+
# FAILED = "failed"
|
|
31
|
+
|
|
32
|
+
__all__ = []
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Event models for TradePose trading system.
|
|
2
|
+
|
|
3
|
+
Contains order event models for the trading decision pipeline.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from tradepose_models.events.order_events import EntryOrderEvent, ExitOrderEvent
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"EntryOrderEvent",
|
|
10
|
+
"ExitOrderEvent",
|
|
11
|
+
]
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Order Event Models for Trading System.
|
|
2
|
+
|
|
3
|
+
Provides EntryOrderEvent and ExitOrderEvent for the trading decision pipeline.
|
|
4
|
+
These events are published to Redis Stream for consumption by Trader Service.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from decimal import Decimal
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from pydantic import BaseModel, Field
|
|
12
|
+
|
|
13
|
+
from tradepose_models.trading.orders import OrderSide, OrderType
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class EntryOrderEvent(BaseModel):
|
|
17
|
+
"""Entry order event (進場委託事件).
|
|
18
|
+
|
|
19
|
+
Published by TradingDecisionJob when a trade entry signal is detected.
|
|
20
|
+
Contains all information needed for Trader Service to execute entry order.
|
|
21
|
+
|
|
22
|
+
Redis Stream: order:events:{user_id}:{account_id}
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
engagement_id: UUID = Field(..., description="Engagement UUID (FK)")
|
|
26
|
+
account_id: UUID = Field(..., description="Account UUID for execution")
|
|
27
|
+
symbol: str = Field(..., description="Trading symbol (mapped via instrument_mapping)")
|
|
28
|
+
side: OrderSide = Field(..., description="Order side (BUY/SELL)")
|
|
29
|
+
quantity: Decimal = Field(..., description="Order quantity (lots)")
|
|
30
|
+
order_type: OrderType = Field(..., description="Order type (MARKET/LIMIT)")
|
|
31
|
+
entry_price: Decimal = Field(..., description="Entry price (required)")
|
|
32
|
+
sl_price: Optional[Decimal] = Field(None, description="Stop Loss price (optional)")
|
|
33
|
+
tp_price: Optional[Decimal] = Field(None, description="Take Profit price (optional)")
|
|
34
|
+
|
|
35
|
+
def to_redis_dict(self) -> dict:
|
|
36
|
+
"""Convert to Redis-compatible dict for Stream publishing."""
|
|
37
|
+
return {
|
|
38
|
+
"event_type": "entry",
|
|
39
|
+
"engagement_id": str(self.engagement_id),
|
|
40
|
+
"account_id": str(self.account_id),
|
|
41
|
+
"symbol": self.symbol,
|
|
42
|
+
"side": self.side.value,
|
|
43
|
+
"quantity": str(self.quantity),
|
|
44
|
+
"order_type": self.order_type.value,
|
|
45
|
+
"entry_price": str(self.entry_price),
|
|
46
|
+
"sl_price": str(self.sl_price) if self.sl_price else "",
|
|
47
|
+
"tp_price": str(self.tp_price) if self.tp_price else "",
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class ExitOrderEvent(BaseModel):
|
|
52
|
+
"""Exit order event (出場委託事件).
|
|
53
|
+
|
|
54
|
+
Published by TradingDecisionJob when a trade exit signal is detected.
|
|
55
|
+
Contains information needed for Trader Service to execute exit order.
|
|
56
|
+
|
|
57
|
+
Redis Stream: order:events:{user_id}:{account_id}
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
engagement_id: UUID = Field(..., description="Engagement UUID (FK)")
|
|
61
|
+
account_id: UUID = Field(..., description="Account UUID for execution")
|
|
62
|
+
symbol: str = Field(..., description="Trading symbol")
|
|
63
|
+
side: OrderSide = Field(..., description="Order side (opposite of entry)")
|
|
64
|
+
quantity: Decimal = Field(..., description="Order quantity (lots)")
|
|
65
|
+
order_type: OrderType = Field(
|
|
66
|
+
default=OrderType.MARKET, description="Order type (typically MARKET for exits)"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
def to_redis_dict(self) -> dict:
|
|
70
|
+
"""Convert to Redis-compatible dict for Stream publishing."""
|
|
71
|
+
return {
|
|
72
|
+
"event_type": "exit",
|
|
73
|
+
"engagement_id": str(self.engagement_id),
|
|
74
|
+
"account_id": str(self.account_id),
|
|
75
|
+
"symbol": self.symbol,
|
|
76
|
+
"side": self.side.value,
|
|
77
|
+
"quantity": str(self.quantity),
|
|
78
|
+
"order_type": self.order_type.value,
|
|
79
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Export API Models.
|
|
2
|
+
|
|
3
|
+
Provides models for export task management, on-demand OHLCV requests,
|
|
4
|
+
and export API requests/responses.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .request import OnDemandOhlcvRequest
|
|
8
|
+
from .requests import ExportRequest, ExportResponse
|
|
9
|
+
from .task_metadata import TaskMetadata, TaskMetadataResponse
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
# Task models
|
|
13
|
+
"TaskMetadata", # Internal: Complete task metadata (Gateway/Worker/Redis)
|
|
14
|
+
"TaskMetadataResponse", # Public: API response (excludes internal fields)
|
|
15
|
+
# Request models
|
|
16
|
+
"OnDemandOhlcvRequest",
|
|
17
|
+
"ExportRequest",
|
|
18
|
+
"ExportResponse",
|
|
19
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Export Request Models
|
|
3
|
+
|
|
4
|
+
Provides models for on-demand OHLCV export requests.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class OnDemandOhlcvRequest(BaseModel):
|
|
13
|
+
"""On-Demand OHLCV 導出請求(與 Rust OnDemandOhlcvRequest struct 一致)
|
|
14
|
+
|
|
15
|
+
特點:
|
|
16
|
+
- 無需預先註冊策略
|
|
17
|
+
- 直接指定指標規格即可導出
|
|
18
|
+
- 適用於快速探索指標、外部數據分析
|
|
19
|
+
|
|
20
|
+
Example:
|
|
21
|
+
>>> from tradepose_models import create_indicator_spec, Indicator, Freq
|
|
22
|
+
>>>
|
|
23
|
+
>>> # 定義指標
|
|
24
|
+
>>> sma_spec = create_indicator_spec(Freq.HOUR_1, Indicator.sma(20), shift=1)
|
|
25
|
+
>>> atr_spec = create_indicator_spec(Freq.HOUR_1, Indicator.atr(14), shift=1)
|
|
26
|
+
>>>
|
|
27
|
+
>>> # 創建請求
|
|
28
|
+
>>> request = OnDemandOhlcvRequest(
|
|
29
|
+
... base_instrument="TXFR1",
|
|
30
|
+
... base_freq="1min",
|
|
31
|
+
... indicator_specs=[
|
|
32
|
+
... sma_spec.model_dump(exclude_none=True),
|
|
33
|
+
... atr_spec.model_dump(exclude_none=True)
|
|
34
|
+
... ],
|
|
35
|
+
... start_date="2025-01-01T00:00:00",
|
|
36
|
+
... end_date="2025-01-02T23:59:59"
|
|
37
|
+
... )
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
base_instrument: str = Field(
|
|
41
|
+
..., description="基礎商品 ID (例如 'TXFR1', 'TXF_M1_SHIOAJI_FUTURE')"
|
|
42
|
+
)
|
|
43
|
+
base_freq: str = Field(
|
|
44
|
+
..., description="基礎頻率 ('1min', '5min', '15min', '1h', '4h', '1D' 等)"
|
|
45
|
+
)
|
|
46
|
+
indicator_specs: List[Dict[str, Any]] = Field(
|
|
47
|
+
..., description="指標規格列表 (IndicatorSpec JSON 數組)", min_length=1
|
|
48
|
+
)
|
|
49
|
+
start_date: Optional[str] = Field(
|
|
50
|
+
None, description="開始時間 (ISO 8601 格式,例如 '2025-01-01T00:00:00')"
|
|
51
|
+
)
|
|
52
|
+
end_date: Optional[str] = Field(None, description="結束時間 (ISO 8601 格式,預設為當前時間)")
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Export API request/response models."""
|
|
2
|
+
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
from tradepose_models.enums.export_type import ExportType
|
|
8
|
+
from tradepose_models.enums.persist_mode import PersistMode
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ExportRequest(BaseModel):
|
|
12
|
+
"""Worker-compatible export request model.
|
|
13
|
+
|
|
14
|
+
This model matches the Gateway's requirements and generates events
|
|
15
|
+
compatible with Worker's BacktestTaskEvent format.
|
|
16
|
+
|
|
17
|
+
Required:
|
|
18
|
+
- strategy_configs: Complete StrategyConfig objects (self-contained)
|
|
19
|
+
- export_type: ExportType enum
|
|
20
|
+
|
|
21
|
+
Optional (nested in backtest_request):
|
|
22
|
+
- start_date, end_date, strategy_ids
|
|
23
|
+
- strategy_name, blueprint_name (for enhanced-ohlcv)
|
|
24
|
+
- base_instrument, base_freq, indicator_specs (for on-demand-ohlcv)
|
|
25
|
+
- persist_mode: Where to store results (REDIS or PSQL)
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
# Required: Complete StrategyConfig objects (user must provide)
|
|
29
|
+
strategy_configs: list[dict] = Field(
|
|
30
|
+
..., description="List of complete StrategyConfig objects (JSON)"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Export type (using enum)
|
|
34
|
+
export_type: ExportType = Field(
|
|
35
|
+
...,
|
|
36
|
+
description="Export type: BACKTEST_RESULTS, LATEST_TRADES, ENHANCED_OHLCV, ON_DEMAND_OHLCV",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
# Persistence mode (default: REDIS only)
|
|
40
|
+
persist_mode: PersistMode = Field(
|
|
41
|
+
default=PersistMode.REDIS,
|
|
42
|
+
description="Where to persist results: REDIS (default) or PSQL (dual-write to PostgreSQL)",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# BacktestRequest fields (nested in worker event)
|
|
46
|
+
start_date: str | None = Field(default=None, description="Start date ISO 8601 format")
|
|
47
|
+
end_date: str | None = Field(default=None, description="End date ISO 8601 format")
|
|
48
|
+
strategy_ids: list[str] | None = Field(
|
|
49
|
+
default=None, description="Strategy IDs to execute (None = all)"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# For EnhancedOhlcv
|
|
53
|
+
strategy_name: str | None = Field(
|
|
54
|
+
default=None, description="Strategy name (for enhanced-ohlcv)"
|
|
55
|
+
)
|
|
56
|
+
blueprint_name: str | None = Field(
|
|
57
|
+
default=None, description="Blueprint name (for enhanced-ohlcv)"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# For OnDemandOhlcv
|
|
61
|
+
base_instrument: str | None = Field(
|
|
62
|
+
default=None, description="Instrument ID for on-demand OHLCV"
|
|
63
|
+
)
|
|
64
|
+
base_freq: str | None = Field(default=None, description="Base frequency for on-demand OHLCV")
|
|
65
|
+
indicator_specs: list[dict] | None = Field(
|
|
66
|
+
default=None, description="Indicator specifications for on-demand OHLCV"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ExportResponse(BaseModel):
|
|
71
|
+
"""Response model for export task creation."""
|
|
72
|
+
|
|
73
|
+
task_id: UUID
|
|
74
|
+
message: str
|
|
75
|
+
operation_type: str
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Task Metadata Models
|
|
3
|
+
|
|
4
|
+
Provides two models for task metadata:
|
|
5
|
+
- TaskMetadata: Internal complete model (Gateway/Worker/Redis)
|
|
6
|
+
- TaskStatusResponse: Public API response (excludes internal fields)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
from uuid import UUID
|
|
12
|
+
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from ..base import BaseModel
|
|
16
|
+
from ..enums import ExportType, TaskStatus
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TaskMetadata(BaseModel):
|
|
20
|
+
"""
|
|
21
|
+
Internal task metadata model (complete with all fields).
|
|
22
|
+
|
|
23
|
+
This model is used for:
|
|
24
|
+
1. Gateway creation - Initial task metadata
|
|
25
|
+
2. Redis storage - Stored as JSON STRING
|
|
26
|
+
3. Worker updates - Worker modifies status, timing, results
|
|
27
|
+
4. Internal communication - Full context for backend
|
|
28
|
+
|
|
29
|
+
For API responses, use TaskStatusResponse instead.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
# Core identification
|
|
33
|
+
task_id: UUID = Field(..., description="Task UUID")
|
|
34
|
+
user_id: UUID = Field(description="User UUID (internal only, not included in API responses)")
|
|
35
|
+
|
|
36
|
+
# Task status
|
|
37
|
+
status: TaskStatus = Field(
|
|
38
|
+
default=TaskStatus.PENDING,
|
|
39
|
+
description="Current task status (0=pending, 1=processing, 2=completed, 3=failed)",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Operation info
|
|
43
|
+
export_type: ExportType = Field(
|
|
44
|
+
description="Export type (0=BACKTEST_RESULTS, 1=LATEST_TRADES, 2=ENHANCED_OHLCV, 3=ON_DEMAND_OHLCV)"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
# Timing (TIMESTAMPTZ in PostgreSQL, datetime in Python, ISO 8601 in JSON)
|
|
48
|
+
created_at: datetime = Field(..., description="Task creation timestamp")
|
|
49
|
+
started_at: Optional[datetime] = Field(None, description="Processing start timestamp")
|
|
50
|
+
completed_at: Optional[datetime] = Field(None, description="Processing completion timestamp")
|
|
51
|
+
|
|
52
|
+
# Worker info
|
|
53
|
+
worker_id: Optional[str] = Field(None, description="Worker ID that processed this task")
|
|
54
|
+
|
|
55
|
+
# Strategy execution info
|
|
56
|
+
executed_strategies: List[str] = Field(
|
|
57
|
+
default_factory=list, description="List of strategy names that were executed"
|
|
58
|
+
)
|
|
59
|
+
strategy_counts: int = Field(
|
|
60
|
+
default=0, description="Number of strategies (Gateway sets, Worker may update)"
|
|
61
|
+
)
|
|
62
|
+
blueprint_counts: int = Field(default=0, description="Number of blueprints processed")
|
|
63
|
+
indicator_counts: int = Field(default=0, description="Number of indicators used")
|
|
64
|
+
trigger_counts: int = Field(default=0, description="Number of triggers used")
|
|
65
|
+
input_rows: int = Field(default=0, description="Number of input rows processed")
|
|
66
|
+
|
|
67
|
+
# Error handling
|
|
68
|
+
error_message: Optional[str] = Field(None, description="Error message if task failed")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TaskMetadataResponse(TaskMetadata):
|
|
72
|
+
"""
|
|
73
|
+
Public API response model for task metadata.
|
|
74
|
+
|
|
75
|
+
Inherits from TaskMetadata but excludes internal-only fields.
|
|
76
|
+
This model is returned by GET /api/v1/tasks/{task_id} endpoint.
|
|
77
|
+
|
|
78
|
+
Excluded fields:
|
|
79
|
+
- executed_strategies: Internal execution details
|
|
80
|
+
- error_message: Internal error tracking
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def model_dump(self, **kwargs):
|
|
84
|
+
"""Override to exclude internal-only fields from serialization."""
|
|
85
|
+
data = super().model_dump(**kwargs)
|
|
86
|
+
data.pop("executed_strategies", None)
|
|
87
|
+
# data.pop("error_message", None)
|
|
88
|
+
return data
|
|
89
|
+
|
|
90
|
+
def model_dump_json(self, **kwargs):
|
|
91
|
+
"""Override JSON serialization to exclude internal-only fields."""
|
|
92
|
+
exclude = kwargs.get("exclude", set())
|
|
93
|
+
if isinstance(exclude, set):
|
|
94
|
+
exclude.add("executed_strategies")
|
|
95
|
+
# exclude.add("error_message")
|
|
96
|
+
kwargs["exclude"] = exclude
|
|
97
|
+
return super().model_dump_json(**kwargs)
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Gateway API Response Models.
|
|
2
|
+
|
|
3
|
+
This module contains common response models used by gateway endpoints
|
|
4
|
+
for operations that return simple success/status messages.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .responses import (
|
|
8
|
+
CancelSubscriptionResponse,
|
|
9
|
+
OperationResponse,
|
|
10
|
+
StrategyOperationResponse,
|
|
11
|
+
WebhookResponse,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"OperationResponse",
|
|
16
|
+
"StrategyOperationResponse",
|
|
17
|
+
"CancelSubscriptionResponse",
|
|
18
|
+
"WebhookResponse",
|
|
19
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Gateway API Response Models."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class OperationResponse(BaseModel):
|
|
10
|
+
"""Generic operation response with task ID."""
|
|
11
|
+
|
|
12
|
+
task_id: str = Field(..., description="Task ID for polling status")
|
|
13
|
+
message: str = Field(..., description="Human-readable message")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class StrategyOperationResponse(BaseModel):
|
|
17
|
+
"""Response for strategy operations (delete, get detail)."""
|
|
18
|
+
|
|
19
|
+
task_id: str = Field(..., description="Task ID for polling status")
|
|
20
|
+
message: str = Field(..., description="Operation status message")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class CancelSubscriptionResponse(BaseModel):
|
|
24
|
+
"""Response for subscription cancellation."""
|
|
25
|
+
|
|
26
|
+
message: str = Field(..., description="Cancellation status message")
|
|
27
|
+
status: str = Field(..., description="Updated subscription status")
|
|
28
|
+
current_period_end: Optional[datetime] = Field(
|
|
29
|
+
None, description="End of current billing period"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class WebhookResponse(BaseModel):
|
|
34
|
+
"""Response for webhook processing."""
|
|
35
|
+
|
|
36
|
+
status: str = Field(..., description="Processing status (success/error)")
|
|
37
|
+
event: str = Field(..., description="Event type that was processed")
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Indicator Pydantic Models for Strong Typing
|
|
3
|
+
|
|
4
|
+
Provides strongly-typed indicator models aligned with Rust backend.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import PolarsExprField
|
|
8
|
+
from .factory import Indicator
|
|
9
|
+
from .market_profile import (
|
|
10
|
+
AtrQuantileConfig,
|
|
11
|
+
MarketProfileIndicator,
|
|
12
|
+
ProfileShape,
|
|
13
|
+
ProfileShapeConfig,
|
|
14
|
+
create_daily_anchor,
|
|
15
|
+
create_profile_shape_config,
|
|
16
|
+
create_weekly_anchor,
|
|
17
|
+
)
|
|
18
|
+
from .momentum import CCIIndicator, RSIIndicator, StochasticIndicator
|
|
19
|
+
from .moving_average import EMAIndicator, SMAIndicator, SMMAIndicator, WMAIndicator
|
|
20
|
+
from .other import BollingerBandsIndicator, RawOhlcvIndicator
|
|
21
|
+
from .trend import ADXIndicator, MACDIndicator, SuperTrendIndicator
|
|
22
|
+
from .volatility import ATRIndicator, ATRQuantileIndicator
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
# Base
|
|
26
|
+
"PolarsExprField",
|
|
27
|
+
# Moving Average
|
|
28
|
+
"SMAIndicator",
|
|
29
|
+
"EMAIndicator",
|
|
30
|
+
"SMMAIndicator",
|
|
31
|
+
"WMAIndicator",
|
|
32
|
+
# Volatility
|
|
33
|
+
"ATRIndicator",
|
|
34
|
+
"ATRQuantileIndicator",
|
|
35
|
+
# Trend
|
|
36
|
+
"SuperTrendIndicator",
|
|
37
|
+
"MACDIndicator",
|
|
38
|
+
"ADXIndicator",
|
|
39
|
+
# Momentum
|
|
40
|
+
"RSIIndicator",
|
|
41
|
+
"CCIIndicator",
|
|
42
|
+
"StochasticIndicator",
|
|
43
|
+
# Other
|
|
44
|
+
"BollingerBandsIndicator",
|
|
45
|
+
"RawOhlcvIndicator",
|
|
46
|
+
# Market Profile
|
|
47
|
+
"MarketProfileIndicator",
|
|
48
|
+
"AtrQuantileConfig",
|
|
49
|
+
"ProfileShape",
|
|
50
|
+
"ProfileShapeConfig",
|
|
51
|
+
"create_daily_anchor",
|
|
52
|
+
"create_weekly_anchor",
|
|
53
|
+
"create_profile_shape_config",
|
|
54
|
+
# Factory
|
|
55
|
+
"Indicator",
|
|
56
|
+
]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base utilities for indicator models
|
|
3
|
+
|
|
4
|
+
Provides Polars Expr field handling for strategy models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
from io import StringIO
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
import polars as pl
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PolarsExprField:
|
|
15
|
+
"""自定義 Polars Expr 字段處理"""
|
|
16
|
+
|
|
17
|
+
@staticmethod
|
|
18
|
+
def serialize(expr: pl.Expr) -> dict:
|
|
19
|
+
"""序列化 Polars Expr 為 dict(與服務器格式一致)
|
|
20
|
+
|
|
21
|
+
Python Polars 1.33+ 的 expr.meta.serialize() 已輸出與 Rust Polars 0.51 兼容的格式:
|
|
22
|
+
- Window variant (not Over)
|
|
23
|
+
- options: {"Over": "GroupsToRows"} (correct WindowType format)
|
|
24
|
+
"""
|
|
25
|
+
json_str = expr.meta.serialize(format="json")
|
|
26
|
+
return json.loads(json_str)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def deserialize(data: Union[str, dict, pl.Expr]) -> pl.Expr:
|
|
30
|
+
"""反序列化為 Polars Expr"""
|
|
31
|
+
if isinstance(data, pl.Expr):
|
|
32
|
+
# 已經是 Expr 對象
|
|
33
|
+
return data
|
|
34
|
+
elif isinstance(data, dict):
|
|
35
|
+
# API 返回的 dict(優先處理)
|
|
36
|
+
json_str = json.dumps(data)
|
|
37
|
+
return pl.Expr.deserialize(StringIO(json_str), format="json")
|
|
38
|
+
elif isinstance(data, str):
|
|
39
|
+
# JSON 字符串(向後兼容)
|
|
40
|
+
return pl.Expr.deserialize(StringIO(data), format="json")
|
|
41
|
+
else:
|
|
42
|
+
raise ValueError(f"無法反序列化類型: {type(data)}")
|