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,1782 @@
1
+ # Market Profile 開發者指南
2
+
3
+ > **其他文件**: 如需了解 Market Profile 的交易理論與策略應用,請參閱 [策略指南](./market_profile_trading.md)
4
+
5
+ ## 目錄
6
+
7
+ - [概述](#概述)
8
+ - [核心概念](#核心概念)
9
+ - [API 參考](#api-參考)
10
+ - [配置指南](#配置指南)
11
+ - [TPO 時間區間設定](#tpo-時間區間設定)
12
+ - [輸出欄位說明](#輸出欄位說明)
13
+ - [形狀識別 (實驗性功能)](#形狀識別-實驗性功能)
14
+ - [使用範例](#使用範例)
15
+ - [常見錯誤](#常見錯誤)
16
+ - [進階主題](#進階主題)
17
+ - [參考資料](#參考資料)
18
+
19
+ ---
20
+
21
+ ## 概述
22
+
23
+ Market Profile 是一種基於 **TPO (Time Price Opportunity)** 的價格分布分析工具,由 J. Peter Steidlmayer 於 1980 年代開發。它透過識別市場參與者的行為特徵和關鍵價格水平,幫助交易者理解市場結構。
24
+
25
+ ### 核心價值
26
+
27
+ - **POC (Point of Control)**: 識別市場最認同的價格水平(最強的支撐/阻力)
28
+ - **Value Area**: 定義 70% TPO 集中的「公平價值區間」
29
+ - **Profile Shape**: 識別市場狀態(趨勢、反轉、區間)
30
+
31
+ ### 快速開始
32
+
33
+ ```python
34
+ from tradepose_models.indicators.market_profile import (
35
+ MarketProfileIndicator,
36
+ create_daily_anchor
37
+ )
38
+
39
+ # 1. 配置 24 小時滾動窗口(最常用)
40
+ anchor = create_daily_anchor(hour=9, minute=15, lookback_days=1)
41
+
42
+ # 2. 創建指標
43
+ mp = MarketProfileIndicator(
44
+ anchor_config=anchor,
45
+ tick_size=0.25, # ES 期貨最小跳動單位
46
+ value_area_pct=0.7 # 標準 70% Value Area
47
+ )
48
+
49
+ # 3. 應用到策略(透過 strategy engine)
50
+ # 結果:包含所有欄位的 Struct 欄位 "market_profile"
51
+ ```
52
+
53
+ ---
54
+
55
+ ## 核心概念
56
+
57
+ ### TPO (Time Price Opportunity)
58
+
59
+ **定義**: 每個時間段(kbar)在其 `[low, high]` 價格範圍內按 `tick_size` 生成的價格層級計數。
60
+
61
+ **計算方式**:
62
+ ```python
63
+ # 對於每根 K 線
64
+ low_level = round(low / tick_size)
65
+ high_level = round(high / tick_size)
66
+
67
+ # 每個價格層級獲得 +1 TPO
68
+ for level in range(low_level, high_level + 1):
69
+ tpo_count[level] += 1
70
+ ```
71
+
72
+ **範例**:
73
+ ```
74
+ K 線: low=100.00, high=100.50, tick_size=0.10
75
+
76
+ 生成的 TPO:
77
+ - level 1000 (100.00): +1
78
+ - level 1001 (100.10): +1
79
+ - level 1002 (100.20): +1
80
+ - level 1003 (100.30): +1
81
+ - level 1004 (100.40): +1
82
+ - level 1005 (100.50): +1
83
+
84
+ 總共: 6 個 TPO
85
+ ```
86
+
87
+ **視覺化**:
88
+ ```
89
+ 價格 TPO 分布
90
+ 100.50 █
91
+ 100.40 ██
92
+ 100.30 ████ ← 更多 TPO = 更高市場接受度
93
+ 100.20 █████ ← POC (最多 TPO)
94
+ 100.10 ███
95
+ 100.00 █
96
+ ```
97
+
98
+ ### POC (Point of Control)
99
+
100
+ **定義**: TPO 計數最多的價格層級,代表市場最認同的價格。
101
+
102
+ **交易意義**:
103
+ - **強支撐/阻力**: 市場參與者在此價格最活躍
104
+ - **磁吸效應**: 價格傾向回到 POC
105
+ - **突破信號**: 價格遠離 POC 表示趨勢形成
106
+
107
+ **確定性保證**: 當多個層級有相同最大 TPO 計數時,選擇**最低價格層級**(保守原則),確保相同輸入永遠產生相同結果(回測可重現)。
108
+
109
+ **範例**:
110
+ ```python
111
+ # TPO 分布
112
+ tpo_count = {
113
+ 1000: 3,
114
+ 1001: 5, # 最大
115
+ 1002: 5, # 最大(tied)
116
+ 1003: 4
117
+ }
118
+
119
+ # POC = level 1001 (100.10) - 選擇最低的層級
120
+ ```
121
+
122
+ ### Value Area (VA)
123
+
124
+ **定義**: 包含 70%(預設)TPO 計數的價格範圍,由 VAH(上界)和 VAL(下界)定義。
125
+
126
+ **擴展算法**:
127
+ 1. 從 POC 開始
128
+ 2. 向上下兩側擴展
129
+ 3. 每次優先選擇 TPO 較多的方向
130
+ 4. 直到累積 TPO ≥ 目標百分比(70%)
131
+
132
+ **交易意義**:
133
+ - **VAH (Value Area High)**: 阻力,價格突破表示強勢
134
+ - **VAL (Value Area Low)**: 支撐,價格跌破表示弱勢
135
+ - **VA 內**: 公平價值區間,適合區間交易
136
+ - **VA 外**: 價格偏離,可能反轉或趨勢延續
137
+
138
+ **範例**:
139
+ ```
140
+ 總 TPO = 14, 目標 70% = 10
141
+
142
+ 擴展過程:
143
+ 1. 起始: va_levels=[1002], count=5
144
+ 2. 上方 1003(4) vs 下方 1001(2) → 選上方
145
+ va_levels=[1002,1003], count=9
146
+ 3. 上方 1004(2) vs 下方 1001(2) → 選上方(相等時選上方)
147
+ va_levels=[1002,1003,1004], count=11 ≥ 10 ✓
148
+
149
+ 結果:
150
+ - VAH = 1004 * 0.10 = 100.40
151
+ - VAL = 1002 * 0.10 = 100.20
152
+ - Value Area = 0.20
153
+ ```
154
+
155
+ ### Anchor Segmentation (時間窗口分段)
156
+
157
+ **定義**: 將時間序列劃分為固定窗口,每個窗口計算獨立的 Market Profile。
158
+
159
+ **固定窗口語義** (ADR-052 v3.0):
160
+ - 每 `lookback_days` 個 anchor 創建一個 segment
161
+ - Segments 之間**無重疊數據**
162
+ - 確保 TPO 計算不會重複計算相同的 K 線
163
+
164
+ **範例**:
165
+ ```python
166
+ # 每日 09:15 anchor, lookback=1
167
+ # - Wed 09:15 的 segment = [Tue 09:15, Wed 09:15)
168
+ # - Thu 09:15 的 segment = [Wed 09:15, Thu 09:15)
169
+ # 每個 segment 包含 24 小時的數據
170
+
171
+ anchor = create_daily_anchor(hour=9, minute=15, lookback_days=1)
172
+ ```
173
+
174
+ #### Daily Anchor 的反直覺特性
175
+
176
+ **重要**: Daily anchor 並非「每天都會有值」,而是「每 N 天一次」。
177
+
178
+ ```python
179
+ # 常見誤解: lookback_days=3 表示「過去 3 天」
180
+ anchor = create_daily_anchor(hour=9, minute=15, lookback_days=3)
181
+
182
+ # 實際行為: 每 3 天觸發一次計算
183
+ # - Mon 09:15: 計算 segment(包含 Fri 09:15 ~ Mon 09:15 數據)
184
+ # - Tue 09:15: 無值(不重疊)
185
+ # - Wed 09:15: 無值(不重疊)
186
+ # - Thu 09:15: 計算 segment(包含 Mon 09:15 ~ Thu 09:15 數據)
187
+ ```
188
+
189
+ **如何實現「每天都有 Market Profile」?**
190
+
191
+ ```python
192
+ # ✅ 方案 1: lookback_days=1(24 小時滾動窗口)
193
+ anchor = create_daily_anchor(hour=9, minute=15, lookback_days=1)
194
+ # - Mon 09:15: 計算 [Fri 09:15, Mon 09:15)
195
+ # - Tue 09:15: 計算 [Mon 09:15, Tue 09:15)
196
+ # - Wed 09:15: 計算 [Tue 09:15, Wed 09:15)
197
+ # 每天都有值 ✓
198
+
199
+ # ❌ 方案 2: lookback_days=3(每 3 天一次)
200
+ # 只有 Mon, Thu, Sun, ... 有值,中間 2 天為空
201
+ ```
202
+
203
+ #### Weekly Anchor 解決多天窗口問題
204
+
205
+ 如果想要「每天都有過去 5 天的 Market Profile」,需創建 5 個 weekly anchor 指標錯開觸發日。詳見 [使用範例 > 範例 7](#範例-7-每天都有-5-天窗口的-market-profile)。
206
+
207
+ ---
208
+
209
+ ## API 參考
210
+
211
+ ### MarketProfileIndicator
212
+
213
+ ```python
214
+ class MarketProfileIndicator(BaseModel):
215
+ type: Literal["MarketProfile"] = "MarketProfile"
216
+ anchor_config: Dict[str, Any] # 必填
217
+ tick_size: float # 必填,必須 > 0
218
+ value_area_pct: float = 0.7 # 預設 70%
219
+ fields: Optional[List[str]] = None # 可選欄位選擇
220
+ shape_config: Optional[Dict[str, Any]] = None # 可選形狀識別
221
+ ```
222
+
223
+ #### 參數說明
224
+
225
+ ##### `anchor_config` (必填)
226
+
227
+ 時間窗口配置,使用 helper functions 創建:
228
+
229
+ **選項 1: 每日 Anchor**
230
+ ```python
231
+ from tradepose_models.indicators.market_profile import create_daily_anchor
232
+
233
+ # 24 小時滾動窗口(最常用)
234
+ anchor = create_daily_anchor(
235
+ hour=9, # 小時 (0-23)
236
+ minute=15, # 分鐘 (0-59)
237
+ lookback_days=1 # 回溯天數
238
+ )
239
+ # 結果: 每日 09:15 的 segment = [前一日 09:15, 當日 09:15)
240
+ ```
241
+
242
+ **選項 2: 每週 Anchor**
243
+ ```python
244
+ from tradepose_models.indicators.market_profile import create_weekly_anchor
245
+ from tradepose_models.enums import Weekday
246
+
247
+ # 週線窗口
248
+ anchor = create_weekly_anchor(
249
+ weekday=Weekday.MON, # 或 0, 或 "Mon"
250
+ hour=9,
251
+ minute=0,
252
+ lookback_days=5 # 約 1 週
253
+ )
254
+ ```
255
+
256
+ **選項 3: Initial Balance 窗口**
257
+ ```python
258
+ from tradepose_models.indicators.market_profile import create_initial_balance_anchor
259
+
260
+ # 交易日開盤後 60 分鐘(09:00 ~ 10:00)
261
+ anchor = create_initial_balance_anchor(
262
+ start_hour=9,
263
+ start_minute=0,
264
+ end_hour=10,
265
+ end_minute=0
266
+ )
267
+ # 結果: 每天 09:00-10:00 計算一次 Market Profile(IB 時段)
268
+
269
+ # 使用案例: RTH (Regular Trading Hours) 分析
270
+ # 美股 RTH = 09:30 ~ 16:00
271
+ anchor_rth = create_initial_balance_anchor(
272
+ start_hour=9,
273
+ start_minute=30,
274
+ end_hour=16,
275
+ end_minute=0
276
+ )
277
+ ```
278
+
279
+ ##### `tick_size` (必填)
280
+
281
+ 最小價格單位,用於劃分價格層級。
282
+
283
+ **選擇指南**:
284
+
285
+ | 商品 | tick_size | 說明 |
286
+ |------|-----------|------|
287
+ | ES (E-mini S&P 500) | `0.25` | 最小變動 0.25 點 |
288
+ | NQ (E-mini Nasdaq) | `0.25` | 最小變動 0.25 點 |
289
+ | TXF (台指期) | `1.0` | 最小變動 1 點 |
290
+ | BTC (比特幣) | `1.0` ~ `10.0` | 根據價格範圍調整 |
291
+ | EUR/USD (外匯) | `0.0001` | 1 pip |
292
+ | 股票 | `0.01` | 1 分 |
293
+
294
+ **原則**:
295
+ - ✅ **使用商品的最小變動單位或其整數倍**
296
+ - ❌ **太小**: TPO 分布過於分散,難以識別集中區域
297
+ - ❌ **太大**: TPO 分布過於集中,失去價格細節
298
+
299
+ > **驗證標準**: 好的 tick_size 應使 Value Area 覆蓋 5-15 個價格層級,POC 清晰可辨
300
+
301
+ ##### `value_area_pct` (可選,預設 0.7)
302
+
303
+ Value Area 百分比,表示要包含多少 TPO。
304
+
305
+ - **標準值**: `0.7` (70%) - 行業標準
306
+ - **範圍**: `(0, 1)` - 必須是小數,不是百分比數字
307
+ - **調整時機**:
308
+ - 高波動市場: 可提高到 `0.8` 或 `0.85`
309
+ - 低波動市場: 可降低到 `0.6` 或 `0.65`
310
+
311
+ **常見錯誤**:
312
+ ```python
313
+ # ❌ 錯誤
314
+ value_area_pct = 70 # 應該是 0.7,不是 70
315
+
316
+ # ✅ 正確
317
+ value_area_pct = 0.7
318
+ ```
319
+
320
+ ##### `fields` (可選,預設 None)
321
+
322
+ 選擇要保留的欄位,減少記憶體使用。
323
+
324
+ **可用欄位**:
325
+ - `"poc"`: Point of Control
326
+ - `"vah"`: Value Area High
327
+ - `"val"`: Value Area Low
328
+ - `"value_area"`: VAH - VAL 範圍
329
+ - `"tpo_distribution"`: TPO 分布詳情(List<Struct{price, count, periods}>)
330
+ - `"segment_id"`: 時間區段 ID(自動包含,用於 ffill)
331
+ - `"profile_shape"`: Profile 形狀類型(需啟用 `shape_config`)
332
+
333
+ **範例**:
334
+ ```python
335
+ # 只保留 POC 和 VAH(記憶體優化)
336
+ # 注意: segment_id 會自動包含(用於 ffill),無需手動指定
337
+ mp = MarketProfileIndicator(
338
+ anchor_config=anchor,
339
+ tick_size=0.25,
340
+ fields=["poc", "vah"] # segment_id 自動添加
341
+ )
342
+
343
+ # 或保留所有欄位(預設)
344
+ mp = MarketProfileIndicator(
345
+ anchor_config=anchor,
346
+ tick_size=0.25,
347
+ fields=None # 包含所有 7 個欄位
348
+ )
349
+
350
+ # ⚠️ 記憶體提示:
351
+ # - tpo_distribution 是嵌套結構,佔用較多記憶體
352
+ # - 如果不需要進階分析(Single Prints、視覺化),可以不包含
353
+ ```
354
+
355
+ ##### `shape_config` (可選,實驗性功能)
356
+
357
+ Profile 形狀識別配置。詳見 [形狀識別](#形狀識別-實驗性功能) 章節。
358
+
359
+ ### Helper Functions
360
+
361
+ #### `create_daily_anchor()`
362
+
363
+ ```python
364
+ def create_daily_anchor(
365
+ hour: int,
366
+ minute: int,
367
+ lookback_days: int = 1,
368
+ ) -> Dict[str, Any]
369
+ ```
370
+
371
+ 創建每日 Anchor 配置。
372
+
373
+ **參數**:
374
+ - `hour`: 小時 (0-23)
375
+ - `minute`: 分鐘 (0-59)
376
+ - `lookback_days`: 回溯天數(預設 1)
377
+
378
+ **返回**: Anchor 配置 dict
379
+
380
+ **範例**:
381
+ ```python
382
+ # 每日 09:15 結束,回溯 1 天
383
+ anchor = create_daily_anchor(9, 15, 1)
384
+
385
+ # 視覺化
386
+ # ├─────────────────────────┤ Segment 1
387
+ # Tue 09:15 Wed 09:15
388
+ # ├─────────────────────────┤ Segment 2
389
+ # Wed 09:15 Thu 09:15
390
+ ```
391
+
392
+ #### `create_weekly_anchor()`
393
+
394
+ ```python
395
+ def create_weekly_anchor(
396
+ weekday: Union[int, str, Weekday],
397
+ hour: int,
398
+ minute: int,
399
+ lookback_days: int = 5,
400
+ ) -> Dict[str, Any]
401
+ ```
402
+
403
+ 創建每週 Anchor 配置。
404
+
405
+ **參數**:
406
+ - `weekday`: 星期幾
407
+ - `int`: 0=週一, 1=週二, ..., 6=週日
408
+ - `str`: "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"
409
+ - `Weekday` enum: `Weekday.MON`, `Weekday.TUE`, ...
410
+ - `hour`: 小時 (0-23)
411
+ - `minute`: 分鐘 (0-59)
412
+ - `lookback_days`: 回溯天數(預設 5)
413
+
414
+ **範例**:
415
+ ```python
416
+ # 使用整數
417
+ anchor = create_weekly_anchor(0, 9, 15, 5) # 週一
418
+
419
+ # 使用字串
420
+ anchor = create_weekly_anchor("Mon", 9, 15, 5)
421
+
422
+ # 使用 Weekday enum
423
+ from tradepose_models.enums import Weekday
424
+ anchor = create_weekly_anchor(Weekday.MON, 9, 15, 5)
425
+ ```
426
+
427
+ #### `create_profile_shape_config()`
428
+
429
+ ```python
430
+ def create_profile_shape_config(
431
+ early_period_ratio: float = 0.15,
432
+ late_period_ratio: float = 0.15,
433
+ trend_ib_max_ratio: float = 0.20,
434
+ trend_monotonic_threshold: float = 0.60,
435
+ trend_imbalance_threshold: float = 0.70,
436
+ pshape_concentration_threshold: float = 0.60,
437
+ bshape_valley_threshold: float = 0.70,
438
+ normal_symmetry_threshold: float = 0.30,
439
+ ) -> Dict[str, Any]
440
+ ```
441
+
442
+ 創建 Profile 形狀識別配置。詳見 [形狀識別](#形狀識別-實驗性功能) 章節。
443
+
444
+ ---
445
+
446
+ ## 配置指南
447
+
448
+ ### 常見使用場景
449
+
450
+ #### 場景 A: 24 小時滾動窗口(最常用)
451
+
452
+ ```python
453
+ # 每日 09:15 為錨點,回溯 1 個交易日
454
+ anchor = create_daily_anchor(9, 15, 1)
455
+
456
+ # 範例:
457
+ # - Wed 09:15 的 segment = [Tue 09:15, Wed 09:15)
458
+ # - Thu 09:15 的 segment = [Wed 09:15, Thu 09:15)
459
+ ```
460
+
461
+ **適用於**: 日內交易、隔夜持倉分析
462
+
463
+ #### 場景 B: Initial Balance (IB) 窗口
464
+
465
+ ```python
466
+ # 交易日開盤後 60 分鐘(09:00 ~ 10:00)
467
+ anchor = {
468
+ "start_rule": {"type": "DailyTime", "hour": 9, "minute": 0},
469
+ "end_rule": {"type": "DailyTime", "hour": 10, "minute": 0},
470
+ "lookback_days": 0
471
+ }
472
+ ```
473
+
474
+ **適用於**: Initial Balance 交易策略、開盤區間突破
475
+
476
+ **重要**: 當 `lookback_days=0` 時,必須確保 `start_rule < end_rule`
477
+
478
+ #### 場景 C: 週線窗口
479
+
480
+ ```python
481
+ # 每週一 09:00 為錨點,回溯 1 週
482
+ anchor = create_weekly_anchor(Weekday.MON, 9, 0, 5)
483
+ ```
484
+
485
+ **適用於**: 波段交易、週線分析
486
+
487
+ > **驗證規則**: 詳見 [API 參考 > anchor_config](#anchor_config-必填) 的驗證說明
488
+
489
+ ### TPO 時間區間設定
490
+
491
+ #### Bar 頻率選擇
492
+
493
+ **行業標準:30 分鐘 TPO**
494
+
495
+ J. Peter Steidlmayer 於 1980 年代在 CBOT 設計 Market Profile 時,選用 30 分鐘作為標準 TPO 區間。這個設計使得:
496
+ - **Initial Balance**(第一個小時)= 2 個 TPO 區間(A 和 B 時段)
497
+ - **標準 6.5 小時交易日** = 13 個 TPO 時段(A 到 M)
498
+
499
+ **為什麼是 30 分鐘?**
500
+
501
+ | 時間區間 | 優點 | 缺點 | 適用場景 |
502
+ |---------|------|------|---------
503
+ | **1-5 分鐘** | 極度精細、捕捉微觀結構 | 噪音多、Profile 過度破碎 | 極短線剝頭皮 |
504
+ | **15 分鐘** | 比 30m 更細緻 | IB = 4 期間(過多) | 短線交易者 |
505
+ | **30 分鐘** | 行業標準、IB = 2 期間、最多研究支持 | 對日內短線稍粗 | **大多數交易者(推薦)** |
506
+ | **60 分鐘** | 適合宏觀視角 | IB = 1 期間(失去意義) | 波段/長線交易者 |
507
+ | **Daily/Weekly** | 宏觀趨勢分析 | 失去日內結構 | Composite Profile |
508
+
509
+ **關鍵洞察**:
510
+ - 30 分鐘是「平衡點」:足夠細緻捕捉日內結構,又不會過度破碎
511
+ - Initial Balance 的定義依賴 30 分鐘:「前兩個 TPO 時段」= 1 小時
512
+ - 如果用 60 分鐘,IB 只有 1 個時段,無法形成有效範圍
513
+ - 如果用 15 分鐘,IB 有 4 個時段,定義變得模糊
514
+
515
+ **Volume Profile vs Market Profile**:
516
+ - **Market Profile**(TPO 為主):建議用 30 分鐘
517
+ - **Volume Profile**(成交量為主):可用更小區間(1m、5m、tick)因為不依賴 TPO 字母標記
518
+
519
+ #### Session 時間設定
520
+
521
+ **RTH vs ETH**:
522
+ - **RTH** (Regular Trading Hours): 正規交易時段,流動性最高,Value Area 最有意義
523
+ - **ETH** (Extended Trading Hours): 延長時段,噪音較多,可參考極值作為支撐/阻力
524
+ - **建議**: 日內交易優先使用 RTH,24 小時市場(外匯、加密)可自定義主要時段
525
+
526
+ #### Composite Profile 時間框架
527
+
528
+ **Daily Profile(每日)**:
529
+ - **計算時機**:每日 RTH 收盤後
530
+ - **重置時機**:每日 RTH 開盤時
531
+ - **用途**:識別當日日型、POC、Value Area
532
+
533
+ **Composite Profile(複合)**:
534
+ - **Weekly**:5-10 個交易日(約 1-2 週)
535
+ - **Monthly**:20-30 個交易日
536
+ - **計算時機**:滾動計算或固定週期結束時
537
+ - **用途**:識別更大的價值區、支撐/阻力
538
+
539
+ **Developing Profile(發展中)**:
540
+ - **計算時機**:實時,隨每根 K 線更新
541
+ - **用途**:日內交易決策
542
+
543
+ #### Session 配置範例
544
+
545
+ ```python
546
+ # 美國股票/期貨(RTH)
547
+ anchor = create_daily_anchor(hour=9, minute=30, lookback_days=1)
548
+ # session: 9:30-16:00 ET
549
+ # IB: 9:30-10:30 ET (2 × 30分鐘)
550
+
551
+ # 外匯(倫敦時段)
552
+ anchor = create_daily_anchor(hour=8, minute=0, lookback_days=1)
553
+ # session: 8:00-17:00 London
554
+ # IB: 8:00-9:00 London
555
+
556
+ # 加密貨幣(自定義 24 小時)
557
+ anchor = create_daily_anchor(hour=0, minute=0, lookback_days=1)
558
+ # 或根據主要交易所活躍時段調整
559
+ ```
560
+
561
+ ---
562
+
563
+ ## 輸出欄位說明
564
+
565
+ Market Profile 計算結果為 **Struct Series**,包含以下欄位:
566
+
567
+ ### 欄位表格
568
+
569
+ | 欄位 | 類型 | 範圍 | 說明 | 交易用途 |
570
+ |------|------|------|------|----------|
571
+ | `poc` | `f64` | Price | Point of Control - TPO 計數最多的價格層級 | 強支撐/阻力,價格磁吸效應 |
572
+ | `vah` | `f64` | Price | Value Area High - 70% TPO 區間上界 | 阻力,突破表示強勢 |
573
+ | `val` | `f64` | Price | Value Area Low - 70% TPO 區間下界 | 支撐,跌破表示弱勢 |
574
+ | `value_area` | `f64` | ≥0 | VAH - VAL 範圍 | 公平價值寬度,波動度指標 |
575
+ | `tpo_distribution` | `List<Struct>` | - | TPO 分布詳情:每個價格層級的計數和時段列表 | Single Prints 識別、進階分析、視覺化 |
576
+ | `segment_id` | `u64` | - | 時間區段 ID(已 ffill) | Window 函數的 partition key |
577
+ | `profile_shape` | `str` | enum | Profile 形狀類型 | 市場狀態判斷,策略選擇 |
578
+
579
+ ### 欄位關係
580
+
581
+ ```
582
+ 恆成立的不等式:
583
+ vah >= poc >= val
584
+
585
+ value_area = vah - val
586
+
587
+ profile_shape 可能值:
588
+ - "p_shaped": P型(看空反轉信號)
589
+ - "b_shaped": b型(看多反轉信號)
590
+ - "trend_day": 趨勢日(順勢交易)
591
+ - "normal": 正態分布(區間交易)
592
+ - "b_double_distribution": B型雙峰(區間交易)
593
+ - "undefined": 無法分類
594
+ ```
595
+
596
+ ### tpo_distribution 欄位詳解
597
+
598
+ `tpo_distribution` 是一個嵌套資料結構,包含完整的 TPO 分布資訊。
599
+
600
+ **資料結構**:
601
+ ```python
602
+ List<Struct{
603
+ price: f64, # 價格層級(實際價格,已乘以 tick_size)
604
+ count: u32, # 該層級的 TPO 計數
605
+ periods: List<u32> # 貢獻此層級的時段索引列表
606
+ }>
607
+ ```
608
+
609
+ **範例資料**:
610
+ ```python
611
+ # 假設 tick_size=0.10
612
+ [
613
+ {price: 100.00, count: 3, periods: [0, 1, 2]},
614
+ {price: 100.10, count: 5, periods: [1, 2, 3, 4, 5]}, # POC
615
+ {price: 100.20, count: 4, periods: [2, 3, 4, 5]},
616
+ {price: 100.30, count: 2, periods: [4, 5]},
617
+ {price: 100.40, count: 1, periods: [5]}, # Single Print
618
+ ]
619
+ ```
620
+
621
+ **使用範例**:
622
+
623
+ ```python
624
+ # 識別 Single Prints(72% 方向準確度指標)
625
+ df = df.with_column(
626
+ pl.col("market_profile")
627
+ .struct.field("tpo_distribution")
628
+ .list.eval(pl.element().struct.field("count") == 1)
629
+ .list.sum()
630
+ .alias("single_print_count")
631
+ )
632
+
633
+ # 提取價格和計數用於視覺化
634
+ df = df.with_columns([
635
+ pl.col("market_profile").struct.field("tpo_distribution")
636
+ .list.eval(pl.element().struct.field("price")).alias("tpo_prices"),
637
+ pl.col("market_profile").struct.field("tpo_distribution")
638
+ .list.eval(pl.element().struct.field("count")).alias("tpo_counts")
639
+ ])
640
+ ```
641
+
642
+ **進階用途**:
643
+ - ✅ **識別 Single Prints**:72% 方向準確度指標(ManOverMarket 研究)
644
+ - ✅ **分析時段分布**:了解哪些時段對哪些價格貢獻最多
645
+ - ✅ **自定義形狀識別**:基於 TPO 分布實現自己的算法
646
+ - ✅ **視覺化 Market Profile**:繪製完整的 TPO Chart
647
+ - ✅ **TPO 密度分析**:檢測 Trend Day 特徵(≤5 TPOs/層級)
648
+
649
+ **記憶體注意**:
650
+ - `tpo_distribution` 是嵌套結構,包含多層資料
651
+ - 對於長時段(如 100+ K 線),可能包含 50-100 個價格層級
652
+ - 如果不需要進階分析,建議使用 `fields` 參數排除此欄位
653
+
654
+ ### 展開 Struct 欄位
655
+
656
+ Market Profile 返回的是 Struct,需要展開才能使用各欄位:
657
+
658
+ ```python
659
+ import polars as pl
660
+
661
+ # 批量展開多個欄位(推薦)
662
+ df = df.with_columns([
663
+ pl.col("market_profile").struct.field("poc").alias("mp_poc"),
664
+ pl.col("market_profile").struct.field("vah").alias("mp_vah"),
665
+ pl.col("market_profile").struct.field("val").alias("mp_val"),
666
+ pl.col("market_profile").struct.field("value_area").alias("mp_va"),
667
+ ])
668
+
669
+ # 或使用 unnest 展開所有欄位
670
+ df = df.unnest("market_profile")
671
+ ```
672
+
673
+ ### 使用 segment_id 進行 Window 操作
674
+
675
+ `segment_id` 欄位已自動 ffill,非常適合作為 Polars window 函數的 partition key。
676
+
677
+ **為什麼使用 segment_id?**
678
+ - Market Profile 的其他欄位(poc, vah, val)是**稀疏的**(只在 anchor 點有值)
679
+ - `segment_id` 已 ffill,每個 segment 的所有行都有相同的 ID
680
+ - 輕量級(只有 8 bytes),ffill 成本低
681
+
682
+ ```python
683
+ # 獲取當前 segment 的 POC(傳播到所有行)
684
+ df = df.with_column(
685
+ pl.col("market_profile").struct.field("poc")
686
+ .over([pl.col("market_profile").struct.field("segment_id")])
687
+ .first()
688
+ .alias("segment_poc")
689
+ )
690
+
691
+ # 判斷價格相對 Value Area 的位置
692
+ df = df.with_columns([
693
+ pl.col("market_profile").struct.field("vah")
694
+ .over([pl.col("market_profile").struct.field("segment_id")]).first().alias("segment_vah"),
695
+ pl.col("market_profile").struct.field("val")
696
+ .over([pl.col("market_profile").struct.field("segment_id")]).first().alias("segment_val"),
697
+ ]).with_column(
698
+ pl.when(pl.col("close") > pl.col("segment_vah")).then(pl.lit("above_va"))
699
+ .when(pl.col("close") < pl.col("segment_val")).then(pl.lit("below_va"))
700
+ .otherwise(pl.lit("inside_va")).alias("va_position")
701
+ )
702
+ ```
703
+
704
+ > **提示**: 總是使用 `.first()` 聚合,因為 POC/VAH/VAL 在同一 segment 內的所有行都相同。
705
+
706
+ ---
707
+
708
+ ## 形狀識別 (實驗性功能)
709
+
710
+ ### 實驗性功能警告
711
+
712
+ **狀態**: BETA - 算法可能在未來版本中變更
713
+
714
+ **準確度**: 尚未在大規模市場數據上驗證
715
+
716
+ **行業差距**: 無開源參考實現存在(根據 KB-006 研究)
717
+
718
+ **當前限制**:
719
+ 1. ❌ **Single Prints 檢測**未實現(業界 72% 方向準確度特徵)
720
+ 2. ❌ **TPO 密度分析**未實現(業界標準: ≤5 TPOs/層級)
721
+ 3. ❌ **統計分布測試**未實現(偏度/峰度)
722
+
723
+ **建議**:
724
+ - ⚠️ 在生產交易中謹慎使用
725
+ - ✅ 根據您的特定市場數據驗證
726
+ - ✅ 將形狀視為**補充信號**,而非主要信號
727
+ - ✅ 預設參數基於行業標準,但可能需要調整
728
+
729
+ 詳見 **ADR-060** 和 **KB-006** 了解詳細算法文件。
730
+
731
+ ### Profile Shape 類型
732
+
733
+ Market Profile 形狀反映市場參與者的行為模式:
734
+
735
+ #### 1. P-Shaped (P型) - 看漲信號(Short Covering)
736
+
737
+ **結構**: 下窄上寬,像字母 "P"
738
+
739
+ **形成機制**: Short Covering(空頭回補)
740
+ - **本質**: Old Business(舊業務)- 既有空頭平倉,而非新買家進場
741
+ - **前置條件**: 通常跟隨一個或多個下跌日(市場過度做空)
742
+
743
+ **視覺特徵**:
744
+ ```
745
+ 價格 TPO 分布
746
+ 1010 CDEFGH ← 高位盤整區(bulge,寬)
747
+ 1009 CDEFGH
748
+ 1008 CDEFGH
749
+ 1007 C
750
+ 1006 C ← 快速上漲區域
751
+ 1005 B
752
+ 1004 AB ← 早期低位開盤(stem/tail,窄)
753
+ 1003 AB
754
+ 1002 A ← 開盤在低位,空頭被迫回補
755
+ 1001 A ← Single Print Buying Tail
756
+ ```
757
+
758
+ **時間序列特徵**:
759
+ - **開盤位置**: 開盤在低位(near the low)
760
+ - **早盤**: 價格從低位快速上漲(空頭被迫回補,形成 stem)
761
+ - **晚盤**: 上漲後在高位盤整/分配(distributing,形成 bulge)
762
+ - **結果**: 形成下窄上寬的 "P" 形狀
763
+
764
+ **交易含義**:
765
+ - **市場狀態**: 空頭回補,可能是下跌趨勢結束的信號
766
+ - **信號方向**: 看漲信號(Bullish signal)
767
+ - **語義解讀**:
768
+ - 在下跌趨勢中出現 → 可能標誌趨勢結束/反轉
769
+ - 在上漲趨勢中出現 → 趨勢延續信號
770
+ - **策略**: POC 是良好的支撐區域,回調到 POC 可考慮做多
771
+ - **注意**: 空頭回補是「暫時性強勢」,需要「新業務」(New Business)才能延續突破
772
+
773
+ **配置參數**:
774
+ - `pshape_concentration_threshold`: 預設 0.60(60% TPO 集中度)
775
+
776
+ **價格區域劃分**(實現細節):
777
+
778
+ 程式碼使用**硬編碼的 30% 閾值**劃分價格區域:
779
+
780
+ - **高位區**: 價格 > 70% 位置(`max_level - 30% * price_range`)
781
+ - **低位區**: 價格 < 30% 位置(`min_level + 30% * price_range`)
782
+ - **中間區**: 30%-70% 之間
783
+
784
+ **範例**:
785
+ ```
786
+ 假設價格範圍 100.00 - 110.00 (10 點範圍)
787
+
788
+ 高位區: > 107.00 (110 - 30% * 10)
789
+ 中間區: 103.00 - 107.00
790
+ 低位區: < 103.00 (100 + 30% * 10)
791
+
792
+ P-Shaped 檢測邏輯:
793
+ - 早期 15% 時段的 60%+ TPO 必須在低位區 (< 103.00) ← 開盤在低位
794
+ - 晚期 15% 時段的 60%+ TPO 必須在高位區 (> 107.00) ← 收盤在高位盤整
795
+ ```
796
+
797
+ **注意**: 30% 閾值目前為硬編碼,未來版本可能改為可配置參數。
798
+
799
+ #### 2. b-Shaped (b型) - 看跌信號(Long Liquidation)
800
+
801
+ **結構**: 上窄下寬,像小寫字母 "b"
802
+
803
+ **形成機制**: Long Liquidation(多頭平倉)
804
+ - **本質**: Old Business(舊業務)- 既有多頭離場,而非新賣家進場
805
+ - **前置條件**: 通常跟隨一個或多個上漲日(市場過度做多)
806
+
807
+ **視覺特徵**:
808
+ ```
809
+ 價格 TPO 分布
810
+ 1010 A ← Single Print Selling Tail
811
+ 1009 A ← 開盤在高位,多頭被迫平倉
812
+ 1008 AB ← 早期高位開盤(stem/tail,窄)
813
+ 1007 AB
814
+ 1006 B ← 快速下跌區域
815
+ 1005 C
816
+ 1004 CDEFGH ← 低位盤整區(bulge,寬)
817
+ 1003 CDEFGH
818
+ 1002 CDEFGH
819
+ 1001 CDEFGH
820
+ ```
821
+
822
+ **時間序列特徵**:
823
+ - **開盤位置**: 開盤在高位(near the high / top of the profile)
824
+ - **早盤**: 價格從高位快速下跌(多頭被迫平倉,形成 stem)
825
+ - **晚盤**: 下跌後在低位盤整/分配(distributing,形成 bulge)
826
+ - **結果**: 形成上窄下寬的 "b" 形狀
827
+
828
+ **交易含義**:
829
+ - **市場狀態**: 多頭平倉,可能是上漲趨勢結束的信號
830
+ - **信號方向**: 看跌信號(Bearish signal)
831
+ - **語義解讀**:
832
+ - 在上漲趨勢中出現 → 可能標誌趨勢結束/反轉
833
+ - 在下跌趨勢中出現 → 趨勢延續信號(consolidation before continuation)
834
+ - **策略**: POC 是良好的阻力區域,反彈到 POC 可考慮做空
835
+ - **注意**: 多頭平倉是「暫時性弱勢」,需要「新業務」(New Business)才能延續突破
836
+
837
+ **價格區域劃分**(與 P-Shaped 相同):
838
+
839
+ 程式碼使用**硬編碼的 30% 閾值**劃分價格區域:
840
+
841
+ - **高位區**: 價格 > 70% 位置(`max_level - 30% * price_range`)
842
+ - **低位區**: 價格 < 30% 位置(`min_level + 30% * price_range`)
843
+ - **中間區**: 30%-70% 之間
844
+
845
+ **b-Shaped 檢測邏輯**(與 P-Shaped 相反):
846
+ - 早期 15% 時段的 60%+ TPO 必須在**高位區** ← 開盤在高位
847
+ - 晚期 15% 時段的 60%+ TPO 必須在**低位區** ← 收盤在低位盤整
848
+
849
+ **注意**: 30% 閾值目前為硬編碼,未來版本可能改為可配置參數。
850
+
851
+ #### 3. Trend Day (趨勢日) - 趨勢市場
852
+
853
+ **結構**: 持續單向移動
854
+
855
+ **視覺特徵**:
856
+ ```
857
+ 價格 TPO 分布
858
+ 1010 FGHIJK ← 晚期高位(上漲趨勢)
859
+ 1009 EFGHIJK
860
+ 1008 DEFGH
861
+ 1007 CDE
862
+ 1006 CD
863
+ 1005 C
864
+ 1004 B
865
+ 1003 AB ← 小 Initial Balance
866
+ 1002 AB
867
+ 1001 A ← 早期低位
868
+ ```
869
+
870
+ **檢測條件**(三者必須同時滿足):
871
+
872
+ **條件 1: 小 Initial Balance (IB)**
873
+ ```python
874
+ # IB = 前 2 個時段的價格範圍
875
+ ib_range = max_price_first_2_periods - min_price_first_2_periods
876
+ total_range = max_price_overall - min_price_overall
877
+ ib_ratio = ib_range / total_range
878
+
879
+ # 必須: IB < 20% 總範圍
880
+ if ib_ratio > 0.20:
881
+ return False # 不是 Trend Day
882
+ ```
883
+
884
+ **條件 2: 單向移動**
885
+ ```python
886
+ # 計算每個時段的價格中心
887
+ # 檢查價格是否單調遞增或遞減
888
+ monotonic_ratio = count_monotonic_movement / total_comparisons
889
+
890
+ # 必須: 60%+ 單向性
891
+ if monotonic_ratio < 0.60:
892
+ return False # 不是 Trend Day
893
+ ```
894
+
895
+ **條件 3: TPO 不平衡**
896
+ ```python
897
+ # 早期 15% 時段 vs 晚期 15% 時段
898
+ early_tpo_count = count_tpos_in_early_periods()
899
+ late_tpo_count = count_tpos_in_late_periods()
900
+
901
+ concentration = max(early_tpo, late_tpo) / (early_tpo + late_tpo)
902
+
903
+ # 必須: 70%+ 集中在一側
904
+ if concentration < 0.70:
905
+ return False # 不是 Trend Day
906
+
907
+ return True # 所有 3 個條件滿足 = Trend Day
908
+ ```
909
+
910
+ **行業標準特徵**(部分未實現):
911
+ - ✅ 小 Initial Balance (< 20% 範圍)
912
+ - ✅ 單向價格擴展
913
+ - ✅ 早期/晚期 TPO 不平衡
914
+ - ❌ **TPO 密度**: 應 ≤5 TPOs/層級(未檢測)
915
+ - ❌ **85% 範圍在 IB 外**(未檢測)
916
+
917
+ **交易含義**:
918
+ - **市場狀態**: 強趨勢,單邊市場
919
+ - **策略**: 順勢交易,避免逆勢
920
+ - **特徵**: 不區分上漲/下跌(統一為 TrendDay)
921
+
922
+ **配置參數**:
923
+ - `trend_ib_max_ratio`: 預設 0.20(IB < 20%)
924
+ - `trend_monotonic_threshold`: 預設 0.60(60% 單向)
925
+ - `trend_imbalance_threshold`: 預設 0.70(70% 不平衡)
926
+
927
+ #### 4. B-Double Distribution (B型雙峰) - 區間市場
928
+
929
+ **結構**: 兩個獨立的 POC(雙峰分布)
930
+
931
+ **視覺特徵**:
932
+ ```
933
+ 價格 TPO 分布
934
+ 1009 DEFGH ← 峰 2
935
+ 1008 DEFGH
936
+ 1007 D
937
+ 1006 ← 深谷
938
+ 1005 A ← 谷
939
+ 1004 ← 深谷
940
+ 1003 ABC ← 峰 1
941
+ 1002 ABC
942
+ 1001 AB
943
+ ```
944
+
945
+ **特徵**:
946
+ - 兩個明顯的局部最大值(POCs)
947
+ - 兩峰之間有深谷(< 70% 峰值 TPO 計數)
948
+ - 峰之間距離 > 20% 價格範圍
949
+
950
+ **交易含義**:
951
+ - **市場狀態**: 區間震蕩,兩個接受區域
952
+ - **策略**: 區間交易,在兩個 POC 之間操作
953
+
954
+ **配置參數**:
955
+ - `bshape_valley_threshold`: 預設 0.70(谷深度閾值)
956
+
957
+ #### 5. Normal (正態分布) - 平衡市場
958
+
959
+ **結構**: 對稱的鐘型分布
960
+
961
+ **視覺特徵**:
962
+ ```
963
+ 價格 TPO 分布
964
+ 1007 C
965
+ 1006 BCD
966
+ 1005 ABCDEFGH ← POC(中心)
967
+ 1004 BCD
968
+ 1003 C
969
+ ```
970
+
971
+ **特徵**:
972
+ - POC 在價格範圍中間(30%-70%)
973
+ - 對稱的 TPO 分布
974
+ - 兩側差異 < 30%
975
+ - **行業標準**: 85% 範圍在 IB 內(未檢測)
976
+
977
+ **交易含義**:
978
+ - **市場狀態**: 平衡市場,供需均衡
979
+ - **策略**: 區間交易,VAH 做空、VAL 做多
980
+ - **特徵**: 50-60% 的交易日是 Normal 型
981
+
982
+ **配置參數**:
983
+ - `normal_symmetry_threshold`: 預設 0.30(<30% 不對稱)
984
+
985
+ #### 6. Undefined (無法分類)
986
+
987
+ 不符合任何上述形狀的 profile。
988
+
989
+ ### ProfileShapeConfig 配置
990
+
991
+ ```python
992
+ class ProfileShapeConfig(BaseModel):
993
+ # ========== 時段劃分(百分比方案)==========
994
+ early_period_ratio: float = 0.15 # 前 15% 時段
995
+ late_period_ratio: float = 0.15 # 後 15% 時段
996
+
997
+ # ========== Trend Day 識別 ==========
998
+ trend_ib_max_ratio: float = 0.20 # IB < 20% 總範圍
999
+ trend_monotonic_threshold: float = 0.60 # 60%+ 單向移動
1000
+ trend_imbalance_threshold: float = 0.70 # 70%+ 集中度
1001
+
1002
+ # ========== P/b 型識別 ==========
1003
+ pshape_concentration_threshold: float = 0.60 # 60% 集中度
1004
+
1005
+ # ========== B-double 識別 ==========
1006
+ bshape_valley_threshold: float = 0.70 # 谷深度閾值
1007
+
1008
+ # ========== Normal 型識別 ==========
1009
+ normal_symmetry_threshold: float = 0.30 # 對稱性閾值
1010
+ ```
1011
+
1012
+ ### 參數來源與調整指南
1013
+
1014
+ **預設值來源** (基於行業標準):
1015
+
1016
+ | 參數 | 預設值 | 來源 | 市場類型 |
1017
+ |------|--------|------|----------|
1018
+ | `early_period_ratio` | 0.15 | 股票市場 IB 定義 (2/13 時段) | 股票 |
1019
+ | `trend_ib_max_ratio` | 0.20 | 行業標準(ATAS, EMinimind) | 通用 |
1020
+ | `trend_monotonic_threshold` | 0.60 | 允許適度回調 | 通用 |
1021
+ | `trend_imbalance_threshold` | 0.70 | 強趨勢特徵 | 通用 |
1022
+ | `pshape_concentration` | 0.60 | 業界共識 | 通用 |
1023
+
1024
+ **調整建議**:
1025
+
1026
+ | 市場類型 | 建議設置 | 理由 |
1027
+ |----------|----------|------|
1028
+ | **股票市場** | 使用預設值 (0.15, 0.20, 0.60, 0.70) | 符合 IB 定義 |
1029
+ | **24h 期貨** | early=0.10, ib_max=0.15, monotonic=0.70, imbalance=0.75 | 更嚴格(IB 更小) |
1030
+ | **高波動** | monotonic=0.50, imbalance=0.60 | 允許更多回調 |
1031
+ | **低波動** | monotonic=0.70, imbalance=0.80 | 需要更強信號 |
1032
+
1033
+ **調整流程**:
1034
+ 1. 從預設值開始
1035
+ 2. 收集真實市場數據
1036
+ 3. 一次調整一個參數
1037
+ 4. 記錄準確度變化
1038
+ 5. 在測試集上驗證
1039
+
1040
+ ### 形狀識別優先級
1041
+
1042
+ 形狀檢測按以下順序進行(從最明顯到最不明顯):
1043
+
1044
+ ```
1045
+ 1. Trend Day (最獨特)
1046
+ ↓ (不是)
1047
+ 2. B-Double (雙峰明顯)
1048
+ ↓ (不是)
1049
+ 3. P-Shaped (晚期集中)
1050
+ ↓ (不是)
1051
+ 4. b-Shaped (早期集中)
1052
+ ↓ (不是)
1053
+ 5. Normal (對稱)
1054
+ ↓ (不是)
1055
+ 6. Undefined (fallback)
1056
+ ```
1057
+
1058
+ ### 交易策略對照表
1059
+
1060
+ | Shape | 市場狀態 | 形成機制 | 交易策略 | 成功率 |
1061
+ |-------|----------|----------|----------|--------|
1062
+ | Trend Day | 強趨勢 | 新業務進場 | 順勢交易,避免逆勢 | N/A |
1063
+ | P-Shaped | 空頭回補(Short Covering) | 舊業務平倉 | 看漲信號,回調到 POC 做多 | N/A |
1064
+ | b-Shaped | 多頭平倉(Long Liquidation) | 舊業務平倉 | 看跌信號,反彈到 POC 做空 | N/A |
1065
+ | Normal | 平衡 | 供需均衡 | 區間交易: VAH 做空、VAL 做多 | 50-60% 天數 |
1066
+ | B-Double | 區間震蕩 | 雙峰分布 | 兩個 POC 之間交易 | N/A |
1067
+
1068
+ **注意**:
1069
+ - P-shape 和 b-shape 代表「舊業務」(Old Business),需要「新業務」(New Business)才能延續突破
1070
+ - 成功率數據尚未經過大規模回測驗證
1071
+
1072
+ ### 使用形狀識別
1073
+
1074
+ ```python
1075
+ from tradepose_models.indicators.market_profile import (
1076
+ MarketProfileIndicator,
1077
+ create_daily_anchor,
1078
+ create_profile_shape_config
1079
+ )
1080
+
1081
+ # 1. 創建形狀配置(使用預設值)
1082
+ shape_config = create_profile_shape_config()
1083
+
1084
+ # 2. 或自訂參數(期貨市場)
1085
+ shape_config = create_profile_shape_config(
1086
+ early_period_ratio=0.10, # 期貨 IB 更小
1087
+ trend_ib_max_ratio=0.15, # 更嚴格
1088
+ trend_monotonic_threshold=0.70, # 更強單向性
1089
+ trend_imbalance_threshold=0.75 # 更強不平衡
1090
+ )
1091
+
1092
+ # 3. 創建指標
1093
+ mp = MarketProfileIndicator(
1094
+ anchor_config=create_daily_anchor(9, 15, 1),
1095
+ tick_size=0.25,
1096
+ shape_config=shape_config # 啟用形狀識別
1097
+ )
1098
+
1099
+ # 4. 應用後,profile_shape 欄位包含形狀類型
1100
+ ```
1101
+
1102
+ ---
1103
+
1104
+ ## 使用範例
1105
+
1106
+ ### 範例 1: 基本 POC 支撐/阻力策略
1107
+
1108
+ ```python
1109
+ import polars as pl
1110
+ from tradepose_models.indicators.market_profile import (
1111
+ MarketProfileIndicator,
1112
+ create_daily_anchor
1113
+ )
1114
+
1115
+ # 1. 配置 Market Profile
1116
+ anchor = create_daily_anchor(hour=9, minute=15, lookback_days=1)
1117
+
1118
+ mp = MarketProfileIndicator(
1119
+ anchor_config=anchor,
1120
+ tick_size=0.25, # ES 期貨
1121
+ value_area_pct=0.7
1122
+ )
1123
+
1124
+ # 2. 應用到 DataFrame(透過 strategy engine)
1125
+ # 假設已經計算完成,結果在 "mp" 欄位
1126
+
1127
+ # 3. 展開 POC 欄位
1128
+ df = df.with_column(
1129
+ pl.col("mp").struct.field("poc").alias("mp_poc")
1130
+ )
1131
+
1132
+ # 4. 生成交易信號
1133
+ df = df.with_column(
1134
+ pl.when(pl.col("close") > pl.col("mp_poc"))
1135
+ .then(pl.lit(1)) # 價格在 POC 上方 -> 多頭
1136
+ .otherwise(pl.lit(-1)) # 價格在 POC 下方 -> 空頭
1137
+ .alias("position")
1138
+ )
1139
+
1140
+ # 5. 查看結果
1141
+ print(df.select(["ts", "close", "mp_poc", "position"]))
1142
+ ```
1143
+
1144
+ ### 範例 2: Value Area 突破策略
1145
+
1146
+ ```python
1147
+ # 1. 展開 VA 相關欄位
1148
+ df = df.with_columns([
1149
+ pl.col("mp").struct.field("vah").alias("mp_vah"),
1150
+ pl.col("mp").struct.field("val").alias("mp_val"),
1151
+ pl.col("mp").struct.field("poc").alias("mp_poc"),
1152
+ ])
1153
+
1154
+ # 2. 判斷價格位置
1155
+ df = df.with_column(
1156
+ pl.when(pl.col("close") > pl.col("mp_vah"))
1157
+ .then(pl.lit("above_va")) # 突破 VAH -> 強勢
1158
+ .when(pl.col("close") < pl.col("mp_val"))
1159
+ .then(pl.lit("below_va")) # 跌破 VAL -> 弱勢
1160
+ .otherwise(pl.lit("inside_va")) # 在 VA 內 -> 盤整
1161
+ .alias("va_position")
1162
+ )
1163
+
1164
+ # 3. 突破策略信號
1165
+ df = df.with_column(
1166
+ pl.when(
1167
+ (pl.col("va_position") == "above_va") &
1168
+ (pl.col("va_position").shift(1) == "inside_va")
1169
+ ).then(pl.lit("LONG")) # 突破 VAH -> 做多
1170
+
1171
+ .when(
1172
+ (pl.col("va_position") == "below_va") &
1173
+ (pl.col("va_position").shift(1) == "inside_va")
1174
+ ).then(pl.lit("SHORT")) # 跌破 VAL -> 做空
1175
+
1176
+ .otherwise(pl.lit("NEUTRAL"))
1177
+ .alias("signal")
1178
+ )
1179
+
1180
+ # 4. 篩選突破機會
1181
+ breakouts = df.filter(pl.col("signal").is_in(["LONG", "SHORT"]))
1182
+ print(breakouts.select(["ts", "close", "mp_vah", "mp_val", "signal"]))
1183
+ ```
1184
+
1185
+ ### 範例 3: 使用 segment_id 計算距離 POC
1186
+
1187
+ ```python
1188
+ # 1. 使用 window 函數獲取 segment POC
1189
+ df = df.with_column(
1190
+ pl.col("mp").struct.field("poc")
1191
+ .over([pl.col("mp").struct.field("segment_id")])
1192
+ .first()
1193
+ .alias("segment_poc")
1194
+ )
1195
+
1196
+ # 2. 計算每根 K 線與 POC 的距離
1197
+ df = df.with_columns([
1198
+ (pl.col("close") - pl.col("segment_poc")).alias("distance_from_poc"),
1199
+ ((pl.col("close") - pl.col("segment_poc")) / pl.col("segment_poc") * 100)
1200
+ .alias("distance_pct")
1201
+ ])
1202
+
1203
+ # 3. 篩選遠離 POC 的 K 線(可能反轉)
1204
+ far_from_poc = df.filter(pl.col("distance_pct").abs() > 2.0) # 距離 POC > 2%
1205
+ print(far_from_poc.select(["ts", "close", "segment_poc", "distance_pct"]))
1206
+ ```
1207
+
1208
+ ### 範例 4: 形狀基礎策略(實驗性)
1209
+
1210
+ ```python
1211
+ from tradepose_models.indicators.market_profile import create_profile_shape_config
1212
+
1213
+ # 1. 配置形狀識別
1214
+ shape_config = create_profile_shape_config()
1215
+
1216
+ mp = MarketProfileIndicator(
1217
+ anchor_config=create_daily_anchor(9, 15, 1),
1218
+ tick_size=0.25,
1219
+ shape_config=shape_config # 啟用形狀識別
1220
+ )
1221
+
1222
+ # 2. 應用後展開形狀欄位
1223
+ df = df.with_column(
1224
+ pl.col("mp").struct.field("profile_shape").alias("mp_shape")
1225
+ )
1226
+
1227
+ # 3. 只在 Trend Day 使用趨勢跟隨策略
1228
+ trend_days = df.filter(pl.col("mp_shape") == "trend_day")
1229
+
1230
+ trend_signals = trend_days.with_columns([
1231
+ pl.col("mp").struct.field("vah").alias("mp_vah"),
1232
+ pl.col("mp").struct.field("val").alias("mp_val"),
1233
+ ]).with_column(
1234
+ pl.when(pl.col("close") > pl.col("mp_vah"))
1235
+ .then(pl.lit("LONG")) # 突破 VAH -> 順勢做多
1236
+ .when(pl.col("close") < pl.col("mp_val"))
1237
+ .then(pl.lit("SHORT")) # 跌破 VAL -> 順勢做空
1238
+ .otherwise(pl.lit("NEUTRAL"))
1239
+ .alias("trend_signal")
1240
+ )
1241
+
1242
+ print(trend_signals.select(["ts", "close", "mp_shape", "trend_signal"]))
1243
+
1244
+ # 4. 在 Normal 型使用區間策略
1245
+ normal_days = df.filter(pl.col("mp_shape") == "normal")
1246
+
1247
+ range_signals = normal_days.with_columns([
1248
+ pl.col("mp").struct.field("vah").alias("mp_vah"),
1249
+ pl.col("mp").struct.field("val").alias("mp_val"),
1250
+ ]).with_column(
1251
+ pl.when(pl.col("close") > pl.col("mp_vah"))
1252
+ .then(pl.lit("SHORT")) # 接近 VAH -> 做空
1253
+ .when(pl.col("close") < pl.col("mp_val"))
1254
+ .then(pl.lit("LONG")) # 接近 VAL -> 做多
1255
+ .otherwise(pl.lit("NEUTRAL"))
1256
+ .alias("range_signal")
1257
+ )
1258
+
1259
+ print(range_signals.select(["ts", "close", "mp_shape", "range_signal"]))
1260
+ ```
1261
+
1262
+ ### 範例 5: 欄位選擇優化記憶體
1263
+
1264
+ ```python
1265
+ # 只需要 POC 和 VAH(減少記憶體使用)
1266
+ # 注意: segment_id 會自動包含(用於 ffill),無需手動指定
1267
+ mp = MarketProfileIndicator(
1268
+ anchor_config=create_daily_anchor(9, 15, 1),
1269
+ tick_size=0.25,
1270
+ fields=["poc", "vah"] # segment_id 自動添加
1271
+ )
1272
+
1273
+ # 應用後,Struct 包含 poc, vah, segment_id
1274
+ df = df.with_columns([
1275
+ pl.col("mp").struct.field("poc").alias("mp_poc"),
1276
+ pl.col("mp").struct.field("vah").alias("mp_vah"),
1277
+ # val, value_area, tpo_distribution, profile_shape 不存在
1278
+ ])
1279
+
1280
+ # ⚠️ 記憶體提示:
1281
+ # - 完整 Market Profile (7 個欄位): ~80 bytes/row
1282
+ # - 只有 POC/VAH/VAL (3 個欄位): ~32 bytes/row
1283
+ # - 只有 segment_id (1 個欄位): ~8 bytes/row
1284
+ # - tpo_distribution 佔用最多記憶體(50-100 個價格層級)
1285
+ ```
1286
+
1287
+ ### 範例 6: Initial Balance 突破策略
1288
+
1289
+ ```python
1290
+ from tradepose_models.indicators.market_profile import create_initial_balance_anchor
1291
+
1292
+ # 1. 配置 IB 窗口(09:00 ~ 10:00)
1293
+ ib_anchor = create_initial_balance_anchor(
1294
+ start_hour=9,
1295
+ start_minute=0,
1296
+ end_hour=10,
1297
+ end_minute=0
1298
+ )
1299
+
1300
+ mp = MarketProfileIndicator(
1301
+ anchor_config=ib_anchor,
1302
+ tick_size=0.25,
1303
+ fields=["vah", "val"] # IB 突破只需要 VAH/VAL
1304
+ )
1305
+
1306
+ # 2. 檢測突破
1307
+ df = df.with_columns([
1308
+ pl.col("mp").struct.field("vah").alias("ib_high"),
1309
+ pl.col("mp").struct.field("val").alias("ib_low"),
1310
+ ]).with_column(
1311
+ pl.when(pl.col("close") > pl.col("ib_high"))
1312
+ .then(pl.lit("BREAKOUT_UP")) # 突破 IB 上界
1313
+ .when(pl.col("close") < pl.col("ib_low"))
1314
+ .then(pl.lit("BREAKOUT_DOWN")) # 突破 IB 下界
1315
+ .otherwise(pl.lit("INSIDE_IB"))
1316
+ .alias("ib_status")
1317
+ )
1318
+
1319
+ # 3. 篩選突破信號
1320
+ breakouts = df.filter(pl.col("ib_status").is_in(["BREAKOUT_UP", "BREAKOUT_DOWN"]))
1321
+ ```
1322
+
1323
+ ### 範例 7: 每天都有 5 天窗口的 Market Profile
1324
+
1325
+ **問題**: 如果使用 `create_daily_anchor(hour=9, minute=15, lookback_days=5)`,會每 5 天才計算一次,無法實現「每天都有過去 5 天的數據」。
1326
+
1327
+ **解決方案**: 創建 5 個 weekly anchor 指標,錯開觸發日,然後合併結果。
1328
+
1329
+ ```python
1330
+ from tradepose_models.indicators.market_profile import create_weekly_anchor
1331
+ from tradepose_models.enums import Weekday
1332
+ import polars as pl
1333
+
1334
+ # 步驟 1: 在策略配置中定義 5 個 Market Profile 指標
1335
+ strategy_config = {
1336
+ "indicators": [
1337
+ # 週一觸發(回溯 5 天)
1338
+ {
1339
+ "type": "MarketProfile",
1340
+ "anchor_config": create_weekly_anchor(Weekday.MON, 9, 15, lookback_days=5),
1341
+ "tick_size": 0.01,
1342
+ "fields": ["poc", "vah", "val"],
1343
+ "display_name": "mp_mon"
1344
+ },
1345
+ # 週二觸發
1346
+ {
1347
+ "type": "MarketProfile",
1348
+ "anchor_config": create_weekly_anchor(Weekday.TUE, 9, 15, lookback_days=5),
1349
+ "tick_size": 0.01,
1350
+ "fields": ["poc", "vah", "val"],
1351
+ "display_name": "mp_tue"
1352
+ },
1353
+ # 週三觸發
1354
+ {
1355
+ "type": "MarketProfile",
1356
+ "anchor_config": create_weekly_anchor(Weekday.WED, 9, 15, lookback_days=5),
1357
+ "tick_size": 0.01,
1358
+ "fields": ["poc", "vah", "val"],
1359
+ "display_name": "mp_wed"
1360
+ },
1361
+ # 週四觸發
1362
+ {
1363
+ "type": "MarketProfile",
1364
+ "anchor_config": create_weekly_anchor(Weekday.THU, 9, 15, lookback_days=5),
1365
+ "tick_size": 0.01,
1366
+ "fields": ["poc", "vah", "val"],
1367
+ "display_name": "mp_thu"
1368
+ },
1369
+ # 週五觸發
1370
+ {
1371
+ "type": "MarketProfile",
1372
+ "anchor_config": create_weekly_anchor(Weekday.FRI, 9, 15, lookback_days=5),
1373
+ "tick_size": 0.01,
1374
+ "fields": ["poc", "vah", "val"],
1375
+ "display_name": "mp_fri"
1376
+ },
1377
+ ]
1378
+ }
1379
+
1380
+ # 步驟 2: 合併 5 個指標的結果(使用 coalesce 選擇非空值)
1381
+ df = df.with_columns([
1382
+ # POC
1383
+ pl.coalesce([
1384
+ pl.col("mp_mon").struct.field("poc"),
1385
+ pl.col("mp_tue").struct.field("poc"),
1386
+ pl.col("mp_wed").struct.field("poc"),
1387
+ pl.col("mp_thu").struct.field("poc"),
1388
+ pl.col("mp_fri").struct.field("poc"),
1389
+ ]).alias("mp_5day_poc"),
1390
+
1391
+ # VAH
1392
+ pl.coalesce([
1393
+ pl.col("mp_mon").struct.field("vah"),
1394
+ pl.col("mp_tue").struct.field("vah"),
1395
+ pl.col("mp_wed").struct.field("vah"),
1396
+ pl.col("mp_thu").struct.field("vah"),
1397
+ pl.col("mp_fri").struct.field("vah"),
1398
+ ]).alias("mp_5day_vah"),
1399
+
1400
+ # VAL
1401
+ pl.coalesce([
1402
+ pl.col("mp_mon").struct.field("val"),
1403
+ pl.col("mp_tue").struct.field("val"),
1404
+ pl.col("mp_wed").struct.field("val"),
1405
+ pl.col("mp_thu").struct.field("val"),
1406
+ pl.col("mp_fri").struct.field("val"),
1407
+ ]).alias("mp_5day_val"),
1408
+ ])
1409
+
1410
+ # 步驟 3: 使用合併後的 Market Profile
1411
+ # 現在 mp_5day_poc/vah/val 每天都有值(只要當週有 5 天交易數據)
1412
+ df = df.with_column(
1413
+ pl.when(pl.col("close") > pl.col("mp_5day_vah"))
1414
+ .then(pl.lit("ABOVE_VA"))
1415
+ .when(pl.col("close") < pl.col("mp_5day_val"))
1416
+ .then(pl.lit("BELOW_VA"))
1417
+ .otherwise(pl.lit("INSIDE_VA"))
1418
+ .alias("position_vs_5day_va")
1419
+ )
1420
+
1421
+ print(df.select(["ts", "close", "mp_5day_poc", "mp_5day_vah", "mp_5day_val", "position_vs_5day_va"]))
1422
+ ```
1423
+
1424
+ **結果說明**:
1425
+ ```
1426
+ 週一 09:15: mp_mon 觸發(包含 Wed上週 ~ Mon本週 數據)→ mp_5day_poc 有值
1427
+ 週二 09:15: mp_tue 觸發(包含 Thu上週 ~ Tue本週 數據)→ mp_5day_poc 有值
1428
+ 週三 09:15: mp_wed 觸發(包含 Fri上週 ~ Wed本週 數據)→ mp_5day_poc 有值
1429
+ 週四 09:15: mp_thu 觸發(包含 Mon本週 ~ Thu本週 數據)→ mp_5day_poc 有值
1430
+ 週五 09:15: mp_fri 觸發(包含 Tue本週 ~ Fri本週 數據)→ mp_5day_poc 有值
1431
+ ```
1432
+
1433
+ **為什麼這樣設計?**
1434
+ - ✅ 避免數據重疊: 每個指標的 segment 不重疊,TPO 計算正確
1435
+ - ✅ 每天都有值: 5 個指標輪流觸發,coalesce 確保每天都有一個非空值
1436
+ - ✅ 計算效率: 每天只計算一次(而非每根 K 線都計算)
1437
+
1438
+ ---
1439
+
1440
+ ## 常見錯誤
1441
+
1442
+ ### 錯誤 1: value_area_pct 使用整數
1443
+
1444
+ ```python
1445
+ # ❌ 錯誤
1446
+ mp = MarketProfileIndicator(
1447
+ anchor_config=anchor,
1448
+ tick_size=0.25,
1449
+ value_area_pct=70 # 應該是 0.7,不是 70
1450
+ )
1451
+ # 錯誤: ValidationError - value_area_pct must be between 0 and 1
1452
+
1453
+ # ✅ 正確
1454
+ mp = MarketProfileIndicator(
1455
+ anchor_config=anchor,
1456
+ tick_size=0.25,
1457
+ value_area_pct=0.7 # 小數格式
1458
+ )
1459
+ ```
1460
+
1461
+ ### 錯誤 2: tick_size 為 0 或負數
1462
+
1463
+ ```python
1464
+ # ❌ 錯誤
1465
+ mp = MarketProfileIndicator(
1466
+ anchor_config=anchor,
1467
+ tick_size=0.0 # tick_size 必須 > 0
1468
+ )
1469
+ # 錯誤: ValidationError - tick_size must be greater than 0
1470
+
1471
+ # ✅ 正確
1472
+ mp = MarketProfileIndicator(
1473
+ anchor_config=anchor,
1474
+ tick_size=0.25 # 正數
1475
+ )
1476
+ ```
1477
+
1478
+ ### 錯誤 3: 誤解 lookback_days 的語義
1479
+
1480
+ ```python
1481
+ # ❌ lookback_days=3 ≠「每天都看過去 3 天」
1482
+ # ✅ lookback_days=3 = 每 3 天計算一次(segments 不重疊)
1483
+ # 詳見 [核心概念 > Anchor Segmentation](#anchor-segmentation-時間窗口分段)
1484
+ ```
1485
+
1486
+ ### 錯誤 4: Anchor 配置驗證失敗
1487
+
1488
+ ```python
1489
+ # ❌ 錯誤: IB 窗口但 start >= end
1490
+ anchor = create_initial_balance_anchor(
1491
+ start_hour=10, start_minute=0,
1492
+ end_hour=9, end_minute=0 # 10:00 > 09:00
1493
+ )
1494
+ # 錯誤: ValidationError - start_rule must be before end_rule
1495
+
1496
+ # ✅ 正確
1497
+ anchor = create_initial_balance_anchor(
1498
+ start_hour=9, start_minute=0,
1499
+ end_hour=10, end_minute=0 # 09:00 < 10:00
1500
+ )
1501
+ ```
1502
+
1503
+ ### 錯誤 5: 結果全是 null
1504
+
1505
+ **可能原因**:
1506
+ 1. 數據時間範圍太短(無法找到回溯 anchor)
1507
+ 2. Anchor 時間不存在於數據中
1508
+ 3. `[start_rule, end_rule)` 窗口內沒有數據
1509
+
1510
+ **調試方法**:
1511
+ ```python
1512
+ # 檢查 segmentation 結果
1513
+ from tradepose_core import segment_by_anchor
1514
+
1515
+ df_test = segment_by_anchor(df, anchor_config)
1516
+ segment_ids = df_test["segment_id"]
1517
+
1518
+ print(f"Total rows: {len(df_test)}")
1519
+ print(f"Non-null segment_id: {len(df_test) - segment_ids.null_count()}")
1520
+ print(f"Unique segments: {segment_ids.n_unique()}")
1521
+
1522
+ if segment_ids.null_count() == len(df_test):
1523
+ print("⚠️ Warning: No segments found!")
1524
+ print("Check:")
1525
+ print(" 1. Anchor time exists in data")
1526
+ print(" 2. Data time range is long enough")
1527
+ print(" 3. lookback_days is appropriate")
1528
+ ```
1529
+
1530
+ ### 錯誤 5: 期望 POC 只在 anchor 點出現
1531
+
1532
+ **現象**:
1533
+ ```
1534
+ 時間 segment_id POC
1535
+ 09:00 6 16724.0 ← Anchor 點
1536
+ 09:15 7 16724.0 ← ❓ 為何相同?
1537
+ 09:30 8 null
1538
+ ```
1539
+
1540
+ **解釋**: 這是**正確行為**,不是 bug。
1541
+
1542
+ 當基礎頻率 < Market Profile 計算頻率時,`asof_join` 會向前填充:
1543
+
1544
+ - **原因**: 09:15 時還沒有新的 30 分鐘 Market Profile
1545
+ - **行為**: 使用最近可用的 Market Profile(09:00 的結果)
1546
+ - **目的**: 確保策略使用「已知」的 Market Profile,避免 look-ahead bias
1547
+
1548
+ **如何只保留 anchor 點**:
1549
+ ```python
1550
+ # 方法 1: 過濾 segment_id 變化點
1551
+ df_anchor_only = df.filter(
1552
+ pl.col("mp").struct.field("segment_id") !=
1553
+ pl.col("mp").struct.field("segment_id").shift(1)
1554
+ )
1555
+
1556
+ # 方法 2: 使用相同頻率
1557
+ # 如果基礎頻率 = Market Profile 計算頻率,不會有 forward-fill
1558
+ df_30min = resample_ohlcv(df, "30m")
1559
+ df_mp = compute_market_profile(df_30min, anchor_config)
1560
+ ```
1561
+
1562
+ 詳見 **ADR-050 Challenge 7** 了解完整解釋。
1563
+
1564
+ ### 錯誤 6: 比較不同 tick_size 的形狀
1565
+
1566
+ ```python
1567
+ # ❌ 錯誤: 比較不同 tick_size 的 profile shape
1568
+ df1 = compute_market_profile(..., tick_size=0.25)
1569
+ df2 = compute_market_profile(..., tick_size=1.0)
1570
+
1571
+ # 形狀識別結果會不同!
1572
+
1573
+ # ✅ 正確: 使用相同 tick_size
1574
+ df1 = compute_market_profile(..., tick_size=0.25)
1575
+ df2 = compute_market_profile(..., tick_size=0.25)
1576
+ ```
1577
+
1578
+ **原因**: tick_size 影響 TPO 分布,進而影響形狀識別結果。
1579
+
1580
+ ### 錯誤 7: 未驗證就使用形狀配置
1581
+
1582
+ ```python
1583
+ # ❌ 危險: 直接使用預設參數在生產環境
1584
+ shape_config = create_profile_shape_config()
1585
+ mp = MarketProfileIndicator(
1586
+ anchor_config=anchor,
1587
+ tick_size=0.25,
1588
+ shape_config=shape_config
1589
+ )
1590
+ # 部署到生產... 沒有回測驗證
1591
+
1592
+ # ✅ 正確: 先回測驗證
1593
+ # 1. 收集歷史數據
1594
+ # 2. 應用形狀識別
1595
+ # 3. 記錄準確度
1596
+ # 4. 調整參數
1597
+ # 5. 驗證在測試集
1598
+ # 6. 部署到生產
1599
+ ```
1600
+
1601
+ ---
1602
+
1603
+ ## 進階主題
1604
+
1605
+ ### 跨頻率合併行為
1606
+
1607
+ 當基礎數據頻率 < Market Profile 計算頻率時,會發生 **forward-fill**:
1608
+
1609
+ **範例**: 15 分鐘基礎數據 + 30 分鐘 Market Profile
1610
+
1611
+ ```
1612
+ ┌─────────┬────────────┬──────┬─────────────────────────────┐
1613
+ │ 時間 │ segment_id │ POC │ 說明 │
1614
+ ├─────────┼────────────┼──────┼─────────────────────────────┤
1615
+ │ 09:00 │ 6 │ 16724│ ✅ Anchor 點 │
1616
+ │ 09:15 │ 7 │ 16724│ ← 使用 09:00 的 MP(最新) │
1617
+ │ 09:30 │ 8 │ null │ ✅ 新 anchor(尚無數據) │
1618
+ │ 09:45 │ 8 │ null │ ← 使用 09:30 的 MP (null) │
1619
+ │ 10:00 │ 9 │ 16800│ ✅ 新 anchor │
1620
+ └─────────┴────────────┴──────┴─────────────────────────────┘
1621
+ ```
1622
+
1623
+ **為什麼這是正確的**:
1624
+
1625
+ 1. **業務邏輯**: 策略在 09:15 做決策時,應該使用「最近可用」的 Market Profile
1626
+ 2. **避免 look-ahead bias**: 不能在 09:15 使用 09:30 的 MP(未來數據)
1627
+ 3. **與其他指標一致**: SuperTrend 等指標也有相同行為
1628
+
1629
+ **相關文件**: ADR-050 Challenge 7
1630
+
1631
+ ### 使用 segment_id 的進階技巧
1632
+
1633
+ **技巧 1: 檢測 segment 邊界**
1634
+ ```python
1635
+ # 標記 segment 開始
1636
+ df = df.with_column(
1637
+ (pl.col("mp").struct.field("segment_id") !=
1638
+ pl.col("mp").struct.field("segment_id").shift(1))
1639
+ .alias("is_segment_start")
1640
+ )
1641
+
1642
+ # 只在 segment 開始時執行操作
1643
+ segment_starts = df.filter(pl.col("is_segment_start"))
1644
+ ```
1645
+
1646
+ **技巧 2: 計算 segment 內排名**
1647
+ ```python
1648
+ # 每個 segment 內價格排名
1649
+ df = df.with_column(
1650
+ pl.col("close")
1651
+ .rank()
1652
+ .over([pl.col("mp").struct.field("segment_id")])
1653
+ .alias("close_rank_in_segment")
1654
+ )
1655
+ ```
1656
+
1657
+ **技巧 3: Segment 內標準化**
1658
+ ```python
1659
+ # 計算 Z-score (segment 內標準化)
1660
+ df = df.with_column(
1661
+ (
1662
+ (pl.col("close") - pl.col("close").mean()) /
1663
+ pl.col("close").std()
1664
+ ).over([pl.col("mp").struct.field("segment_id")])
1665
+ .alias("close_zscore")
1666
+ )
1667
+ ```
1668
+
1669
+ ### Performance 特性
1670
+
1671
+ **記憶體使用**:
1672
+
1673
+ | 配置 | 每行記憶體 | 說明 |
1674
+ |------|-----------|------|
1675
+ | 所有欄位 | ~80 bytes | 包含 7 個欄位 |
1676
+ | 只 POC/VAH/VAL | ~32 bytes | 3 個 f64 欄位 |
1677
+ | 只 segment_id | ~8 bytes | 1 個 u64 欄位 |
1678
+
1679
+ **計算時間** (10,000 bars):
1680
+
1681
+ | 操作 | 時間 | 說明 |
1682
+ |------|------|------|
1683
+ | TPO 計算 | ~100 ms | 核心計算 |
1684
+ | 形狀識別 | ~7 ms | 可選功能 |
1685
+ | 總計 | ~107 ms | 啟用形狀識別 |
1686
+ | 總計 | ~100 ms | 不啟用形狀識別 |
1687
+
1688
+ **優化建議**:
1689
+ 1. ✅ 使用 `fields` 參數選擇需要的欄位
1690
+ 2. ✅ 不需要時不啟用 `shape_config`
1691
+ 3. ✅ 使用適當的 `tick_size`(不要太小)
1692
+ 4. ✅ 使用 `segment_id` 進行 window 操作而非 ffill 整個 struct
1693
+
1694
+ ### 與 TradingView/Sierra Chart 對比
1695
+
1696
+ **共同點**:
1697
+ - ✅ TPO 計算方式相同
1698
+ - ✅ POC 定義相同
1699
+ - ✅ Value Area 算法相同(70% TPO)
1700
+
1701
+ **差異**:
1702
+ - ⚠️ 形狀識別: TradingView/Sierra Chart **不提供**自動形狀識別
1703
+ - ⚠️ TPO Letters: 本實現不包含 TPO 字母標記(視覺化功能)
1704
+ - ⚠️ Single Prints: TradingView 有,本實現未檢測
1705
+
1706
+ **驗證建議**:
1707
+ ```python
1708
+ # 使用相同數據在 TradingView 和本系統計算
1709
+ # 對比 POC, VAH, VAL 值
1710
+ # 應該在誤差範圍內(<= 1 tick_size)
1711
+ ```
1712
+
1713
+ ---
1714
+
1715
+ ## 參考資料
1716
+
1717
+ ### 內部文件
1718
+
1719
+ - **ADR-050**: Market Profile Stateful Implementation
1720
+ - 完整的 TPO 計算算法
1721
+ - 實現挑戰和解決方案
1722
+ - 使用指南
1723
+
1724
+ - **ADR-060**: Market Profile Shape Recognition 優化
1725
+ - 形狀識別算法詳細說明
1726
+ - 行業標準對比
1727
+ - 參數調整指南
1728
+
1729
+ - **KB-006**: Market Profile Shape Recognition 行業研究
1730
+ - 行業標準文獻回顧
1731
+ - 形狀類型定義
1732
+ - Single Prints 特徵研究
1733
+
1734
+ ### 外部資源
1735
+
1736
+ - [TradingView TPO Indicator](https://www.tradingview.com/support/solutions/43000713306-time-price-opportunity-tpo-indicator/)
1737
+ - [Sierra Chart TPO Charts](https://www.sierrachart.com/index.php?page=doc/StudiesReference/TimePriceOpportunityCharts.html)
1738
+ - [Market Profile Guide - EMinimind](https://eminimind.com/the-ultimate-guide-to-market-profile/)
1739
+ - [ATAS Platform - Market Profile](https://atas.net/volume-analysis-software/market-profile/)
1740
+
1741
+ ### 學術文獻
1742
+
1743
+ - J. Peter Steidlmayer (1984). "Markets in Profile: Profiting from the Auction Process"
1744
+ - Market Profile 原創理論
1745
+ - Initial Balance 定義
1746
+
1747
+ - Dalton, Jones, Dalton (2007). "Mind Over Markets"
1748
+ - Market Profile 形狀分類
1749
+ - 交易策略應用
1750
+
1751
+ ### 行業標準
1752
+
1753
+ - **Stock Market IB**: 前 2 個 30 分鐘時段 = 前 15% 時段(13 個時段/天)
1754
+ - **Futures Market IB**: 前 2 個 30 分鐘時段 = 前 4% 時段(48 個時段/天)
1755
+ - **Value Area**: 70% TPO 覆蓋率(業界共識)
1756
+ - **Trend Day**: IB < 20% 總範圍(ATAS, EMinimind 標準)
1757
+
1758
+ ---
1759
+
1760
+ ## 版本資訊
1761
+
1762
+ **文件版本**: 1.0
1763
+ **最後更新**: 2025-01-25
1764
+ **對應實現版本**: tradepose-python 包
1765
+ **對應 Rust 版本**: ADR-050 v2.1, ADR-060 v1.0
1766
+
1767
+ **變更歷史**:
1768
+ - v1.0 (2025-01-25): 初始完整版本
1769
+
1770
+ ---
1771
+
1772
+ ## 聯繫與支援
1773
+
1774
+ 如有問題或建議,請參考:
1775
+ - 專案 README
1776
+ - GitHub Issues
1777
+ - 技術文件(ADR-050, ADR-060)
1778
+
1779
+ **重要提醒**:
1780
+ - ⚠️ 形狀識別為實驗性功能,使用時請謹慎
1781
+ - ✅ POC 和 Value Area 為穩定功能,可用於生產
1782
+ - 📚 建議先閱讀 ADR-050 了解實現細節