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

張貼日期:Jan 15, 2019 6:0:59 AM

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