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,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Trades Schema
|
|
3
|
+
|
|
4
|
+
Polars schema definition for trades data from backtest results or latest trades export.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
# Trades Schema (from backtest-results or latest-trades export)
|
|
10
|
+
# Complete schema matching API response with MAE/MFE fields
|
|
11
|
+
trades_schema = {
|
|
12
|
+
# Position identifiers
|
|
13
|
+
"final_position_id": pl.UInt32,
|
|
14
|
+
"position_id": pl.UInt32,
|
|
15
|
+
"direction": pl.Int32, # -1 for Short, 1 for Long
|
|
16
|
+
"status": pl.Boolean, # false = closed, true = open
|
|
17
|
+
# Entry details
|
|
18
|
+
"entry_idx": pl.UInt32,
|
|
19
|
+
"entry_time": pl.Datetime("ms"),
|
|
20
|
+
"entry_price": pl.Float64,
|
|
21
|
+
"entry_reason": pl.UInt32,
|
|
22
|
+
"favorable_entry_hypo_price": pl.Float64,
|
|
23
|
+
"adverse_entry_hypo_price": pl.Float64,
|
|
24
|
+
"favorable_entry_strategy": pl.UInt32,
|
|
25
|
+
"adverse_entry_strategy": pl.UInt32,
|
|
26
|
+
# Exit details
|
|
27
|
+
"exit_idx": pl.UInt32,
|
|
28
|
+
"exit_time": pl.Datetime("ms"),
|
|
29
|
+
"exit_price": pl.Float64,
|
|
30
|
+
"exit_reason": pl.UInt32,
|
|
31
|
+
"favorable_exit_hypo_price": pl.Float64,
|
|
32
|
+
"adverse_exit_hypo_price": pl.Float64,
|
|
33
|
+
"favorable_exit_strategy": pl.UInt32,
|
|
34
|
+
"adverse_exit_strategy": pl.UInt32,
|
|
35
|
+
# Holding period
|
|
36
|
+
"holding_seconds": pl.Int64,
|
|
37
|
+
"holding_bars": pl.UInt32,
|
|
38
|
+
# MAE/MFE metrics (Maximum Adverse/Favorable Excursion)
|
|
39
|
+
"g_mfe": pl.Float64, # Global Maximum Favorable Excursion
|
|
40
|
+
"mae": pl.Float64, # Maximum Adverse Excursion
|
|
41
|
+
"mfe": pl.Float64, # Maximum Favorable Excursion
|
|
42
|
+
"mae_lv1": pl.Float64, # MAE Level 1
|
|
43
|
+
"mhl": pl.Float64, # Maximum High/Low
|
|
44
|
+
# MAE/MFE indices (bar index where each occurred)
|
|
45
|
+
"g_mfe_idx": pl.UInt32,
|
|
46
|
+
"mae_idx": pl.UInt32,
|
|
47
|
+
"mfe_idx": pl.UInt32,
|
|
48
|
+
"mae_lv1_idx": pl.UInt32,
|
|
49
|
+
"mhl_idx": pl.UInt32,
|
|
50
|
+
# Volatility at each key point (typically ATR)
|
|
51
|
+
"entry_volatility": pl.Float64,
|
|
52
|
+
"exit_volatility": pl.Float64,
|
|
53
|
+
"g_mfe_volatility": pl.Float64,
|
|
54
|
+
"mae_volatility": pl.Float64,
|
|
55
|
+
"mfe_volatility": pl.Float64,
|
|
56
|
+
"mae_lv1_volatility": pl.Float64,
|
|
57
|
+
"mhl_volatility": pl.Float64,
|
|
58
|
+
# P&L metrics
|
|
59
|
+
"pnl": pl.Float64, # Absolute profit/loss
|
|
60
|
+
"pnl_pct": pl.Float64, # Percentage profit/loss
|
|
61
|
+
# Metadata
|
|
62
|
+
"strategy_name": pl.Utf8,
|
|
63
|
+
"blueprint_name": pl.Utf8,
|
|
64
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"""Shared data schemas and DTOs (共用數據模式).
|
|
2
|
+
|
|
3
|
+
This module contains Pydantic models for domain objects and data transfer
|
|
4
|
+
objects (DTOs) that are shared across multiple packages.
|
|
5
|
+
|
|
6
|
+
這個模組包含跨多個包共享的領域對象和數據傳輸對象(DTO)的 Pydantic 模型。
|
|
7
|
+
|
|
8
|
+
Example usage:
|
|
9
|
+
from tradepose_models.schemas import PlanLimits, TaskResponse
|
|
10
|
+
|
|
11
|
+
# Use in gateway to enforce limits
|
|
12
|
+
limits = PlanLimits(
|
|
13
|
+
max_requests_per_minute=100,
|
|
14
|
+
max_concurrent_tasks=5
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Use in client to understand capabilities
|
|
18
|
+
plan = client.get_plan()
|
|
19
|
+
print(f"You can run {plan.limits.max_concurrent_tasks} tasks")
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Placeholder for shared schemas
|
|
24
|
+
# Models will be migrated here from gateway as needed
|
|
25
|
+
# 共用模式將根據需要從 gateway 遷移到這裡
|
|
26
|
+
|
|
27
|
+
# Example structure:
|
|
28
|
+
# class PlanLimits(BaseModel):
|
|
29
|
+
# """Rate limits and quotas for subscription plans."""
|
|
30
|
+
# max_requests_per_minute: int
|
|
31
|
+
# max_concurrent_tasks: int
|
|
32
|
+
# max_strategies: int
|
|
33
|
+
|
|
34
|
+
__all__ = []
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Shared models used across Gateway and Client.
|
|
2
|
+
|
|
3
|
+
This module will be deprecated in favor of the more organized structure:
|
|
4
|
+
- enums.py
|
|
5
|
+
- schemas.py
|
|
6
|
+
- validators.py
|
|
7
|
+
- types.py
|
|
8
|
+
|
|
9
|
+
Please import from those modules instead of this one.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# This file is kept for backward compatibility
|
|
13
|
+
# New models should be added to the appropriate specialized module
|
|
14
|
+
|
|
15
|
+
__all__ = []
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Strategy Configuration Models.
|
|
2
|
+
|
|
3
|
+
Provides models for strategy configuration including IndicatorSpec, Trigger,
|
|
4
|
+
Blueprint, and StrategyConfig, plus API request/response models.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .base import StrategyBase
|
|
8
|
+
from .blueprint import Blueprint
|
|
9
|
+
from .config import StrategyConfig, parse_strategy
|
|
10
|
+
from .entities import BlueprintEntity, StrategyEntity
|
|
11
|
+
from .helpers import create_blueprint, create_indicator_spec, create_trigger
|
|
12
|
+
from .indicator_spec import IndicatorConfig, IndicatorSpec
|
|
13
|
+
from .performance import StrategyPerformance
|
|
14
|
+
from .portfolio import Portfolio
|
|
15
|
+
from .registry import BlueprintSelection, RegistryEntry, StrategyRegistry
|
|
16
|
+
from .requests import (
|
|
17
|
+
ListStrategiesRequest,
|
|
18
|
+
ListStrategiesResponse,
|
|
19
|
+
RegisterStrategyRequest,
|
|
20
|
+
RegisterStrategyResponse,
|
|
21
|
+
)
|
|
22
|
+
from .trigger import Trigger
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
# Core models
|
|
26
|
+
"IndicatorSpec",
|
|
27
|
+
"IndicatorConfig",
|
|
28
|
+
"Trigger",
|
|
29
|
+
"Blueprint",
|
|
30
|
+
"StrategyConfig",
|
|
31
|
+
# Base and Entities
|
|
32
|
+
"StrategyBase",
|
|
33
|
+
"StrategyEntity",
|
|
34
|
+
"BlueprintEntity",
|
|
35
|
+
# Performance
|
|
36
|
+
"StrategyPerformance",
|
|
37
|
+
# Registry and Portfolio
|
|
38
|
+
"BlueprintSelection",
|
|
39
|
+
"RegistryEntry",
|
|
40
|
+
"StrategyRegistry",
|
|
41
|
+
"Portfolio",
|
|
42
|
+
# Helpers
|
|
43
|
+
"parse_strategy",
|
|
44
|
+
"create_indicator_spec",
|
|
45
|
+
"create_trigger",
|
|
46
|
+
"create_blueprint",
|
|
47
|
+
# API requests/responses
|
|
48
|
+
"RegisterStrategyRequest",
|
|
49
|
+
"RegisterStrategyResponse",
|
|
50
|
+
"ListStrategiesRequest",
|
|
51
|
+
"ListStrategiesResponse",
|
|
52
|
+
]
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy Base Model
|
|
3
|
+
|
|
4
|
+
Provides the StrategyBase class with shared strategy fields (without blueprints).
|
|
5
|
+
Used as base class for StrategyConfig and StrategyEntity.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Any, List, Optional
|
|
9
|
+
|
|
10
|
+
import polars as pl
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
12
|
+
|
|
13
|
+
from ..enums import Freq
|
|
14
|
+
from ..indicators import PolarsExprField
|
|
15
|
+
from .indicator_spec import IndicatorSpec
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class StrategyBase(BaseModel):
|
|
19
|
+
"""Strategy base class with shared fields (without blueprints).
|
|
20
|
+
|
|
21
|
+
Contains the core strategy configuration that is shared between:
|
|
22
|
+
- StrategyConfig: Local/backtest model with embedded blueprints
|
|
23
|
+
- StrategyEntity: Database entity with UUID and timestamps
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
name: str = Field(..., description="Strategy name")
|
|
27
|
+
base_instrument: str = Field(..., description="Base trading instrument")
|
|
28
|
+
base_freq: Freq = Field(..., description="Base frequency")
|
|
29
|
+
note: str = Field(..., description="Strategy notes")
|
|
30
|
+
|
|
31
|
+
volatility_indicator: Optional[pl.Expr] = Field(
|
|
32
|
+
None,
|
|
33
|
+
description="Volatility field expression, e.g. pl.col('1D_ATR|14')",
|
|
34
|
+
)
|
|
35
|
+
indicators: List[IndicatorSpec] = Field(
|
|
36
|
+
default_factory=list, description="Indicator specifications"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
@field_validator("volatility_indicator", mode="before")
|
|
40
|
+
@classmethod
|
|
41
|
+
def validate_volatility_indicator(cls, v: Any) -> Optional[pl.Expr]:
|
|
42
|
+
"""Auto-convert volatility_indicator to pl.Expr"""
|
|
43
|
+
if v is None:
|
|
44
|
+
return None
|
|
45
|
+
return PolarsExprField.deserialize(v)
|
|
46
|
+
|
|
47
|
+
@field_serializer("volatility_indicator")
|
|
48
|
+
def serialize_volatility_indicator(self, expr: Optional[pl.Expr]) -> Optional[dict]:
|
|
49
|
+
"""Serialize volatility_indicator to dict"""
|
|
50
|
+
if expr is None:
|
|
51
|
+
return None
|
|
52
|
+
return PolarsExprField.serialize(expr)
|
|
53
|
+
|
|
54
|
+
model_config = ConfigDict(
|
|
55
|
+
arbitrary_types_allowed=True # Allow pl.Expr custom type
|
|
56
|
+
)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Blueprint Model
|
|
3
|
+
|
|
4
|
+
Provides the Blueprint class for strategy blueprints with entry/exit triggers.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, List, Optional
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, Field, field_validator
|
|
11
|
+
|
|
12
|
+
from ..enums import TradeDirection, TrendType
|
|
13
|
+
from .trigger import Trigger
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Blueprint(BaseModel):
|
|
17
|
+
"""策略藍圖"""
|
|
18
|
+
|
|
19
|
+
id: Optional[UUID] = Field(None, description="藍圖 ID(由資料庫自動填充)")
|
|
20
|
+
name: str = Field(..., description="藍圖名稱")
|
|
21
|
+
direction: TradeDirection = Field(..., description="方向")
|
|
22
|
+
trend_type: TrendType = Field(..., description="趨勢類型")
|
|
23
|
+
entry_first: bool = Field(..., description="是否優先進場")
|
|
24
|
+
note: str = Field(default="", description="備註")
|
|
25
|
+
|
|
26
|
+
entry_triggers: List[Trigger] = Field(..., description="進場觸發器列表")
|
|
27
|
+
exit_triggers: List[Trigger] = Field(..., description="出場觸發器列表")
|
|
28
|
+
|
|
29
|
+
@field_validator("direction", mode="before")
|
|
30
|
+
@classmethod
|
|
31
|
+
def convert_direction(cls, v: Any) -> TradeDirection:
|
|
32
|
+
"""自動轉換字串為 TradeDirection enum(保持 API 兼容性)"""
|
|
33
|
+
if isinstance(v, str):
|
|
34
|
+
try:
|
|
35
|
+
return TradeDirection(v)
|
|
36
|
+
except ValueError:
|
|
37
|
+
valid_values = [e.value for e in TradeDirection]
|
|
38
|
+
raise ValueError(
|
|
39
|
+
f"Invalid direction: '{v}'. Valid values: {', '.join(valid_values)}"
|
|
40
|
+
)
|
|
41
|
+
return v
|
|
42
|
+
|
|
43
|
+
@field_validator("trend_type", mode="before")
|
|
44
|
+
@classmethod
|
|
45
|
+
def convert_trend_type(cls, v: Any) -> TrendType:
|
|
46
|
+
"""自動轉換字串為 TrendType enum(保持 API 兼容性)"""
|
|
47
|
+
if isinstance(v, str):
|
|
48
|
+
try:
|
|
49
|
+
return TrendType(v)
|
|
50
|
+
except ValueError:
|
|
51
|
+
valid_values = [e.value for e in TrendType]
|
|
52
|
+
raise ValueError(
|
|
53
|
+
f"Invalid trend_type: '{v}'. Valid values: {', '.join(valid_values)}"
|
|
54
|
+
)
|
|
55
|
+
return v
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy Configuration Model
|
|
3
|
+
|
|
4
|
+
Provides the StrategyConfig class for complete strategy configuration.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any, Dict, List, Optional, Union
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
import polars as pl
|
|
11
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
12
|
+
|
|
13
|
+
from ..enums import Freq
|
|
14
|
+
from ..indicators import PolarsExprField
|
|
15
|
+
from .blueprint import Blueprint
|
|
16
|
+
from .indicator_spec import IndicatorSpec
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class StrategyConfig(BaseModel):
|
|
20
|
+
"""完整策略配置"""
|
|
21
|
+
|
|
22
|
+
id: Optional[UUID] = Field(None, description="策略 ID(由資料庫自動填充)")
|
|
23
|
+
name: str = Field(..., description="策略名稱")
|
|
24
|
+
base_instrument: str = Field(..., description="基礎交易標的")
|
|
25
|
+
base_instrument_id: Optional[int] = Field(
|
|
26
|
+
None, description="基礎交易標的 ID(由 Gateway 自動填充)"
|
|
27
|
+
)
|
|
28
|
+
base_freq: Freq = Field(..., description="基礎頻率")
|
|
29
|
+
note: str = Field(default="", description="備註")
|
|
30
|
+
|
|
31
|
+
volatility_indicator: Optional[pl.Expr] = Field(
|
|
32
|
+
None,
|
|
33
|
+
description="波動率欄位表達式,如 pl.col('1D_ATR|14') 或 pl.col('profile').struct.field('shape')",
|
|
34
|
+
)
|
|
35
|
+
indicators: List[IndicatorSpec] = Field(default_factory=list, description="指標列表")
|
|
36
|
+
|
|
37
|
+
base_blueprint: Blueprint = Field(..., description="基礎藍圖")
|
|
38
|
+
advanced_blueprints: List[Blueprint] = Field(default_factory=list, description="進階藍圖列表")
|
|
39
|
+
|
|
40
|
+
@field_validator("volatility_indicator", mode="before")
|
|
41
|
+
@classmethod
|
|
42
|
+
def validate_volatility_indicator(cls, v: Any) -> Optional[pl.Expr]:
|
|
43
|
+
"""自動轉換 volatility_indicator 為 pl.Expr
|
|
44
|
+
|
|
45
|
+
支援:
|
|
46
|
+
- None / null
|
|
47
|
+
- 字串(如 "1D_ATR|14")→ 自動轉為 pl.col("1D_ATR|14")
|
|
48
|
+
- 完整 Expr JSON(如 {"Column": "..."})
|
|
49
|
+
"""
|
|
50
|
+
if v is None:
|
|
51
|
+
return None
|
|
52
|
+
return PolarsExprField.deserialize(v)
|
|
53
|
+
|
|
54
|
+
@field_serializer("volatility_indicator")
|
|
55
|
+
def serialize_volatility_indicator(self, expr: Optional[pl.Expr]) -> Optional[dict]:
|
|
56
|
+
"""序列化 volatility_indicator 為 dict(與服務器格式一致)"""
|
|
57
|
+
if expr is None:
|
|
58
|
+
return None
|
|
59
|
+
return PolarsExprField.serialize(expr)
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(
|
|
62
|
+
arbitrary_types_allowed=True # 允許 pl.Expr 這種自定義類型
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_api(cls, api_response: Union[Dict[str, Any], str]) -> "StrategyConfig":
|
|
67
|
+
"""從 API 響應創建策略配置
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
api_response: API 返回的 JSON 數據(dict 或 JSON 字符串)
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
StrategyConfig 實例,conditions 和 price_expr 自動轉為 pl.Expr
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> # 從 JSON 字符串
|
|
77
|
+
>>> strategy = StrategyConfig.from_api(json_str)
|
|
78
|
+
>>> # 從 dict
|
|
79
|
+
>>> strategy = StrategyConfig.from_api(response.json())
|
|
80
|
+
>>> # 直接使用
|
|
81
|
+
>>> expr = strategy.base_blueprint.entry_triggers[0].conditions[0]
|
|
82
|
+
>>> print(type(expr)) # <class 'polars.expr.expr.Expr'>
|
|
83
|
+
"""
|
|
84
|
+
if isinstance(api_response, str):
|
|
85
|
+
return cls.model_validate_json(api_response)
|
|
86
|
+
else:
|
|
87
|
+
return cls.model_validate(api_response)
|
|
88
|
+
|
|
89
|
+
def to_json(self, indent: int = 2) -> str:
|
|
90
|
+
"""序列化為 JSON 字符串
|
|
91
|
+
|
|
92
|
+
自動將 pl.Expr 轉為 JSON 字符串格式
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
JSON 字符串,conditions 和 price_expr 已序列化
|
|
96
|
+
"""
|
|
97
|
+
return self.model_dump_json(indent=indent, exclude_none=True).replace("\n", "")
|
|
98
|
+
|
|
99
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
100
|
+
"""轉換為字典(用於發送到 API)
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
字典,pl.Expr 已序列化為 JSON 字符串
|
|
104
|
+
"""
|
|
105
|
+
return self.model_dump(exclude_none=True)
|
|
106
|
+
|
|
107
|
+
def save(self, filepath: str) -> None:
|
|
108
|
+
"""保存到 JSON 文件"""
|
|
109
|
+
with open(filepath, "w", encoding="utf-8") as f:
|
|
110
|
+
f.write(self.to_json())
|
|
111
|
+
print(f"✅ 策略已保存到: {filepath}")
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def load(cls, filepath: str) -> "StrategyConfig":
|
|
115
|
+
"""從 JSON 文件加載"""
|
|
116
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
117
|
+
return cls.from_api(f.read())
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def parse_strategy(data: Union[Dict[str, Any], str]) -> StrategyConfig:
|
|
121
|
+
"""解析策略配置(最簡單的方式)
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
data: API JSON 數據(dict 或 JSON 字符串)
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
StrategyConfig 實例,可直接訪問 pl.Expr
|
|
128
|
+
|
|
129
|
+
Example:
|
|
130
|
+
>>> import requests
|
|
131
|
+
>>> # 從 API
|
|
132
|
+
>>> response = requests.get("http://localhost:8080/api/v1/strategies/txf_1d_sma20_30")
|
|
133
|
+
>>> strategy = parse_strategy(response.json())
|
|
134
|
+
>>>
|
|
135
|
+
>>> # 直接訪問 Polars Expr
|
|
136
|
+
>>> conditions = strategy.base_blueprint.entry_triggers[0].conditions
|
|
137
|
+
>>> print(type(conditions[0])) # <class 'polars.expr.expr.Expr'>
|
|
138
|
+
>>>
|
|
139
|
+
>>> # 從 JSON 字符串
|
|
140
|
+
>>> strategy = parse_strategy(json_str)
|
|
141
|
+
"""
|
|
142
|
+
return StrategyConfig.from_api(data)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Strategy Database Entities
|
|
3
|
+
|
|
4
|
+
Provides StrategyEntity and BlueprintEntity for database operations.
|
|
5
|
+
These models include UUID IDs and timestamp fields for persistence.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import Any, List, Optional
|
|
10
|
+
from uuid import UUID
|
|
11
|
+
|
|
12
|
+
import polars as pl
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer, field_validator
|
|
14
|
+
|
|
15
|
+
from ..enums import Freq, TradeDirection, TrendType
|
|
16
|
+
from ..indicators import PolarsExprField
|
|
17
|
+
from .indicator_spec import IndicatorSpec
|
|
18
|
+
from .trigger import Trigger
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class StrategyEntity(BaseModel):
|
|
22
|
+
"""Strategy database entity.
|
|
23
|
+
|
|
24
|
+
Represents a strategy stored in the database with UUID ID and metadata.
|
|
25
|
+
Does not include blueprints (they are stored separately and linked via FK).
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
id: UUID = Field(..., description="Strategy UUID (primary key)")
|
|
29
|
+
user_id: UUID = Field(..., description="User UUID (owner)")
|
|
30
|
+
name: str = Field(..., description="Strategy name")
|
|
31
|
+
base_instrument: str = Field(..., description="Base trading instrument")
|
|
32
|
+
base_freq: Freq = Field(..., description="Base frequency")
|
|
33
|
+
note: str = Field(default="", description="Strategy notes")
|
|
34
|
+
|
|
35
|
+
volatility_indicator: Optional[pl.Expr] = Field(
|
|
36
|
+
None,
|
|
37
|
+
description="Volatility field expression",
|
|
38
|
+
)
|
|
39
|
+
indicators: List[IndicatorSpec] = Field(
|
|
40
|
+
default_factory=list, description="Indicator specifications (list)"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
is_archived: bool = Field(default=False, description="Whether strategy is archived")
|
|
44
|
+
created_at: datetime = Field(..., description="Creation timestamp")
|
|
45
|
+
updated_at: datetime = Field(..., description="Last update timestamp")
|
|
46
|
+
|
|
47
|
+
@field_validator("volatility_indicator", mode="before")
|
|
48
|
+
@classmethod
|
|
49
|
+
def validate_volatility_indicator(cls, v: Any) -> Optional[pl.Expr]:
|
|
50
|
+
"""Auto-convert volatility_indicator to pl.Expr"""
|
|
51
|
+
if v is None:
|
|
52
|
+
return None
|
|
53
|
+
return PolarsExprField.deserialize(v)
|
|
54
|
+
|
|
55
|
+
@field_serializer("volatility_indicator")
|
|
56
|
+
def serialize_volatility_indicator(self, expr: Optional[pl.Expr]) -> Optional[dict]:
|
|
57
|
+
"""Serialize volatility_indicator to dict"""
|
|
58
|
+
if expr is None:
|
|
59
|
+
return None
|
|
60
|
+
return PolarsExprField.serialize(expr)
|
|
61
|
+
|
|
62
|
+
model_config = ConfigDict(
|
|
63
|
+
arbitrary_types_allowed=True # Allow pl.Expr custom type
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class BlueprintEntity(BaseModel):
|
|
68
|
+
"""Blueprint database entity.
|
|
69
|
+
|
|
70
|
+
Represents a blueprint stored in the database with UUID ID and metadata.
|
|
71
|
+
Linked to a strategy via strategy_id FK.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
id: UUID = Field(..., description="Blueprint UUID (primary key)")
|
|
75
|
+
strategy_id: UUID = Field(..., description="Parent strategy UUID (FK)")
|
|
76
|
+
name: str = Field(..., description="Blueprint name")
|
|
77
|
+
direction: TradeDirection = Field(..., description="Trade direction")
|
|
78
|
+
trend_type: TrendType = Field(..., description="Trend type")
|
|
79
|
+
entry_first: bool = Field(..., description="Whether to prioritize entry")
|
|
80
|
+
note: str = Field(default="", description="Blueprint notes")
|
|
81
|
+
|
|
82
|
+
entry_triggers: List[Trigger] = Field(default_factory=list, description="Entry trigger list")
|
|
83
|
+
exit_triggers: List[Trigger] = Field(default_factory=list, description="Exit trigger list")
|
|
84
|
+
|
|
85
|
+
is_base: bool = Field(default=False, description="Whether this is the base blueprint")
|
|
86
|
+
is_archived: bool = Field(default=False, description="Whether blueprint is archived")
|
|
87
|
+
created_at: datetime = Field(..., description="Creation timestamp")
|
|
88
|
+
updated_at: datetime = Field(..., description="Last update timestamp")
|
|
89
|
+
|
|
90
|
+
@field_validator("direction", mode="before")
|
|
91
|
+
@classmethod
|
|
92
|
+
def convert_direction(cls, v: Any) -> TradeDirection:
|
|
93
|
+
"""Auto-convert string to TradeDirection enum"""
|
|
94
|
+
if isinstance(v, str):
|
|
95
|
+
return TradeDirection(v)
|
|
96
|
+
return v
|
|
97
|
+
|
|
98
|
+
@field_validator("trend_type", mode="before")
|
|
99
|
+
@classmethod
|
|
100
|
+
def convert_trend_type(cls, v: Any) -> TrendType:
|
|
101
|
+
"""Auto-convert string to TrendType enum"""
|
|
102
|
+
if isinstance(v, str):
|
|
103
|
+
return TrendType(v)
|
|
104
|
+
return v
|