Lua 擴展模組
Contents
什麼是 Lua 擴展模組?
Lua 擴展模組是一個動態連結程式庫,它包含使用 C 或 C++ 編寫、可透過 Lua 程式碼使用的函數集合。
本文假設您熟悉 Lua VM 並使用 Lua VM 進行開發。您可以參閱 Lua 文件Section 3. The Application Program Interface,瞭解 Lua VM。
請注意,Lua 擴展模組只能用 C 或 C++ 開發。本文假設您使用 Microsoft Visual Studio,但您可使用任何 Win32 C++ 編譯器來代替它。
如何開始?
準備環境
您只需下載並安裝最新版 Indicore Integration SDK。最新版本始終可以在此處獲取:IndicoreDev.exe
建立專案
- 建立新 C++/Win32 專案。
- 選擇 DLL 應用程式類型,並建立一個空專案
- 在連結器選項的「輸入」部分,添加
C:\Gehtsoft\IndicoreDev\lib\lua5.1.lib
和C:\Gehtsoft\IndicoreDev\lib\indicore2.lib
至「其他相依性」屬性。 - 輸出檔案的名稱必須與您將要使用的擴展表名稱完全相同。在這裡,我們要實現 Lua 的幾何平均值函數,因此可以將輸出模組命名為
geomean.dll
。
標頭檔
將下列標頭檔添加至原始檔案:
#include <windows.h>
#include <stdio.h>
#include <math.h>
#include <tchar.h>
#include <string>
#include <vector>
#include <map>
#include <list>
#define INDICORE2_EXTKIT
#include "C:/Gehtsoft/IndicoreDev/include/indicore2.h"
編寫擴展函數範本
然後,您必須建立一個擴展函數範本。所有擴展函數的原型完全相同。您可以使用任何名稱,但好的做法是使用與我們要用的名稱相似的名稱。
/** The geomean.calc(stream, from, to).
stream 價格點流
from 要計算的範圍的第一個週期
to 要計算的範圍的最後一個週期
該函數返回幾何平均值(1 個數字)
*/
int geomean_calc(lua_State *L)
{
double ret = 0;
//待定:在返回的變數中計算平均值
lua_pushnumber(L, ret);
return 1;
}
建立模組描述
現在,我們必須建立一個對擴展模組表的描述。擴展模組表是一個 luaL_reg
結構陣列。陣列的每個元素對應一個函數,並定義函數名稱,在 lua 中可見。最後一個元素必須為由零填充的結構。
static const struct luaL_reg geomean_lib [] =
{
{"calc", geomean_calc},
{NULL, NULL}
};
建立模組註冊函數
現在我們必須建立一個可匯出函數,該函數將在 Lua 被請求載入擴展模組時呼叫。此函數的名稱必須使用以下模式建立:luaopen_name
,其中 name 是擴展模組的名稱(在此處是 geomean)。
extern "C" int __declspec(dllexport) luaopen_extension (lua_State *L)
{
luaL_register(L, "geomean", geomean_lib);
lua_getglobal(L, "geomean");
if (lua_isnil(L, -1) || !lua_istable(L, -1)) {
luaL_error(L, "the geomean library is not registered");
}
return 1;
}
準備測試指標
該指標與簡單移動平均線 (MVA.lua) 指標十分相似。它只採用一個整數參數 N(週期數),可應用於價格點資料,還可從第 N 條棒線開始計算。讓我們建立指標框架。有關編寫指標的資訊,請參閱 Indicore SDK,特別是關於建立簡單指標的逐步說明。
function Init()
indicator:name("Test indicator");
indicator:description("");
indicator:requiredSource(core.Tick);
indicator:type(core.Indicator);
indicator.parameters:addGroup("Calculation");
indicator.parameters:addInteger("N", "Number of periods", "", 10, 2, 1000);
indicator.parameters:addGroup("Style");
indicator.parameters:addColor("C", "Line Color", "", core.rgb(0, 0, 255));
indicator.parameters:addInteger("W", "Line Width", "", 1, 1, 5);
indicator.parameters:addInteger("S", "Line Style", "", core.LINE_SOLID);
indicator.parameters:setFlag("S", core.FLAG_LEVEL_STYLE);
end
local n;
local first = nil;
local source = nil;
local out = nil;
require("geomean");
function Prepare(onlyName)
local name;
name = profile:id() .."(" .. instance.source:name() .. ")";
instance:name(name);
if onlyName then
return ;
end
source = instance.source;
n = instance.parameters.N;
first = source:first() + n - 1;
out = instance:addStream("GEOMEAN", core.Line, name, "GEOMEAN", instance.parameters.C, first);
out:setWidth(instance.parameters.W);
out:setStyle(instance.parameters.S);
end
function Update(period, mode)
if period >= first then
out[period] = geomean.calc(source, period - n + 1, period);
end
end
請注意突出顯示的行。第一行強制 Lua 載入擴展。第二行呼叫函數。
實現擴展函數
現在,我們可以實現擴展函數。首先,我們必須從堆疊中獲取參數。從指標程式碼可以看出,第一個參數是一個流。因此,您需要瞭解如何將 Lua 表轉換為 IIndicatorTickSource
介面。這也是該特定專案需要 Indicore Integration SDK 的原因。其 IndicoreAccessors
類別專為實現該目的而設計。
因此,從堆疊獲取參數值,並檢查類型。
/** The geomean.calc(stream, from, to).
stream 價格點流
from 要計算的範圍的第一個週期
to 要計算的範圍的最後一個週期
該函數返回幾何平均值(1 個數字)
*/
int geomean_calc(lua_State *L)
{
double ret = 0;
IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
if (src == 0)
return luaL_error(L, "第一個參數必須為來源。");
if (lua_isnumber(L, 2) == 0)
return luaL_error(L, "第二個參數必須為數字");
int from = (int)lua_tonumber(L, 2);
if (lua_isnumber(L, 3) == 0)
return luaL_error(L, "第三個參數必須為數字");
int to = (int)lua_tonumber(L, 3);
lua_pushnumber(L, ret);
return 1;
}
現在,檢查「from」和「to」參數。這兩個參數都必須在價格來源資料範圍之內,並且「to」必須晚於「from」。
/** The geomean.calc(stream, from, to).
stream 價格點流
from 要計算的範圍的第一個週期
to 要計算的範圍的最後一個週期
該函數返回幾何平均值(1 個數字)
*/
int geomean_calc(lua_State *L)
{
double ret = 0;
IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
if (src == 0)
return luaL_error(L, "第一個參數必須為來源。");
if (lua_isnumber(L, 2) == 0)
return luaL_error(L, "第二個參數必須為數字");
int from = (int)lua_tonumber(L, 2);
if (lua_isnumber(L, 3) == 0)
return luaL_error(L, "第三個參數必須為數字");
int to = (int)lua_tonumber(L, 3);
if (from < src->getFirstPeriod() || from >= src->size() ||
to < src->getFirstPeriod() || to >= src->size())
return luaL_error(L, "週期索引超出範圍");
if (to <= from)
return luaL_error(L, "第三個參數必須大於第二個參數");
lua_pushnumber(L, ret);
return 1;
}
現在實現幾何平均值計算。計算 n 的幾何平均值的公式為:
<math>\operatorname{geomean}(price, n)_i = \sqrt[n]{\prod_{j=i-n+1}^i price_j} = ({\prod_{j=i-n+1}^i price_j})^{\frac{1}{n}}</math>
/** The geomean.calc(stream, from, to).
stream 價格點流
from 要計算的範圍的第一個週期
to 要計算的範圍的最後一個週期
該函數返回幾何平均值(1 個數字)
*/
int geomean_calc(lua_State *L)
{
double ret = 0;
IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
if (src == 0)
return luaL_error(L, "第一個參數必須為來源。");
if (lua_isnumber(L, 2) == 0)
return luaL_error(L, "第二個參數必須為數字");
int from = (int)lua_tonumber(L, 2);
if (lua_isnumber(L, 3) == 0)
return luaL_error(L, "第三個參數必須為數字");
int to = (int)lua_tonumber(L, 3);
if (from < src->getFirstPeriod() || from >= src->size() ||
to < src->getFirstPeriod() || to >= src->size())
return luaL_error(L, "週期索引超出範圍");
if (to <= from)
return luaL_error(L, "第三個參數必須大於第二個參數");
double n = (double)(to - from + 1);
double prod = 1;
for (int i = from; i <= to; i++)
prod *= src->getTick(i);
ret = pow(prod, 1.0 / n);
lua_pushnumber(L, ret);
return 1;
}
至此,擴展已完成。只需編譯即可。
部署和測試
- 安裝指標;
- 執行指標。
已知問題
C++ 異常
使用的 LuaJIT 版本不支援 Microsoft Visual C++/Win32 程式碼的 C++ 例外。因此,您不得在從 Lua 呼叫的函數中使用 try/catch 權限,也不得從 try/catch 區塊內部回呼 Lua。
其他語言版本
Language: | English • español • français • русский • 中文 • 中文(繁體) |
---|