使用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