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, "geomean 库未注册");
}
return 1;
}
准备测试指标
该指标与简单移动平均线 (MVA.lua) 指标十分相似。它只采用一个整数参数 N(周期数),可应用于分笔成交点数据,还可从第 N 条棒线开始计算。让我们创建指标框架。有关编写指标的信息,请参阅 Indicore SDK,特别是关于创建简单指标的分步说明。
function Init()
indicator:name("测试指标");
indicator:description("");
indicator:requiredSource(core.Tick);
indicator:type(core.Indicator);
indicator.parameters:addGroup("计算");
indicator.parameters:addInteger("N", "周期数", "", 10, 2, 1000);
indicator.parameters:addGroup("样式");
indicator.parameters:addColor("C", "线条颜色", "", core.rgb(0, 0, 255));
indicator.parameters:addInteger("W", "线条宽度", "", 1, 1, 5);
indicator.parameters:addInteger("S", "线条样式", "", 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 • русский • 中文 • 中文(繁體) |
---|