資訊系統/架構/產品‎ > ‎KGQ‎ > ‎GMDS相關文章‎ > ‎GMDS‎ > ‎

使用HFOCX資訊示範AmiBroker上用ADK設計Realtime Quote

張貼者:2019年1月14日 下午10:00Wei-Xiuang Wang   [ 已更新 2019年2月10日 下午8:43 ]

AmiBroker Development Kit (ADK) 的設計,
大致上就是寫一個dll專案, 主cpp裏include ADK提供的 Plugin.h
然後需要提供以下functions:

PLUGINAPI int GetPluginInfo( struct PluginInfo *pInfo );
PLUGINAPI int Init(void);
PLUGINAPI int Release(void);

要設計Realtime Quote則需再提供
PLUGINAPI int GetStatus( struct PluginStatus *status );

如果還要支援線圖資料則再增加
PLUGINAPI int GetQuotesEx( LPCTSTR pszTicker, int nPeriodicity, int nLastValid, int nSize, struct Quotation *pQuotes, GQEContext *pContext  );

整個ADK其實在GMDS上要運用只需保留一個檔案, 就是 ADK\Include\Plugin.h 就可以了,
但是 Plugin.h 裡面寫了#include "plugin_legacy.h", 所以可以把 plugin_legacy.h 也留著,
不過這只有在跟舊版的AmiBroker( < v5.27)搭配時才有作用,
如真的不需要就把 Plugin.h 裏的include註解掉, 檔案也就可以刪了~ 真的沒什麼用

而要接收DTS資訊源(這裏會用HFOCX的外匯行情作示範)
只需要有 DbfTCdll2.dll 就可以作了,
如果是寫C++則可以引入 DbfTCdll2.h 可以寫的比C#更容易!

PS: 與GMDS的api是全面性支援的概念有所不同, ADK 由於是用 _cdecl (VC預設) 方式宣告函式,
官方說明是可以使用VC6和DevC開發, 而BCB雖然也能正確編譯, 但作出的dll AmiBroker是不能用的!


這裡的示範, 為了調用方便, 設計兩種索引方式, 大致如下規劃
void *vpQuoteIndexHandle = NULL;//行情庫管理
int iCountContracts = 0;//商品數

typedef struct
{//商品資料結構
    struct RecentInfo sRecentInfo;
} RecentInfoDataItem;

typedef struct
{//商品索引用, 由行情源對應商品的記憶區塊
    RecentInfoDataItem *spRecentItem;
} CommodityRefer;

typedef struct
{//商品索引用, 由商品短碼反查
    RecentInfoDataItem *spRecentItem;
    CommodityRefer *spCommodity;
} RecentItemRefer;

CommodityRefer *spCurrCommodity = NULL;

//資料源欄位代碼
#define Tag_UniSymbolName        "1"        //結合"93" "94" "95",多重資訊來源也是Unique的長代碼
#define Tag_ExchangeName        "93"
#define Tag_ContractName        "94"    //通常包含"95"的內容,於單一資訊源是Unique的短代碼
#define Tag_ContractDate        "95"
#define Tag_OpenInterest        "13"
#define Tag_Reference            "14"
#define Tag_Open                "6"
#define Tag_High                "8"
#define Tag_Low                    "10"
#define Tag_Close                "12"
#define Tag_Bid                    "18"
#define Tag_BidVol                "19"
#define Tag_Offer                "20"
#define Tag_OfferVol            "21"
#define Tag_Last                "22"
#define Tag_LastVol                "23"
#define Tag_TotalVol            "27"
//還有很多,有用到再找


程式內容大概這樣寫
CommodityRefer * GetCommodityRefer(const char *cpDBName, const char *cpSymbol, bool bAutoCreate = false)
{//商品索引用, 由行情源對應商品的記憶區塊
    spCurrCommodity = (CommodityRefer *)fnDbfTCdll_GetItemQuoteMemory(cpDBName, cpSymbol);
    if (!spCurrCommodity && bAutoCreate)
    {
        CommodityRefer sCommodity;
        memset(&sCommodity, 0, sizeof(sCommodity));
        spCurrCommodity = (CommodityRefer *)fnDbfTCdll_CreateItemQuoteMemory(cpDBName, cpSymbol, &sCommodity, sizeof(sCommodity));
        return spCurrCommodity;
    }
    return spCurrCommodity;
}

char caContractDBN[] = "RecentItemRefer";
RecentItemRefer * GetRecentItemRefer(const char *cpContractName)
{return (RecentItemRefer *)fnDbfTCdll_GetItemQuoteMemory(caContractDBN, cpContractName);}
bool MakeRecentItemRefer(CommodityRefer *spCommodity)
{//商品索引用, 由商品短碼反查
    if (spCommodity)
    {
        CommodityRefer &sCommodity = *spCommodity;
        struct RecentInfo &sRecentInfo = sCommodity.spRecentItem->sRecentInfo;
        RecentItemRefer sRecentRefer = {sCommodity.spRecentItem, &sCommodity};
        return (fnDbfTCdll_CreateItemQuoteMemory(caContractDBN, sRecentInfo.Name, &sRecentRefer, sizeof(sRecentRefer)) != NULL);
    }
    return false;
}


即時數據接收的Callback大概是這樣的內容
void DBFTCDLLAPI TagDataProcessFunction(const char *cpDBName, const char *cpVarName, const char *cpSymbol, const char *cpNewValue, const char *cpOldValue)
{// 1 - By Tag, 每個商品的每個Tag更新都會呼叫
    if (!spCurrCommodity)    //同商品會有多次通知,僅需檢索一次
        spCurrCommodity = GetCommodityRefer(cpDBName, cpSymbol, true);
//這裡的這個CB其實沒要作什麼事,可以取消不用 (留著只是示範)
//或者也可以設計一些flag幫助 ItemProcessFunction 處理
}

void DBFTCDLLAPI ItemProcessFunction(const char *cpDBName, const char *cpVarName, const char *cpSymbol, const char *cpNewValue, const char *cpOldValue)
{// 2 - By Item, 有更新的商品會呼叫 (傳入之cpVarName,cpNewValue,cpOldValue等參數為NULL)
    if (spCurrCommodity)
    {
        CommodityRefer &sCommodity = *spCurrCommodity;
        spCurrCommodity = NULL;// 本商品處理完畢,重抓

        if (!sCommodity.spRecentItem) //商品剛建立
        {
            char *cpContractName = fnDbfTCdll_GetCurrTagValue(Tag_ContractName);
            if (cpContractName && cpContractName[0])
            {//確認商品代碼存在, 產生對應的AmiBroker的RecentInf儲存區塊
                sCommodity.spRecentItem = (RecentInfoDataItem *)fnDbfTCdll_STManGetTickMemory(vpQuoteIndexHandle, iCountContracts, true);//true : 自動產生
                if (sCommodity.spRecentItem)
                {
                    memset(sCommodity.spRecentItem, 0, sizeof(RecentInfoDataItem));

                    struct RecentInfo &sRecentInfo = sCommodity.spRecentItem->sRecentInfo;
                    //初始化AmiBroker的RecentInf結構內容
                    sRecentInfo.nStructSize = sizeof(sRecentInfo);
                    memccpy(sRecentInfo.Name, cpContractName, 0, sizeof(sRecentInfo.Name));//商品代碼
                    memccpy(sRecentInfo.Exchange, fnDbfTCdll_GetCurrTagValue(Tag_ExchangeName), 0, sizeof(sRecentInfo.Exchange));//商品交易所/來源
                    sRecentInfo.nBitmap = RI_Wana;
                    iCountContracts++;

                    if (!GetRecentItemRefer(sRecentInfo.Name))
                    {//建立索引,使用短代碼作為AmiBroker的操作鍵
                        MakeRecentItemRefer(&sCommodity);
                    }

                    sRecentInfo.nBitmap |= RI_Wana | RI_DATEUPDATE;
                    sRecentInfo.nStatus = RI_STATUS_UPDATE | RI_STATUS_BIDASK | RI_STATUS_TRADE;

                    Set_float(sRecentInfo.fOpen, fnDbfTCdll_GetCurrTagValue(Tag_Open));
                    Set_float(sRecentInfo.fHigh, fnDbfTCdll_GetCurrTagValue(Tag_High));
                    Set_float(sRecentInfo.fLow, fnDbfTCdll_GetCurrTagValue(Tag_Low));
                    //Set_float(sRecentInfo.fOpenInt, fnDbfTCdll_GetCurrTagValue(Tag_OpenInterest));

                    Set_float(sRecentInfo.fBid, fnDbfTCdll_GetCurrTagValue(Tag_Bid));
                    Set_int(sRecentInfo.iBidSize, fnDbfTCdll_GetCurrTagValue(Tag_BidVol));
                    Set_float(sRecentInfo.fAsk, fnDbfTCdll_GetCurrTagValue(Tag_Offer));
                    Set_int(sRecentInfo.iAskSize, fnDbfTCdll_GetCurrTagValue(Tag_OfferVol));

                    Set_float(sRecentInfo.fLast, fnDbfTCdll_GetCurrTagValue(Tag_Last));
                    Set_int(sRecentInfo.iTradeVol, fnDbfTCdll_GetCurrTagValue(Tag_LastVol));
                    Set_int(sRecentInfo.iTotalVol, fnDbfTCdll_GetCurrTagValue(Tag_TotalVol));
                    vUpdateVol_forAmiBroker527(sRecentInfo);

                    vUpdateCurrDateTime();    //"etc_code_datetime.cpp"
                    sRecentInfo.nDateChange = lDecDate; // format YYYYMMDD
                    sRecentInfo.nTimeChange = lDecTime; // format HHMMSS
                    sRecentInfo.nDateUpdate = lDecDate; // format YYYYMMDD
                    sRecentInfo.nTimeUpdate = lDecTime; // format HHMMSS
                }
            }
        }
        else
        {
            struct RecentInfo &sRecentInfo = sCommodity.spRecentItem->sRecentInfo;
            sRecentInfo.nStatus = 0;

            if (Update_float(sRecentInfo.fOpen, fnDbfTCdll_GetCurrTagValue(Tag_Open)))
                sRecentInfo.nStatus |= RI_STATUS_UPDATE;
            if (Update_float(sRecentInfo.fHigh, fnDbfTCdll_GetCurrTagValue(Tag_High)))
                sRecentInfo.nStatus |= RI_STATUS_UPDATE;
            if (Update_float(sRecentInfo.fLow, fnDbfTCdll_GetCurrTagValue(Tag_Low)))
                sRecentInfo.nStatus |= RI_STATUS_UPDATE;
            //if (Update_float(sRecentInfo.fOpenInt, fnDbfTCdll_GetCurrTagValue(Tag_OpenInterest)))
            //    sRecentInfo.nStatus |= RI_STATUS_UPDATE;

            if (Update_float(sRecentInfo.fBid, fnDbfTCdll_GetCurrTagValue(Tag_Bid)))
                sRecentInfo.nStatus |= RI_STATUS_BIDASK;
            if (Update_int(sRecentInfo.iBidSize, fnDbfTCdll_GetCurrTagValue(Tag_BidVol)))
                sRecentInfo.nStatus |= RI_STATUS_BIDASK;
            if (Update_float(sRecentInfo.fAsk, fnDbfTCdll_GetCurrTagValue(Tag_Offer)))
                sRecentInfo.nStatus |= RI_STATUS_BIDASK;
            if (Update_int(sRecentInfo.iAskSize, fnDbfTCdll_GetCurrTagValue(Tag_OfferVol)))
                sRecentInfo.nStatus |= RI_STATUS_BIDASK;

            if (Update_float(sRecentInfo.fLast, fnDbfTCdll_GetCurrTagValue(Tag_Last)))
                sRecentInfo.nStatus |= RI_STATUS_TRADE;
            if (Update_int(sRecentInfo.iTradeVol, fnDbfTCdll_GetCurrTagValue(Tag_LastVol)))
                sRecentInfo.nStatus |= RI_STATUS_TRADE;
            if (Update_int(sRecentInfo.iTotalVol, fnDbfTCdll_GetCurrTagValue(Tag_TotalVol)))
                sRecentInfo.nStatus |= RI_STATUS_TRADE;
            if ((sRecentInfo.nStatus & RI_STATUS_TRADE) == RI_STATUS_TRADE)
                vUpdateVol_forAmiBroker527(sRecentInfo);

            vUpdateCurrDateTime();    //"etc_code_datetime.cpp"
            if (sRecentInfo.nTimeChange != lDecTime)
            {
                sRecentInfo.nTimeChange = lDecTime; // format HHMMSS
                sRecentInfo.nTimeUpdate = lDecTime; // format HHMMSS
                sRecentInfo.nStatus |= RI_STATUS_UPDATE;
                if (sRecentInfo.nDateChange != lDecDate)
                {
                    sRecentInfo.nDateChange = lDecDate; // format YYYYMMDD
                    sRecentInfo.nDateUpdate = lDecDate; // format YYYYMMDD
                    sRecentInfo.nBitmap |= RI_DATECHANGE;
                }
            }
        }
    }
}

如上之設計, 如果想玩其它花樣, 可以用以下方式隨時取得商品表與內容,
RecentInfoDataItem *spDataItem = (RecentInfoDataItem *)fnDbfTCdll_STManGetTickMemory(vpQuoteIndexHandle, iIndex, false);
其中iIndex就是直接調出第幾個商品 0 <= iIndex < iCountContracts
所以用一個for-loop就等於將整個商品表掃一次了~

ADK要設計的內容就很簡單了...Init/Release
PLUGINAPI int Init(void)
{
    if (!hmDbfTCdll)
        hmDbfTCdll = LoadLibrary(DbfTCdllLibSource);
    if (hmDbfTCdll)
    {
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_SelectDataSet);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_InitUser);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_Start);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_Stop);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetConnectionStatus);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_CallBack_Register);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetCurrTagValue);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetPrevTagValue);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_CreateStringIdMapping);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetStringId);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_CreateItemQuoteMemory);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetItemQuoteMemory);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_GetQueueCount);

        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManCreateGroup);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManCreateProduct);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManGetTickHandle);
        WWXfunc_DllImport(hmDbfTCdll, fnDbfTCdll_STManGetTickMemory);

        if (fnDbfTCdll_CallBack_Register)
        {
            //CallBack 模式
            fnDbfTCdll_CallBack_Register(TagDataProcessFunction, 1);// 1 - By Tag, 每個商品的每個Tag更新都會呼叫
            fnDbfTCdll_CallBack_Register(ItemProcessFunction, 2);// 2 - By Item, 有更新的商品會呼叫 (傳入之cpVarName,cpNewValue,cpOldValue等參數為NULL)
            if (fnDbfTCdll_STManCreateGroup)
            {
                unsigned long uGroupIndex = fnDbfTCdll_STManCreateGroup("RecentInfoDataItem", sizeof(RecentInfoDataItem), NULL, 64, 256);
                unsigned long uProductIndex = fnDbfTCdll_STManCreateProduct(uGroupIndex);
                vpQuoteIndexHandle = fnDbfTCdll_STManGetTickHandle(uGroupIndex, uProductIndex);
                if (!vpQuoteIndexHandle)
                    MessageBox(0, "Dll file "DbfTCdllLibSource" STMan Err! Memory crash!!", PLUGIN_NAME" Init Error!", MB_OK);
            }
            else
                MessageBox(0, "Dll file "DbfTCdllLibSource" not support STMan", PLUGIN_NAME" Init Error!", MB_OK);
        }
        else
        {
            FreeLibrary(hmDbfTCdll);
            hmDbfTCdll = NULL;
            MessageBox(0, "Dll file "DbfTCdllLibSource" load faild!!", PLUGIN_NAME" Init Error!", MB_OK);
        }
    }
    else
    {
        MessageBox(0,
            "Dll file "DbfTCdllLibSource" not found!!\n"
            "請將檔案置於AmiBroker的工作環境資料夾內"
            , PLUGIN_NAME" Init Error!"
            , MB_OK
        );
    }

    if (hmDbfTCdll)
    {
        fnDbfTCdll_InitUser(PLUGIN_NAME);//直接用PLUGIN_NAME當User,同一資訊源相同User只能一個在線
        fnDbfTCdll_SelectDataSet(caDB_list);
        if (fnDbfTCdll_Start(caDTS_host))
            g_nStatus = STATUS_WAIT;
    }

    return 1; // default implementation does nothing

};     

PLUGINAPI int Release(void)
{
    if (hmDbfTCdll)
        fnDbfTCdll_Stop();

    return 1; // default implementation does nothing
};


ADK的Realtime Quote就只要這樣
// GetSymbolLimit function is optional, used only by real-time plugins
PLUGINAPI int GetSymbolLimit( void )
{//告知AmiBroker, 可以向本plugin註冊最多幾個symbol
    return 99999;
}

// GetRecentInfo function is optional, used only by real-time plugins
PLUGINAPI struct RecentInfo * GetRecentInfo( LPCTSTR pszTicker )
{//HFOCX可用的代碼可以看這裏==> http://hfocx.dm.ftw.tw/0025/?Quote=All&xml=1
    RecentItemRefer *spRecentRefer = GetRecentItemRefer(pszTicker);
    if (spRecentRefer)
        return &(spRecentRefer->spRecentItem->sRecentInfo);
    return NULL;
}


把連線狀態回應給AmiBroker可以簡單這樣設計
////////////////////////////////////////
// GetStatus function is called periodically
// (in on-idle processing) to retrieve the status of the plugin
// Returned status information (see PluginStatus structure definition)
// contains numeric status code    as well as short and long
// text descriptions of status.
//
// The highest nibble (4-bit part) of nStatus code
// represents type of status:
// 0 - OK, 1 - WARNING, 2 - MINOR ERROR, 3 - SEVERE ERROR
// that translate to color of status area:
// 0 - green, 1 - yellow, 2 - red, 3 - violet

PLUGINAPI int GetStatus( struct PluginStatus *status )
{
    char *cpStatus = "GetConnectionStatus() failed!";
    if (hmDbfTCdll)
    {
        cpStatus = fnDbfTCdll_GetConnectionStatus();
        if (cpStatus[1] == 'O')
            g_nStatus = STATUS_CONNECTED;
        else if (cpStatus[1] == 'X')
            g_nStatus = STATUS_DISCONNECTED;
        else
            g_nStatus = STATUS_WAIT;
    }

    switch( g_nStatus )
    {
    case STATUS_WAIT:
        status->nStatusCode = 0x10000000;
        strcpy( status->szShortMessage, "WAIT" );
        strcpy( status->szLongMessage, cpStatus );
        status->clrStatusColor = RGB( 255, 255, 0 );
        break;
    case STATUS_CONNECTED:
        status->nStatusCode = 0x00000000;
        strcpy( status->szShortMessage, "OK" );
        strcpy( status->szLongMessage, cpStatus );
        status->clrStatusColor = RGB( 0, 255, 0 );
        break;
    case STATUS_DISCONNECTED:
        status->nStatusCode = 0x20000000;
        strcpy( status->szShortMessage, "ERR" );
        strcpy( status->szLongMessage, cpStatus );
        status->clrStatusColor = RGB( 255, 0, 0 );
        break;
    case STATUS_SHUTDOWN:
        status->nStatusCode = 0x30000000;
        strcpy( status->szShortMessage, "DOWN" );
        strcpy( status->szLongMessage, "Connection is shut down.\nThe dll file '"DbfTCdllLibSource"' can't load." );
        status->clrStatusColor = RGB( 192, 0, 192 );
        break;
    default:
        strcpy( status->szShortMessage, "Unkn" );
        strcpy( status->szLongMessage, cpStatus );
        status->clrStatusColor = RGB( 255, 255, 255 );
        break;
    }

    return 1;
}


把作出來的dll放AmiBroker安裝路徑下的 Plugins 資料夾中
例如 C:\Program Files\AmiBroker\Plugins\
而收DTS用的 DbfTCdll2.dll 則放AmiBroker的工作路徑下
例如 C:\Program Files\AmiBroker\
然後跑AmiBroker起來, 到選單 Tools --> Plug-ins...
可以看到

然後有選單看是要新增 File --> New --> Database...
還是編輯 File --> Database setting...
就可以選這資料源來使用了

沒有任何商品代碼嗎? 從選單的 Sumbol --> New... 將下面整行內容貼進去
AUD/USD,AUD/JPY,USD/CAD,USD/CHF,CHF/JPY,EUR/USD,EUR/CHF,EUR/GBP,EUR/JPY,GBP/USD,GBP/CHF,GBP/JPY,HKGOLD,USD/JPY,LKG,GOLD,GOLDX,SILVER,SILVERX,NZD/USD
按OK鈕之後, 所有輸入的商品代碼就會列在Symbol視窗格, 然後於視窗格中按Ctrl+A全選,
再用滑鼠右鍵點出功能清單, 點選 Add to Realtime Quote windows 這樣就完工了~

如果還沒有看到Realtime Quote視窗格, 則是要在選單列的 Window 那邊, 把Realtime Quote項目點勾起來即可

本例中與上游(UniDBF)的上游(Pats-Emu)相比對

當然, 如果使用VC10以上或DevC5就能產出x64的dll版本搭配64位元版的AmiBroker囉!


更進一步就是設計設定的介面, 可以用文件編輯ini的方式或是比較搞剛作GUI,
依拙見還是選擇前者既簡單又方便, 就請各自發揮了~

當然, 任何資訊都可以很容易轉成DTS服務, 只要轉成DTS方式服務後,
就都能像上面一樣輕輕鬆鬆運用在AmiBroker裏了... 即便要加入線圖資訊的功能,
透過GMDS提供的STM很容易就能完美又簡潔的建構出來~
( 前面提到 iCountContracts 所形成的商品表, 就也是使用STM的範例 )

這也就是說可以把各種不好使的資訊來源透過GMDS的提供的方式(Feed-Server/API)全都打進GMDS的架構內,
然後不管什麼資訊源最後統一都是最容易上手的DTS介接模式, 再來DTS以下都是維持不變的,
就算源頭換來換去再也不用擔心要大費周章改一堆了!
如能透過券商端提供TSHS與客端使用PatsEmu-TSHS, 那麼資料完全自動銜接, 也不用再考慮什麼回補問題了~

針對 DbfTCdll2.dll 的使用上, 關於程式使用dll的方式,
可進一步參考 使用dll的一些基礎見識

其它連結: AmiBroker



註解