實戰(zhàn):從passorder到VWAP算法單的量化落地)
QMT里的交易函數(shù)最值得先搞清楚的并不是“哪個函數(shù)能賺錢”而是策略信號到底怎么變成真實委托。很多人寫Python量化策略回測時K線圖很漂亮一接到QMT實盤就卡在下單環(huán)節(jié)方向填反了、價格類型理解錯了、數(shù)量單位不對、委托沒成交就去查持倉最后日志一團(tuán)亂。下面按真實落地順序拆從最常用的passorder下單函數(shù)開始一直講到VWAP算法單。適合正在從回測走向模擬盤和實盤的新手也適合已經(jīng)裝了QMT但還沒把交易接口完整跑通的人。最值得關(guān)注的是交易函數(shù)不是背完參數(shù)就結(jié)束重點是執(zhí)行順序、異常處理和邊界條件。1. 先搞清楚QMT交易函數(shù)到底管哪些事很多人一上來就找“下單函數(shù)”然后照著文檔敲一遍發(fā)現(xiàn)能跑卻又不敢用。原因在于交易函數(shù)不是單獨存在的它是整個交易鏈路里的一環(huán)。策略負(fù)責(zé)給信號交易函數(shù)負(fù)責(zé)把信號變成委托再通過交易所撮合成交最后把成交結(jié)果同步回策略。1.1 為什么交易函數(shù)比策略信號更容易被忽略寫策略的人通常喜歡研究指標(biāo)、因子、模型因為這部分看起來更有“技術(shù)含量”。但真正上過模擬盤和實盤的人會告訴你執(zhí)行層出問題的概率反而更高。一個典型的錯誤是回測里用“收盤價成交”實盤卻用限價單掛在很遠(yuǎn)的價格結(jié)果一直不成交。另一個典型錯誤是策略信號出現(xiàn)了兩次程序又沒做去重導(dǎo)致重復(fù)下單。這些問題都不是策略方向錯了而是交易函數(shù)的調(diào)用時機(jī)和狀態(tài)管理沒做好。所以在接觸QMT時我建議先把“交易函數(shù)能干什么”和“交易函數(shù)在什么條件下能執(zhí)行成功”這兩件事分開看。前者看文檔后者要看賬戶狀態(tài)、交易時段、證券狀態(tài)和參數(shù)類型。1.2 QMT交易函數(shù)的核心分類下單、撤單、查詢、賬戶同步通常提到的QMT交易函數(shù)可以大致分成四類類別典型用途我一般會關(guān)注的點下單買入、賣出、算法單、條件單返回的委托編號、是否立即報單撤單撤銷未成交委托撤單是否成功、委托狀態(tài)是否更新查詢資金、持倉、委托、成交數(shù)據(jù)是實時還是快照字段含義是否理解賬戶同步登錄確認(rèn)、賬戶狀態(tài)、可用資金變動程序重啟后是否需要重新登錄或重新訂閱如果把這四類函數(shù)串起來一個最小交易閉環(huán)是這樣的登錄賬戶確認(rèn)可用資金和持倉。策略產(chǎn)生信號調(diào)用下單函數(shù)。查詢委托狀態(tài)判斷是“已報”“部成”還是“已成”。如果超時未成交執(zhí)行撤單。查詢成交和最新持倉更新策略內(nèi)部狀態(tài)。很多人只寫第2步忽略第1、3、4步結(jié)果就是策略看起來能發(fā)單但不知道到底成交沒有。尤其做網(wǎng)格、做T、做高頻一點的交易時委托狀態(tài)和持倉狀態(tài)必須實時同步否則很容易重復(fù)下單或漏單。這個階段不需要把所有函數(shù)都背下來。先理解每一類函數(shù)解決什么問題什么情況下會被調(diào)用再去看具體參數(shù)就會快很多。不要把交易函數(shù)當(dāng)API字典去啃而是當(dāng)成一套“你給指令、券商柜臺執(zhí)行、狀態(tài)回傳”的流程來看。2. passorder是下單的核心入口參數(shù)和調(diào)用順序必須先盤清楚QMT里最常聽到的下單函數(shù)就是passorder。很多新手會在策略代碼里直接調(diào)用但因為參數(shù)比較多很容易出現(xiàn)方向填錯、價格類型選錯、數(shù)量單位搞混這類問題。先把這個函數(shù)拆明白后面的撤單、批量、算法單才能順手。2.1 passorder的基本調(diào)用邏輯passorder這類函數(shù)本質(zhì)上是把一筆委托指令發(fā)送給券商柜臺。它的核心輸入包括賬戶、證券代碼、買賣方向、委托價格、委托數(shù)量、價格類型等。雖然不同版本的QMT接口在參數(shù)順序和具體名稱上可能有差異但做的事情是一樣的。我在這里給一個示意形式的調(diào)用結(jié)構(gòu)方便理解參數(shù)位置。實際落地時一定要以你當(dāng)前使用的QMT接口文檔為準(zhǔn)不要照搬網(wǎng)上的代碼。passorder( account, # 資金賬號 symbol, # 證券代碼例如 600000.SH direction, # 買賣方向例如 buy / sell price_type, # 價格類型例如 limit / market price, # 委托價格市價單通常不填或填0 volume, # 委托數(shù)量注意是股、手還是張 remark # 委托備注用于日志追蹤 )真實接口里可能出現(xiàn)“操作類型”“委托類型”“交易市場”等多個參數(shù)。不要怕麻煩第一次寫的時候逐個參數(shù)打印出來確認(rèn)一遍。我最常用的調(diào)試方法是先用一個模擬盤賬號把參數(shù)里的買賣方向、價格、數(shù)量全部做成變量打印到日志。這樣即使這一單失敗也能立刻看出是哪個參數(shù)不對。2.2 價格類型和數(shù)量單位是新手最容易翻車的地方下單能不能成交價格類型起決定性作用。限價單會指定一個具體價格比如10.50元買入只有價格低于或等于10.50的賣單才能與你撮合。如果價格掛得太低委托會一直掛在隊列里直到收盤被作廢。市價單則不用指定價格系統(tǒng)按當(dāng)前行情撮合成交速度快但可能出現(xiàn)滑點尤其是在流動性不好的票上。所以我的建議是第一版策略盡量使用限價單并且價格不要拍腦袋填??梢愿鶕?jù)盤口買一賣一價、最新價或者按照一定偏移量計算。等邏輯穩(wěn)定了再去嘗試市價單或其他價格類型。數(shù)量單位也很容易出問題。股票通常按“股”或“手”為單位1手等于100股可轉(zhuǎn)債、基金、指數(shù)可能又是另外一套規(guī)則。如果你把股票數(shù)量寫成10可能程序認(rèn)為是10股也可能認(rèn)為是10手不同環(huán)境的處理方式不一樣。最穩(wěn)妥的辦法是先用最小交易數(shù)量跑一筆模擬單然后去持倉里核對數(shù)量是否和預(yù)期一致。2.3 一個最簡下單示例這里給一個非?;A(chǔ)的邏輯用來演示下單前、下單后分別要做什么# 示意代碼下單前先獲取賬戶和持倉下單后查詢委托狀態(tài) account_id get_account() symbol 600000.SH direction buy price_type limit price 10.52 volume 100 # 下單前打印關(guān)鍵參數(shù) print(委托參數(shù):, account_id, symbol, direction, price, volume) # 調(diào)用下單函數(shù) order_id passorder( account_id, symbol, direction, price_type, price, volume, remarkfirst_demo ) # 檢查返回結(jié)果 if order_id is None: print(下單失敗請檢查參數(shù)和賬戶權(quán)限) else: print(委托提交成功order_id , order_id)這種寫法雖然簡單但已經(jīng)把“下單前確認(rèn)輸入下單后檢查返回”的框架拉起來了。之后要加條件判斷、撤單、批量都能在這個框架上擴(kuò)展。注意不要在沒確認(rèn)賬戶、證券代碼和交易時段的情況下批量發(fā)單。先跑通一筆模擬單再談批量。3. 從單筆下單到自動交易條件單、批量單和撤單重發(fā)能手動發(fā)一筆單子之后下一個問題就是怎么讓策略自動發(fā)單。自動交易不是簡單地把函數(shù)放進(jìn)循環(huán)里而是要設(shè)計觸發(fā)條件、執(zhí)行頻率、異常處理和狀態(tài)同步。3.1 條件單和定時任務(wù)條件單的核心是“滿足條件后再下單”。這個條件可以來自技術(shù)指標(biāo)比如均線交叉、價格突破也可以來自外部信號比如另一個程序?qū)懭霐?shù)據(jù)庫的數(shù)據(jù)。為了避免在每個K線收盤瞬間重復(fù)觸發(fā)我會在策略里加一個“上次下單時間”或“信號是否已處理”的標(biāo)記# 示意代碼避免重復(fù)觸發(fā)同一個信號 last_signal_time None def check_and_trade(current_time, signal): global last_signal_time if signal and last_signal_time ! current_time: # 下單邏輯 send_order(...) last_signal_time current_time定時任務(wù)則更直接比如每個分鐘K線結(jié)束后檢查一次持倉和信號。注意調(diào)度邏輯要獨立于行情推送否則行情卡頓會導(dǎo)致整個交易流程阻塞。QMT的環(huán)境一般允許你在策略里掛定時器或監(jiān)聽行情回調(diào)。不要一上來就做毫秒級調(diào)度先把分鐘級的流程跑順確認(rèn)委托、成交、日志都沒問題再考慮提高頻率。3.2 批量任務(wù)和失敗重試批量任務(wù)很有誘惑力因為一個組合可能有幾十只股票要調(diào)倉。但批量下單有一個隱患如果中間某只股票停牌、漲停跌停、數(shù)量不符合要求整個循環(huán)可能中斷或者重復(fù)下單。我的建議是批量任務(wù)先做兩件事每筆委托獨立生成一條記錄包含證券代碼、方向、價格、數(shù)量、時間、返回碼。每筆委托之間保留一定間隔尤其當(dāng)你調(diào)用的接口對頻率有限制時。如果某一筆失敗不要立刻在循環(huán)里無限重試。先記錄失敗原因等整個批次跑完再統(tǒng)一處理失敗項。這樣既不會因為個別票的問題拖垮全部任務(wù)也能留下清晰的日志。如果要把任務(wù)隊列持久化很多項目會把委托記錄寫到SQLite、MySQL或者Redis里。QMT策略本身消耗的資源不算高但一旦加上行情訂閱、數(shù)據(jù)庫寫入、外部請求程序復(fù)雜度會上升。建議先用本地文件或SQLite把字段確認(rèn)清楚再引入Redis這類外部組件不然排起錯來會同時面對交易問題和中間件問題很難定位。3.3 撤單、查詢成交和持倉同步自動交易里撤單不是可選項而是風(fēng)控的一部分。限價單掛在那邊可能一直不成交尤其在價格快速偏離時。一個常見做法是委托發(fā)出后如果超過一定時間還沒全部成交就撤單并按最新價格重新委托。這里要區(qū)分“全部成交”“部分成交”“未成交”三種狀態(tài)委托狀態(tài)你要做的事全部成交更新持倉和資金記錄成交流水部分成交判斷剩余量是否繼續(xù)等待還是撤單重發(fā)未成交檢查價格、盤口、時間決定是否等待或撤單查詢持倉時也要區(qū)分“總持倉”和“可用持倉”。有些品種當(dāng)天買入的持倉不能賣出這個問題在實盤里很常見。所以下單前先確認(rèn)可用數(shù)量下單后不要立刻認(rèn)為持倉已經(jīng)變化要等成交回報。撤單后也不要馬上重新發(fā)單最好先等一個完整的委托狀態(tài)更新再計算新的價格和數(shù)量。否則容易出現(xiàn)“撤了又補(bǔ)、補(bǔ)了又撤”的抖動不僅增加手續(xù)費還可能把成本越做越高。4. VWAP算法不是黑盒核心是切片、權(quán)重和動態(tài)調(diào)整VWAP全稱Volume Weighted Average Price也就是成交量加權(quán)平均價格。它本身是一個指標(biāo)但在算法交易里常常被當(dāng)成執(zhí)行目標(biāo)讓大單在指定時間段內(nèi)盡量按市場成交量分布來下單最終成交均價接近這個時間段的VWAP。4.1 VWAP到底在解決什么問題如果手里有一筆大單一次全砸進(jìn)市場很容易把價格打偏造成沖擊成本。VWAP的思路是把大單拆成很多小單分散到不同時間點上讓委托流動方向和市場成交量節(jié)奏盡量匹配。它不預(yù)測漲跌也不保證一定買到最低價或賣到最高價。它的目標(biāo)是降低“因為自己這筆單子太大而造成的額外成本”。所以判斷VWAP執(zhí)行好不好不是看最后賺了多少錢而是看成交均價和參考VWAP的偏離程度。4.2 在QMT里落地VWAP的常見思路有些QMT交易環(huán)境提供了算法單接口可以直接委托一個VWAP算法單然后由本地算法模塊或柜臺動態(tài)執(zhí)行。這種方式省事但參數(shù)和限制通常要提前確認(rèn)不同環(huán)境和不同券商支持程度也不同。更通用的做法是自己在策略里實現(xiàn)一個簡易VWAP執(zhí)行器。步驟如下確定交易時間窗比如10:00到14:00。根據(jù)過去若干天同一時間段的成交量分布計算每個時間切片的交易權(quán)重。把目標(biāo)倉位乘以權(quán)重得到每個切片要委托的數(shù)量。到時間點后調(diào)用下單函數(shù)發(fā)出委托。查詢成交情況如果某個切片沒有成交完把剩余量滾動到后面的切片。每次下單前檢查價格、漲跌停、可用資金和持倉避免無效委托。這個邏輯看起來不復(fù)雜難在切片權(quán)重怎么給、動態(tài)調(diào)整怎么做、價格保護(hù)怎么設(shè)。4.3 VWAP算法單的代碼邏輯和參數(shù)判斷下面是一個用于理解流程的偽代碼# 示意代碼簡易VWAP執(zhí)行邏輯 slices generate_slices(start_time, end_time, target_volume, past_days20) for sl in slices: if not is_trading_time(sl.time): continue volume round(target_volume * sl.weight) # 動態(tài)調(diào)整如果前面切片有未成交量補(bǔ)到當(dāng)前切片 volume pending_volume if volume 0: order_id send_order( symbolsymbol, directiondirection, price_typelimit, priceget_protected_price(), volumevolume ) wait(sl.interval) # 查詢當(dāng)前切片成交量更新未成交累計 filled query_filled(order_id) pending_volume max(0, volume - filled)核心參數(shù)可以單獨做一張表參數(shù)作用經(jīng)驗值開始時間啟動VWAP執(zhí)行的時間一般避開開盤前15分鐘結(jié)束時間停止發(fā)送新委托的時間建議留出撤單時間切片數(shù)量時間窗分成多少段先少后多20到30段足夠歷史回看天數(shù)計算成交量分布用多少天20個交易日左右最大單筆比例單次委托不超過目標(biāo)量的百分比10%到20%價格保護(hù)漲停不追買、跌停不追賣必開滑點控制當(dāng)前價格偏離過大時暫停根據(jù)品種流動性調(diào)整我最開始在模擬盤跑VWAP時用的是固定間隔等權(quán)重切片比如每15分鐘下一單。跑完發(fā)現(xiàn)比一次全下要穩(wěn)定但距離真正的“成交量加權(quán)”還是有差距。后來加入歷史成交量分布效果才明顯改善。如果你第一次做不要追求完美先把固定間隔版本跑通再一點點加上權(quán)重。注意VWAP算法的核心不是代碼有多復(fù)雜而是“切得足夠小、價格保護(hù)到位、失敗能動態(tài)調(diào)整”。沒有止損和風(fēng)控的VWAP只是把大單分成了很多小單風(fēng)險并沒有消失。5. 從“能下單”到“策略可跑”完整流程和資源邊界交易函數(shù)說到底只是執(zhí)行層。要讓一套策略真正能跑還需要把數(shù)據(jù)、回測、模擬盤、日志和運維串起來。5.1 策略代碼、回測、模擬盤和實盤的關(guān)系很多新手容易混淆四件事回測能賺錢、模擬盤能成交、實盤能成交、實盤能穩(wěn)定賺錢。這是完全不同的階段?;販y的核心是驗證邏輯在過去數(shù)據(jù)上是否成立但回測很容易受到未來函數(shù)、幸存者偏差和手續(xù)費設(shè)置影響。模擬盤雖然更接近實盤但撮合規(guī)則和真實盤口仍有差異。實盤則要考慮滑點、延遲、資金容量、漲跌停、停牌、網(wǎng)絡(luò)波動等亂七八糟的問題。所以我的建議是先寫Python量化交易策略代碼做一段歷史回測確認(rèn)沒有明顯邏輯錯誤后再放到QMT模擬盤上跑一周最后用極小資金做實盤驗證。不要跳過中間任何一步?;販y里很常見的錯誤是信號用了未來數(shù)據(jù)比如用當(dāng)天收盤數(shù)據(jù)算信號再假設(shè)當(dāng)天開盤價成交。這在比賽中可能不算什么但在真實交易里會直接造成策略失真。5.2 行情、交易接口、數(shù)據(jù)庫和運行環(huán)境準(zhǔn)備在QMT里跑策略通常需要準(zhǔn)備以下幾塊QMT客戶端或交易終端能夠登錄模擬盤賬號。Python環(huán)境能夠在策略里調(diào)用交易函數(shù)。行情數(shù)據(jù)源用于實時或分鐘級數(shù)據(jù)計算。數(shù)據(jù)存儲至少要有日志目錄和委托記錄表。運行計劃比如每天開盤前啟動策略、收盤后生成報告。如果是低配置機(jī)器運行簡單的分鐘級策略通常夠用。但如果你同時訂閱大量股票、接收高頻行情、寫數(shù)據(jù)庫、跑復(fù)雜模型CPU、內(nèi)存、磁盤都會成為瓶頸。不要一上來就開很大并發(fā)先監(jiān)控一段時間看看占用率和運行速度。有一個容易被忽略的點是輸出目錄。策略運行時間長了日志文件會越來越大。我一般會按日期建目錄每天一個日志文件定期清理。這樣即使某天程序異常也能快速定位是哪一天的委托、哪個時間段出的問題。5.3 單任務(wù)驗證和連續(xù)運行驗證第一次跑通策略我建議分三個步驟先手動觸發(fā)一次下單確認(rèn)賬戶、證券代碼、價格、數(shù)量都正確。再讓策略自動發(fā)一次單確認(rèn)條件判斷和日志記錄正常。最后連續(xù)運行幾天觀察是否有重復(fù)觸發(fā)、漏單、界面卡死或數(shù)據(jù)斷流。這三個步驟里最容易被忽略的是連續(xù)運行驗證。很多策略只在開盤后跑幾分鐘問題不明顯。一旦全天運行跨午休、跨交易日、網(wǎng)絡(luò)波動、行情中斷都會暴露出來。判斷策略是不是“能跑”不要只看有沒有下單。要盯幾個指標(biāo)每天委托次數(shù)、委托成功率、撤單次數(shù)、成交均價偏離、重復(fù)下單次數(shù)、程序啟動到收盤是否穩(wěn)定。如果這些指標(biāo)正常再談優(yōu)化。6. 高頻出現(xiàn)的報錯和排查順序先看輸入再看環(huán)境QMT交易函數(shù)報錯是最浪費時間的環(huán)節(jié)。因為問題經(jīng)常不在函數(shù)本身而在參數(shù)、權(quán)限、交易狀態(tài)和環(huán)境配置。盲目改參數(shù)只會讓情況更亂。6.1 最容易誤判的幾類問題我在模擬盤和實盤里見過很多次類似問題報“賬號未登錄”但界面明明顯示已連接。這種情況往往是策略進(jìn)程和客戶端會話沒有同步或者需要重新初始化交易接口。報“委托數(shù)量不合法”檢查數(shù)量發(fā)現(xiàn)沒寫錯其實是單位或最小委托數(shù)量理解錯了。報“非交易時間”可能是節(jié)假日、午休、集合競價階段或者系統(tǒng)時間不同步。報“證券代碼錯誤”可能是把600000.SH寫成了600000缺少市場前綴。報“資金不足”不一定是真的沒錢可能是可用資金計算方式不同比如賣出股票回款還沒到賬。這些問題的共同點是表面看像交易函數(shù)的問題實際上更多是前置條件和輸入格式的問題。6.2 通用排查順序遇到報錯或委托異常我一般按下面這個順序排查不會一上來就改參數(shù)看現(xiàn)象是下單失敗、委托無回報、成交異常還是程序直接崩潰看輸入?yún)?shù)證券代碼、方向、價格、數(shù)量、賬戶逐項打印出來核對。看交易狀態(tài)當(dāng)前是否交易時間證券是否停牌是否漲停跌停賬戶是否有權(quán)限??促Y源占用CPU、內(nèi)存、磁盤是否異常網(wǎng)絡(luò)是否斷流日志是否在持續(xù)輸出??匆蕾嚢姹竞徒涌诎姹静呗源a是否換過環(huán)境QMT版本是否變化接口函數(shù)是否需要重新確認(rèn)。排查的時候日志是最重要的線索。如果日志里根本沒有記錄函數(shù)返回那可能連下單函數(shù)都沒被調(diào)用先檢查條件判斷。如果日志記錄了返回但狀態(tài)一直是“未成交”再去看價格和盤口。如果程序直接崩潰再去看內(nèi)存、磁盤和依賴。有一個經(jīng)驗遇到“偶爾能下單偶爾不能下單”的問題優(yōu)先懷疑條件判斷和狀態(tài)同步而不是懷疑接口不穩(wěn)定。比如持倉查詢還沒完成就發(fā)賣單或者信號重復(fù)觸發(fā)都會讓同一條邏輯在不同時間表現(xiàn)不同。7. 把QMT交易函數(shù)用順手的幾條經(jīng)驗最后留幾條我自己在QMT策略里反復(fù)用到的原則。這些不是功能列表而是踩過坑之后形成的判斷標(biāo)準(zhǔn)。7.1 先跑通最小閉環(huán)再談批量和算法任何新策略都先用一個小賬號、一只票、一筆單子跑通。不要第一次就上幾十只股票的組合也不要第一次就開VWAP算法單。最小閉環(huán)的意思是從策略信號到下單函數(shù)再到查詢回報中間所有參數(shù)都是自己確認(rèn)過的。只有這個閉環(huán)穩(wěn)定后面加批量、加數(shù)據(jù)庫、加算法才能有基礎(chǔ)。很多人直接照抄別人的批量下單代碼跑到一半才發(fā)現(xiàn)某些參數(shù)和自己的環(huán)境不匹配。到時候既要排查交易問題又要排查代碼問題很難定位。7.2 我最后會盯的幾個指標(biāo)實盤階段我會每天記錄幾個指標(biāo)委托成功率發(fā)出委托后有多少單被柜臺正常接收。成交率接收后有多少單最終成交沒成交的是因價格還是狀態(tài)。撤單次數(shù)撤單多說明價格設(shè)置或執(zhí)行時機(jī)有問題。成交均價偏離如果用了VWAP或算法執(zhí)行這是最重要的指標(biāo)。重復(fù)下單次數(shù)同一信號是否被重復(fù)處理。連續(xù)運行時長策略是否能在開盤到收盤期間穩(wěn)定跑完。這些指標(biāo)不需要很復(fù)雜一個日志文件加一個簡單統(tǒng)計腳本就能完成。關(guān)鍵是每天看而不是等到發(fā)現(xiàn)問題再看。QMT交易函數(shù)本身只是工具真正決定量化結(jié)果的是你如何設(shè)計執(zhí)行流程、如何處理異常、如何控制風(fēng)險。把passorder這類基礎(chǔ)下單函數(shù)用穩(wěn)把VWAP這類算法拆成可驗證的切片邏輯再配合完整的日志和風(fēng)控策略才算真正從“回測好看”走到了“可以落地”。