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,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