Lua 擴展模組

From FxCodeBaseWiki
Jump to: navigation, search

什麼是 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.libC:\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;
}

至此,擴展已完成。只需編譯即可。

部署和測試

  • 將編譯的 geomean.dll 放入 lua5.1.dll 所在的同一個應用程式(例如匯圖寶指標偵錯器)資料夾(通常為應用程式的安裝資料夾);
  • 安裝指標;
  • 執行指標。

已知問題

C++ 異常

使用的 LuaJIT 版本不支援 Microsoft Visual C++/Win32 程式碼的 C++ 例外。因此,您不得在從 Lua 呼叫的函數中使用 try/catch 權限,也不得從 try/catch 區塊內部回呼 Lua。

亦請參閱該問題的相關內容:LuaJIT 文件討論帖

其他語言版本

Language: English  • español • français • русский • 中文 • 中文(繁體)‎