Day 10 的 OBI 有一個很硬的限制:現貨的掛單簿歷史在免費資料源裡不存在,所以它只有我們自己錄的那 45 分鐘。今天換到另一邊——成交資料。掛單可以撤,成交撤不掉,而且 Day 09 那條 aggTrades 路徑要幾天有幾天。
先看一個具體的問題。2026-07-15 那天的 BTC/USDT 現貨,1 分鐘 K 線的收盤價從 65,200 附近走到 64,750 附近。「那天的平均價」是多少?
最直覺的算法是把 1,440 根 K 線的收盤價加起來除以 1,440。但這個算法有一個很明顯的問題:那 1,440 分鐘裡,有些分鐘成交了 200 顆 BTC,有些分鐘成交了 0.25 顆(那天最冷清的一分鐘)。算術平均把它們當成一樣重要。
用工程的話講:算術平均假設每個樣本的權重相同,而市場資料的樣本權重顯然不同。正確的權重是成交量。 這就是 VWAP(Volume Weighted Average Price,成交量加權平均價):
VWAP = Σ(價格 × 成交量) / Σ成交量
一行公式,但它換掉的問題比看起來多。均線問的是「價格往哪走」,VWAP 問的是**「現在的價格,對已經進場的人來說是賺還是賠」**——因為 VWAP 就是那些人的平均成本。
今天要處理三件事:兩種累積方式(它們是兩個不同的工具,不是同一個工具的兩種設定)、一個會吃掉數值精度的實作陷阱,以及把 VWAP 變成策略真正能用的形狀。
「用成交量加權」聽起來像一個修正項,好像是在原本的平均上打個補丁。它其實換掉的是更底層的東西——用什麼當作「一個樣本」。
K 線是照時間切的。切法決定了每根 K 線代表一段等長的時間,而時間本身並不是市場的計量單位。市場真正在計量的是成交:一筆成交發生時,買賣雙方同意了一個價格,那是市場對「現在值多少」的一次表態;一分鐘裡完全沒有成交,市場什麼也沒說。
算術平均把 1,440 根 K 線當成 1,440 個等權樣本,等於宣稱「每一分鐘的意見一樣重要」。可是其中有一分鐘成交了 200 顆 BTC(幾千筆成交的共識),另一分鐘成交了 0.25 顆(可能只有兩三筆)。這兩個樣本的資訊量差了三個數量級。
成交量加權做的事,換個角度看就是把時間軸換成成交量軸。VWAP 不問「這一分鐘的價格是多少」,它問「每成交一顆 BTC,平均成交在什麼價格」。這條軸上每一單位都是等重的,因為每一單位都是同樣多的真實成交。
這個視角在後面幾天會反覆出現。Day 12 量活躍度,本質上就是在量「時間軸與成交量軸的換算率」——同樣一分鐘,有時候裝進去很多成交,有時候幾乎是空的。Day 14 的 Volume Profile 更直接:它乾脆把時間軸整個丟掉,只問「哪些價格區間累積了最多成交」。
一個要買進 500 顆 BTC 的機構不可能一次下單——那會把價格推上去。實際做法是拆成幾百張小單,在一段時間裡慢慢買。
買完之後怎麼評估執行得好不好?跟收盤價比沒有意義(收盤價只是最後一個瞬間),跟最高最低比也沒有意義——那兩個數字是事後才知道的,而且只有一筆成交碰得到。合理的基準是同一段時間裡市場的平均成本,也就是 VWAP。買在 VWAP 之下代表買得比市場平均便宜。
這件事的因果方向值得留意:不是先有 VWAP 這個指標,然後有人拿它當基準;是先有「怎麼衡量拆單執行的品質」這個需求,而 VWAP 是那個需求的答案。它是一個執行品質的量尺,被拿來當進出場訊號是後來的事。這也解釋了為什麼它的定義只有一種,沒有參數可調——量尺如果人人一把不同的,就沒有量尺的功能了。
於是 VWAP 有一個其他指標沒有的性質:它是很多人真的在拿來當標準的數字。 均線的參數(20 還是 60)是各人自選的,而以 VWAP 為目標的執行演算法會照著它下單。這讓「價格在 VWAP 之上或之下」多了一層自我實現的成分——不是因為它有什麼神秘的預測力,而是因為有一群人的決策確實掛在它上面。
VWAP 是平均成本,所以:
第二種情況是「壓力」這個詞在資料上的樣子。它不保證價格會往下,但它描述了一群人的處境,而那個處境會影響他們的行為。
這裡有一個要小心的地方:「平均成本」是一個統計上的重心,不是任何一個人的成本。真實的持倉分布可能是兩群人——一群買在 65,200,一群買在 64,300——而平均值落在中間那個沒什麼人成交的價位。用一個數字概括整個分布,就會漏掉「分布長什麼形狀」這件事。Day 14 的 Volume Profile 正是為了補這個缺口:它不給一個平均值,它給整條分布。
實測 2026-07-15 那天:價格在日內 VWAP 之上的時間佔 45.14%。也就是說那天多數時間裡,當天進場的人平均是虧的。
均線有固定的視窗——20 根就是 20 根,昨天如此,明天也如此。日內 VWAP 不是,它的視窗從當天開盤起算,一路長大。
這件事的後果比聽起來大。早上第一根 K 線,VWAP 就等於那根的價格;第十根的時候它只累積了十根,新的一根有十分之一的影響力;到了晚上第一千根,新的一根只有千分之一的影響力。也就是說,同一條線在一天的開頭像一條很短的均線,到了晚上像一條很長的均線。
由此可以推出兩件之後會在圖上看到的事,而且不必等到看圖:
滾動視窗沒有這兩個性質,代價是它不對應任何一群人的實際成本——「過去 60 根的平均成本」不是任何人的持倉均價,它只是一個統計量。
這就是為什麼今天堅持說這兩者是兩個工具,而不是同一個工具的兩種設定。它們的視窗行為不同、可解釋性不同、通道寬度的成因也不同。
成交量本身沒有方向——一顆 BTC 換手,同時有人買也有人賣。要讓成交量帶方向,得靠 Day 09 講的 taker 概念:主動吃單的那一方是誰。買賣雙方在數量上永遠相等,但誰比較急這件事不對稱,而那就是方向的來源。
Day 09 已經把 taker_buy_base_volume 存進 candles 了(官方 K 線的 12 欄之一),所以「這一分鐘的主動買佔多少」是查得到的。Day 09 那兩根 K 線就是這麼比出來的:一根 80.6%、一根 4.7%。
要留意這個標記能宣稱的範圍:它說的是「哪一方主動」,不是「哪一方比較聰明」。一個急著停損出場的人跟一個看到好消息急著進場的人,在資料上都是主動方。方向資訊是真的,方向的含義要靠上下文,而那個上下文就是 Day 13 的題目——同樣是主動買,突破時出現與盤整時出現是兩回事。
今天的 VWAP 不區分方向(它是所有成交的加權平均),但這個概念今天要先建立。
動手之前先把工作攤開來。今天要算的是兩個特徵:一個是平均成本本身,一個是「現在離它多遠」的無單位版本。
第 2 步與第 3 步要能各自取用:通道要標準差、偏離度要標準差,而報表裡兩個都要印。第 4 步則完全建在前兩步之上,它自己不重算任何東西——「哪一種累積、哪一個價格」只在第 1、2 步決定一次。
Σ(價格 × 成交量) / Σ成交量 這個式子有一個沒說的地方:Σ 從哪裡加到哪裡。
前一節已經把兩種答案的性質講完了,這裡只補命名與一個約定。專案裡它是一個值(VWAPMode,SESSION 與 ROLLING),並且進到特徵的名字(vwap_session 與 vwap_rolling_60),設定檔上看得見。理由等一下實跑那節會兌現:兩者的觸發頻率差將近一倍,藏在設定裡的差異遲早會變成一次對不起來的回測。
約定的部分是「一天」怎麼切。股市的日內 VWAP 有明確的物理意義:開盤到收盤是一個完整的交易時段,收盤之後所有人都停下來。加密貨幣 24/7 不休市,所謂「當日」是我們自己在 UTC 午夜切一刀。切在 UTC 午夜、紐約午夜還是台北午夜,會得到三條不同的線,而沒有哪一條比較「正確」。
這不代表日內 VWAP 在加密貨幣上沒用——很多人確實看 UTC 日界,所以它有那層自我實現的成分。但它是一個約定,寫程式的人要知道自己選了什麼。
實作上分日用 DatetimeIndex.normalize() 而不是 index.date。後者看起來更直覺,但它把索引轉成一整欄 Python date 物件(object dtype),groupby 因此慢好幾倍,而且時區資訊在那一步就掉了。normalize() 保留型別與時區,只把時間部分歸零。
第二個沒說的地方:價格 是哪個價格。
收盤價是預設的直覺選擇,但它只採樣一個瞬間——一根長影線的 K 線,收盤價完全看不出那一分鐘走過的範圍。成交量加權的計算慣例用典型價 (高 + 低 + 收) / 3,把區間也算進去。
典型價是慣例,不是定理。它的邏輯呼應本篇第一節:那一分鐘的成交散落在整個價格區間裡,不是全部發生在收盤價上,所以用單一瞬間代表整根會系統性地偏掉。至於為什麼是這三個而不是開高低收四個平均,答案就只是慣例,沒有更深的理由。
真正精確的做法是拿逐筆成交去算,Day 09 那條路徑有那份資料,而那正是 Day 14 要做的事——用 tick 精算跟用 K 線近似的差別,那天會有數字。
順帶一句很划算的檢查:官方 K 線的 12 欄裡有 quote_volume(以報價幣計的成交額),而 quote_volume / volume 就是那一根真正的 VWAP,不需要任何近似。單根 K 線內的近似誤差可以直接跟它對,Day 14 會用到。
在專案裡這個選擇也是一個值(PriceSource,CLOSE 與 TYPICAL)而不是一個布林參數,因為之後還會有第三種。
# quantbot/domain/features/volume_weighted_average_price.py
def _weighted_mean(self, values: pd.Series, volume: pd.Series) -> pd.Series:
"""加權平均。兩種累積方式只差 cumsum 與 rolling().sum()。
分母是成交量的累積和,所以完全沒有成交的那幾根會讓分母是 0。那時候回 NaN
而不是 0——「沒有人成交」時平均成本沒有定義,填 0 會在圖上畫出一條掉到
原點的線,而那條線會被下游當成真的價格。
"""
weighted = values * volume
if self.mode is VWAPMode.SESSION:
session = self._session_of(values.index)
numerator = weighted.groupby(session).cumsum()
denominator = volume.groupby(session).cumsum()
else:
numerator = weighted.rolling(self.window).sum()
denominator = volume.rolling(self.window).sum()
return (numerator / denominator).where(denominator > 0)
全部向量化,沒有一行在遍歷 K 線。日內模式用 groupby(...).cumsum():分組之後各自累積,跨日自然重置。兩種模式在程式上只差一個方法呼叫,在概念上卻是前面講的那兩個不同工具——這種「實作幾乎一樣、語意完全不同」的地方,正是最需要在名字上分開的地方。
VWAP 只是第一階的統計量。要回答「現在偏離得多不多」,還需要第二階:加權標準差。
先講一件容易被跳過的事:這裡用的是加權標準差,權重跟 VWAP 是同一組。理由是一致性——中心已經是用成交量加權算出來的,如果離散度改用等權,兩者描述的就不是同一個分布了。用成交量當權重的意思是:離散度衡量的是「每成交一顆 BTC,成交價離平均成本多遠」,跟中心的定義對得上。
課本上的算法是「平方的平均,減掉平均的平方」。它受歡迎的理由很實際:完全向量化得起來,兩個加權平均各算一次就好,不必先算平均再回頭掃第二遍。
問題是它在 BTC 的價位上會出事。BTC 的價格是五位數,平方之後是十位數;而一天之內的變異數可能只有幾十。也就是說那條算法要拿兩個非常大的數相減,去湊出一個很小的結果,而相減的時候前面那些位數會彼此抵銷——float64 的精度就是在這一步被吃掉的。
這種誤差不會噴例外,也不會產生 NaN,它產生一個看起來很正常、但後面幾位是垃圾的數字。更麻煩的是它的嚴重程度跟標的的價位成正比:同一段程式碼在價格 30 的標的上完全沒事,換到 BTC 才開始掉精度,所以它特別容易在開發階段被漏掉。
解法是先把價格平移到 0 附近,讓相減的兩個數量級接近。變異數不受平移影響(每個值都減掉同一個常數,離散程度不變),所以這不是近似,是同一件事的另一種寫法:
# quantbot/domain/features/volume_weighted_average_price.py
def standard_deviation(self, view: MarketView) -> pd.Series:
"""加權標準差,用來畫通道、也用來標準化偏離程度。
算法是「加權平方的平均 − 加權平均的平方」,但**先把價格平移到 0 附近**。
直接對 BTC 的價格用那條算法會出事:五位數的價格平方之後是十位數,而
變異數只有幾十,拿兩個量級差很多的數相減會把 float64 的精度吃掉大半。
平移之後兩邊的量級就接近了。
平移不改變變異數,所以這不是近似,是同一件事的另一種寫法。
"""
candles = view.candles.frame
price = self.price_source.of(candles)
volume = candles["volume"].astype("float64")
centre = float(price.mean())
shifted = price - centre
mean = self._weighted_mean(shifted, volume)
mean_of_squares = self._weighted_mean(shifted**2, volume)
# 浮點誤差可能讓變異數變成 -1e-12 這種值,開根號會得到 NaN
variance = (mean_of_squares - mean**2).clip(lower=0.0)
# 用 Series.pow 而不是 np.sqrt:後者的回傳型別在 pandas-stubs 下退化成 Any,
# 於是 mypy --strict 再也看不出這個函式回傳的是 Series
return variance.pow(0.5).rename(f"{self.name}_standard_deviation")
clip(lower=0.0) 那一行不是防禦性的裝飾。即使平移過,浮點誤差還是可能讓變異數變成 -1e-12 這種值(數學上不可能為負),而 sqrt(-1e-12) 會得到 NaN——一個突然出現的 NaN 會讓下游的偏離度整段消失,而原因非常難查。
平移救回多少,是一件可以測的事,而測法的形狀值得說明。對照組是逐項展開的定義式——先算出平均,再一項一項量每個價格離它多遠,精度最好但要掃兩遍資料。測試斷言的是:平移過的向量化版本跟它的誤差小於 1e-9,而沒平移的版本誤差至少大一百倍。
這個測試不是在斷言「我的版本是對的」,而是在斷言**「我的版本比那個看起來一樣的寫法準確得多」**。沒有這個對照,兩種寫法都會通過任何「數值範圍合理」的檢查——這正是這類數值缺陷的特徵,它沒有一個會失敗的斷言,除非刻意去造一個。
VWAP 本身不能直接進策略,因為它的單位是價格。「價格比 VWAP 高 120 USDT」在 BTC 上是小事,在 ETH 上是大事,同一個門檻 NEVER 能同時適用於兩個交易對。除以標準差之後它變成無單位的,跨交易對、跨時段才可比:
# quantbot/domain/features/vwap_deviation.py
def compute(self, view: MarketView) -> pd.Series:
price = self._vwap.price_source.of(view.candles.frame)
centre = self._vwap.compute(view)
spread = self._vwap.standard_deviation(view)
# 標準差為 0 的時候(整段只有一個成交價)偏離沒有定義,回 NaN 而不是 inf
return ((price - centre) / spread.where(spread > 0)).rename(self.name)
這也是 Day 18 均值回歸策略裡「價格顯著偏離 VWAP」那句話的實際定義——「顯著」是幾個標準差,是一個要被寫下來的數字,不是一種感覺。
型別上它用組合而不是繼承:VWAPDeviation 收一個 VWAP 進來。這讓「哪一種累積、哪一個價格」只需要決定一次,而且兩者都是 Feature,Day 15 的管線可以把它們當成兩個獨立的特徵各自註冊。
今天的測試守的都是同一類問題:寫錯了也會得到一個看起來很正常的數字。 四個邊界值得逐一寫下來。
一、加權真的有加到。 兩根 K 線,價格 100 與 200、成交量 1 與 99:算術平均是 150,VWAP 是 199。忘記乘權重、或把權重乘在錯的地方,算出來還是一個落在 100 與 200 之間的合理數字,沒有任何跡象。這個測試看起來很淺,但它守的正是這篇文章存在的理由。
二、跨日要重置。 前 24 小時價格 100、後 24 小時價格 200,日內模式在第 24 根(第二天第一根)必須正好等於 200,不能是任何中間值。忘記分組的話那裡會落在 150 附近,而 150 看起來完全像一個合理的 VWAP。
三、沒有成交的那幾根回 NaN 不回 0。 分母是成交量的累積和,完全沒成交時它是 0。理由跟 Day 10 的「兩側都空」一樣:0 是一個有意義的值(「平均成本是零元」顯然不對),NaN 才是「這裡沒有定義」。實務上這會發生在冷門交易對與交易所維護期間;BTC/USDT 不會,但寫特徵的時候不能假設只有 BTC。
四、標準差為 0 時偏離度回 NaN 不回 inf。 整段只有一個成交價時標準差是 0,除法會得到 inf,而 inf 比 NaN 危險——它會通過 notna() 檢查,然後在下游的比較運算裡永遠成立。一個「偏離超過 2 個標準差就進場」的條件會在這裡無條件觸發。
這四個加上前面那個精度對照,共同的形狀是:錯誤的版本不會失敗,只會安靜地給出另一個答案。 這類缺陷只有兩種抓法,一種是刻意造一個知道正確答案的極端案例,另一種是等到回測結果不合理再回頭查半天。
把偏離度除成「幾個標準差」之後,很容易順手接受一個隱含的假設:2 個標準差 ≈ 很罕見。這個聯想來自常態分布,而偏離度不是常態分布的。
等一下的實測會給出數字:日內模式超過 1 個標準差的比例是 28.70%,接近常態的 31.7%;滾動 60 根是 53.29%,遠遠超出。同一個「2 個標準差」的門檻,在兩種模式下代表的稀有程度完全不同。
所以標準化真正買到的是可比性(跨交易對、跨時段的單位統一),不是機率解釋。要知道一個門檻多罕見,唯一可靠的辦法是回頭數這份資料裡它觸發了幾次——這也是 Day 18 訂策略門檻時要做的事,而不是查常態分布表。
前面說「日內」與「滾動」是兩個不同的工具。這句話用同一天的資料就能量出來。
uv run python -m quantbot.entrypoints.vwap_command \
--symbol BTC/USDT --market spot --timeframe 1m \
--start 2026-07-15 --end 2026-07-16 --mode session
1,440 根 K 線:2026-07-15 00:00:00+00:00 → 2026-07-15 23:59:00+00:00
vwap_session:最後一根 64,974.38,價格 64,756.83
價格在 VWAP 之上的比例:45.14%
偏離超過 1 個標準差:413 根(28.70%)
偏離超過 2 個標準差:72 根(5.00%)
換成滾動 60 根(也就是過去一小時):
1,440 根 K 線:2026-07-15 00:00:00+00:00 → 2026-07-15 23:59:00+00:00
vwap_rolling_60:最後一根 64,810.65,價格 64,756.83
價格在 VWAP 之上的比例:48.89%
偏離超過 1 個標準差:736 根(53.29%)
偏離超過 2 個標準差:180 根(13.03%)
這兩份報告的百分比分母不是 1,440。偏離度要有標準差才算得出來,而日內模式當天的第一根、滾動模式的前 59 根都還沒有,所以分母分別是 1,439 與 1,381——拿 1,440 去除會對不起來,差的那一截就是暖機期。報表同時印根數與百分比就是為了這件事:只印百分比的話,分母是什麼再也問不出來。
並排看:
| 日內重置 | 滾動 60 根 | |
|---|---|---|
| 最後一根的 VWAP | 64,974.38 | 64,810.65 |
| 價格在 VWAP 之上 | 45.14% | 48.89% |
| 偏離超過 1 個標準差 | 28.70% | 53.29% |
| 偏離超過 2 個標準差 | 5.00% | 13.03% |
同一天、同一根 K 線,兩條線相差 164 USDT(64,974.38 對 64,810.65)。 而「偏離超過一個標準差」的比例從 28.70% 變成 53.29%——差了將近一倍。
差距的來源不是價格,是標準差。日內 VWAP 的標準差累積了整天的價格範圍,通道很寬;滾動 60 根只用最近一小時,通道窄得多,於是價格待在通道外的時間自然多得多。這正是前面「越走越鈍」那一節的直接後果:一個視窗會長大的工具與一個視窗固定的工具,通道寬度的行為本來就不一樣。
實務結論很直接:「價格偏離 VWAP 兩個標準差」在沒有講清楚哪一種累積之前,不是一個定義。 兩個人照同一句話寫程式,會得到兩個觸發頻率差一倍的策略。
拉長到一年半的 1 小時線,滾動 168 根(一週):
13,848 根 K 線:2025-01-01 00:00:00+00:00 → 2026-07-31 23:00:00+00:00
vwap_rolling_168:最後一根 64,164.76,價格 62,920.68
價格在 VWAP 之上的比例:49.05%
偏離超過 1 個標準差:45.87% / 超過 2 個標準差:7.85%
「價格在 VWAP 之上」在三組設定裡都落在 45% 到 49%——這個數字本身沒有交易資訊(它接近 50% 是應該的),但它是一個有用的健康檢查:如果它跑到 70% 或 30%,那八成是計算或對齊出了問題,不是市場的性質。這種「應該接近某個值、否則就是有 bug」的量,在特徵工程裡值得多找幾個出來。
輸出是上下兩格:上格 K 線疊上 VWAP 與 ±1、±2 標準差通道,下格是偏離度。通道跟 K 線疊在同一張而不是分開兩張,因為要看的是「價格現在在通道的哪裡」,那是一個相對位置,拆開就得靠眼睛在兩邊來回對時間軸。
上下兩格的分工正是標準化的價值:上格的通道會隨行情呼吸(波動大的時候變寬),所以「離通道多遠」在圖上不好比較;下格的單位是標準差,±2 那條線一整天都在同一個高度。策略要用的是下格那種形狀。
畫完之後有一件事值得用眼睛確認:把日內 VWAP 那張圖拉到 UTC 午夜的位置,會看到那條線在那一根斷開重來,而通道在每天開頭幾根特別窄。前面推導過為什麼會這樣,圖只是把它兌現。那個窄通道會讓偏離度在每天開頭特別容易破 2——這是日內模式的已知性質,不是 bug,但拿它當進場條件的人要知道有這件事。
quantbot/
├── domain/
│ ├── values/
│ │ ├── price_source.py 今天:CLOSE / TYPICAL
│ │ └── vwap_mode.py 今天:SESSION / ROLLING
│ └── features/
│ ├── volume_weighted_average_price.py 今天:VWAP + 加權標準差
│ └── vwap_deviation.py 今天:策略要用的無單位版本
├── infrastructure/charting/
│ └── plotly_vwap_chart_renderer.py 今天
├── entrypoints/vwap_command.py 今天
└── tests/domain/features/
└── test_volume_weighted_average_price.py 今天
六項全過才算完成:
uv run pytest tests/domain/features/test_volume_weighted_average_price.py 全綠,包含前面列的四個邊界:加權平均不等於算術平均(100/200 配 1/99 要得到 199 而不是 150)、跨日重置(第二天第一根正好等於 200)、零成交量回 NaN、標準差為 0 時偏離回 NaN。滾動模式則要斷言它不認識日界。1e-9,而沒平移的版本誤差至少大一百倍。這一項是今天最容易被當成裝飾而跳過的,但它守的是一個不會報錯的錯誤。uv run python -m quantbot.entrypoints.vwap_command --symbol BTC/USDT --market spot --timeframe 1m --start 2026-07-15 --end 2026-07-16 --mode session 印出 1,440 根、價格在 VWAP 之上約 45%,並產出 notebooks/day11-spot_BTCUSDT_1m-vwap_session.html。--mode rolling --window 60 再跑一次,兩者的「偏離超過 1 個標準差」比例要明顯不同(實測 28.70% 對 53.29%)。這個差異是預期的結果,不是哪一邊算錯。uv run mypy quantbot 與 uv run lint-imports 全過。第 4 項是今天真正的重點。兩個數字都對,而它們差一倍——這說明「偏離 VWAP 兩個標準差」這句話在沒有指定累積方式之前不構成一個定義。
免責聲明:本文為程式與資料工程的技術分享,所有數字皆為教學範例,不構成投資建議;VWAP 是對已發生成交的描述,不預測後續走勢。
今天的兩個特徵都在回答「價格在哪裡」——相對於平均成本的位置。明天問一個不一樣的問題:這段時間市場有多熱。
這個問題其實是今天第一節那個視角的延伸。既然時間軸與成交量軸是兩條不同的軸,那麼「同樣一分鐘裡裝進了多少成交」本身就是一個值得量的量。
Day 09 那兩根 K 線又要再出場一次。它們的開高低收與成交量幾乎一樣,成交筆數差 4.2 倍。所以「熱不熱」至少有三個角度(成交筆數、成交量、價格實際走了多少),而它們的答案會不一致。
明天 Day 12 會把這三個角度都做成標準化的分數,並且處理一個很容易寫成未來函數的地方:加密貨幣 24/7 不休市,但它有很明顯的時段節奏(實測最冷清與最熱鬧的鐘點差 2.71 倍)。要問「現在真的不尋常嗎」,就得跟同一個鐘點的歷史比,而那個比較只要寫成一行 groupby("hour").transform("mean"),就會把未來的資料算進基準裡。
也會補上 ATR——它是 Day 24 決定停損距離的依據,而它在這個專案裡不是 Indicator 而是 Feature,理由是 Day 04 那個基底類別的契約真的裝不下它。
quote_volume 與 taker_buy_base_volume(quote_volume / volume 就是那一根真正的 VWAP) — Binance Spot API Documentation, Market Data Endpoints
DatetimeIndex.normalize() 保留型別與時區、只把時間部分歸零,而 .date 會退化成 object dtype — pandas documentation, DatetimeIndex.normalize
Series.rolling 與 groupby(...).cumsum() 的向量化累積寫法 — pandas documentation, Windowing operations