關於 Excel DDE Tick 資料變更事件的處理

透過看盤軟體來接收即時性的報價資料,最便利的工具莫過於 Excel 了。 諸多交易人幾乎是利用 Excel 透過 DDE 連結方式來取得報價資料並自行撰寫程式 (如 VBA) 來分析處理。而事實上, DDE 是一種老舊的傳輸規格 (Microsoft 已制定了 RTD 規格, Real Time Data, 用來取代並不穩定的 DDE 傳輸,但國內看盤軟體似乎並未支援),不僅 .NET Framework 沒有直接支援,要處理即時性的 DDE 接收,有些實做的地方還挺麻煩的。

舉個例,在 Excel 的工作表內,設定某一個 CELL 的公式 (formula)欄為:

CATDDE|'STOCK2330  '!CurPrice

代表的就是會接受來自看盤軟體 (本身即為 DDE Server)所傳送過來的資料,並且每次會即時更新跳動 (Tick)的資料。

稍微懂一些程式撰寫的交易人都知道,當某個 CELL 的資料發生變動時,只要寫好所對應的事件處理 (Event Handle)方法,就可以處理變動欄位的資料了。 Excel 預設所提供關於 CELL 資料變動處理的事件程序為 "WorkSheet_Change()",如下:

Private Sub Worksheet_Change(ByVal Target As  Range) 
         'do something
End Sub 

理想上很簡單,不是嗎? 問題是,如果 CELL 的欄位是屬於公式 (formula)型態的 (DDE 連結即為其中之一),那麼就無法利用上述的事件程序來處理。 因公式計算後所變動的資料,並不算在資料變動的事件內;屬於公式計算後所變動的資料,是需要利用 "Work_Calculate()" 程序,如下:

Private Sub Worksheet_Calculate() 
         'do something
End Sub 

老實說,我搞不懂 MS 幹嘛這樣分,我也不想搞懂,只要能達成我所想要的功能即可。 但真正的問題來了… 比較上述的程序,哪裡不一樣? _Change Event 有參數,反之 _Calculate Event 則沒有!

這可是相當相當的麻煩了! 因為透過第一個程序內的參數 (Range Object),可以很方便地得知是哪一個 CELL 的資料變動,但第二個程式則否。 所以若要知道在某一個工作表是哪一個 CELL 的資料變動,就必須寫 FOR LOOP 迴圈,一個一個來判斷是哪一個 CELL 欄位的資料變動。 我幾乎看到許多寫判斷 Tick 資料變動的程式,都是以該事件程序 (Worksheet_Calculate) 來處理。 當然,欄位不多還 OK,但是若是像我這樣,一個工作表是撈約 100 檔的權值成份股、約有 500 ~ 800 個 DDE 欄位的話,那肯定效能會大出問題,連動當然會影響到 Tick 資料的正確性了。 整整爬文了一整個早上,總算才找到真正正確處理 DDE CELL 型態的事件程序了。 不應該使用 "WorkSheet_Calculate()"、而是採用 "SetLinkOnData()" 程序才是。

參考 MSDN 對該程序 (SetLinkOnData Method) 的說明:
"Sets the name of a procedure that runs whenever a DDE link is updated."

如何用? 比想像還要麻煩。 你必須先登記所想要處理含 DDE Link CELL 的字串,然後再委託交給事先寫好的 VBA 巨集 (Marco) 來處理。如下:

ActiveWorkbook.SetLinkOnData _
    "CATDDE|'STOCK2330  '!Volume", _
    "my_Link_Update_Macro"

第一個參數是 DDE Link 的字串":第二個參數就是巨集的名稱。

記得,是要把值得關注的 DDE CELL 一個一個利用該程序來登記,若是要全部的 CELL 都要登記,那麼參考如下的寫法 (refer. DDE Link Change Event。):

' Obtain an array for the links to Excel workbooks
' in the active workbook.
Links = ActiveWorkbook.LinkSources(xlOLELinks)
' If the Links array is not empty, then open each
' linked workbook. If the array is empty, then
' display an error message.
If Not IsEmpty(Links) Then
  For I = 1 To Ubound(Links)
  ActiveWorkbook.SetLinkOnData Links(i), "LinkChange"
Next I
Else
  MsgBox "This workbook does not contain any links " & _
             "to other workbooks"
End If
End Sub

Sub LinkChange()
  MsgBox "linked"
End Sub

只不過這樣的話,就與使用 "Worksheet_Change()" 幾乎沒什麼兩樣了。

若都只是使用 VBA 來撰寫程式的話那也還好,還不至於太麻煩,而若是採用 Excel + C#.NET 呢? 像我現在就是利用 C#.NET 程式來動態載入 (import)某一個 CSV 檔案,內含了所需要處理的權值成份股、代碼、權重等 (每一次重新會載入,因為這些資料,例如權重常會變動),並 "餵" 到 Excel 的工作表中來展現並處理。 使用 C#.NET 對我而言比用 VBA 簡單而且方便多了,而且也相當容易除錯 (DEBUG),並且處理效能會好上很多。 但是上述那個 "SetLinkOnData()" 程序中的第二個參數指定,截至目前為止,絕對是必須使用巨集 (Macro)的方式來處理方可! 無法在此宣告使用 C# 某一個 Class 的 Method,真的相當麻煩! (是有一種方法,透過該 Macro,再回頭呼叫 C#,但似乎相當難搞)

唉,所以我現在的作法就是,利用 C# 動態載入 CSV 檔案至 Excel 的工作表,並可接受即時的 DDE 報價來源; 而後當某一檔股票的成交量變動時,(這一段就是利用 SetLinkOnData() 來處理,然後再比對是哪一列 (Row)所在的股票成交量發生變動) 再利用自行所撰寫的巨集程式,來寫入到如 Access or SqlLite 的資料庫內;再回頭利用 C# 每隔一分鐘去資料庫撈出資料來統計彙整,並展在 Excel 的工作表。

算是有些畸形的作法,很無奈,這也算是權宜之計,一切都不得已受限於目前實做技術的障礙而妥協的一種作法。 反正,能建立起所屬於自己的交易模型才是最重要的!