Modules d'extension Lua

From FxCodeBaseWiki
Jump to: navigation, search

Que sont les modules d'extension Lua ?

Le module d'extension Lua est une bibliothèque de liens dynamiques qui contient un ensemble de fonctions écrites en C ou en C++ pouvant être utilisées dans le code Lua.

Pour comprendre cet article, il faut connaître les MV Lua et le développement avec les MV Lua. Vous pouvez obtenir des informations sur les VM Lua dans la documentation sur Lua, Section 3. The Application Program Interface.

Notez que le module d'extension Lua peut être créé uniquement en C ou en C++. Cet article part du principe que Microsoft Visual Studio est utilisé mais n'importe quel compilateur de C++ Win 32 peut être employé.

Comment commencer ?

Préparation de l'environnement

Il vous suffit de télécharger et d'installer la dernière version d'Indicore Integration SDK. La version la plus récente est toujours disponible ici : IndicoreDev.exe

Création du projet

  • Créez un nouveau projet C++/Win32.
  • Choisissez un type d'application DLL et créez un projet vide.
  • Dans les options de l'éditeur de liens, à la section « Input », ajoutez C:\Gehtsoft\IndicoreDev\lib\lua5.1.lib et C:\Gehtsoft\IndicoreDev\lib\indicore2.lib à la propriété Additional Dependencies.
  • Le nom du fichier de sortie doit être identique au nom de la table d'extension que vous allez utiliser. Dans notre cas, nous allons appliquer la fonction de la valeur géométrique moyenne pour Lua. Nous appellerons donc notre module de sortie geomean.dll.

Fichiers d'en-tête

Ajoutez les en-têtes suivants à votre fichier source :

#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"

Écriture du modèle de la fonction d'extension

Ensuite, vous devez créer un modèle de fonction d'extension. Toutes les fonctions d'extension sont faites exactement sur le même modèle. Vous pouvez utiliser n'importe quel nom mais il est préférable de donner un nom semblable à celui que vous souhaitez utiliser.

/** geomean.calc(stream, from, to). 

    stream le flux de ticks
    from   la première période de l'intervalle du calcul
    to     la dernière période de l'intervalle du calcul

    La fonction renvoie la valeur moyenne géométrique (un nombre)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;
    //À définir : calcul de la valeur moyenne dans une variable ret
  
    lua_pushnumber(L, ret);
    return 1;
}

Création de la description du module

Maintenant, il faut créer une description pour la table du module d'extension. La table du module d'extension est un ensemble de structures luaL_reg. Chaque élément du tableau correspond à une fonction et définit le nom de la fonction tel qu'il est affiché dans Lua. Le dernier élément doit contenir uniquement des zéros.

static const struct luaL_reg geomean_lib [] =
{
     {"calc", geomean_calc},
     {NULL, NULL}
};

Création de la fonction d'enregistrement du module

Ensuite, il faut créer une fonction exportable qui sera appelée lorsque Lua devra charger notre module d'extension. Le nom de cette fonction doit être créé selon le modèle suivant : luaopen_namename correspond au nom du module d'extension (geomean dans notre exemple).

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;
}

Préparation de l'indicateur test

L'indicateur est très semblable à la moyenne mobile simple (MVA.lua). Seul un paramètre entier N (nombre de périodes) est nécessaire. Il peut être appliqué sur les données de ticks et calculé à partir de la Ne barre. Créons le squelette de l'indicateur. Pour plus d'informations sur l'écriture des indicateurs, consultez Indicore SDK, notamment les instructions détaillées sur la création d'un indicateur simple.

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

Faites attention aux lignes surlignées. La première oblige Lua à charger votre extension. La seconde est un appel de notre fonction.

Implémentation de la fonction d'extension

Nous devons maintenant implanter la fonction d'extension. Tout d'abord, il faut récupérer les paramètres de la pile. Comme vous pouvez le voir dans le code, le premier paramètre est un flux. Nous devons donc convertir la table Lua en interface IIndicatorTickSource. C'est pour cela qu'Indicore Integration SDK est nécessaire pour ce projet. Sa classe IndicoreAccessors a été conçue pour cela.

Il vous faut donc obtenir les paramètres de la pile et vérifier les types.

/** geomean.calc(stream, from, to). 

    stream le flux de ticks
    from   la première période de l'intervalle du calcul
    to     la dernière période de l'intervalle du calcul

    La fonction renvoie la valeur moyenne géométrique (un nombre)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;

    IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
    if (src == 0)
        return luaL_error(L, "le premier paramètre doit être la source.");

    if (lua_isnumber(L, 2) == 0)
        return luaL_error(L, "le deuxième paramètre doit être un nombre");
    int from = (int)lua_tonumber(L, 2);

    if (lua_isnumber(L, 3) == 0)
        return luaL_error(L, "le troisième paramètre doit être un nombre");
    int to = (int)lua_tonumber(L, 3);
  
    lua_pushnumber(L, ret);
    return 1;
}

Maintenant, vérifiez les paramètres « from » et « to ». Les deux valeurs doivent se trouver dans la fourchette du cours des données sources et la valeur « to » doit se trouver après la valeur « from ».

/** geomean.calc(stream, from, to). 

    stream le flux de ticks
    from   la première période de l'intervalle du calcul
    to     la dernière période de l'intervalle du calcul

    La fonction renvoie la valeur moyenne géométrique (un nombre)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;

    IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
    if (src == 0)
        return luaL_error(L, "le premier paramètre doit être la source.");

    if (lua_isnumber(L, 2) == 0)
        return luaL_error(L, "le deuxième paramètre doit être un nombre");
    int from = (int)lua_tonumber(L, 2);

    if (lua_isnumber(L, 3) == 0)
        return luaL_error(L, "le troisième paramètre doit être un nombre");
    int to = (int)lua_tonumber(L, 3);

    if (from < src->getFirstPeriod() || from >= src->size() ||
        to < src->getFirstPeriod() || to >= src->size())
        return luaL_error(L, "l'indice de période n'est pas dans l'intervalle");

    if (to <= from)
        return luaL_error(L, "le troisième paramètre doit être supérieur au deuxième");

  
    lua_pushnumber(L, ret);
    return 1;
}

Maintenant, appliquez le calcul de la moyenne géométrique. La formule pour calculer la moyenne géométrique de n valeurs est la suivante :

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

/** geomean.calc(stream, from, to). 

    stream le flux de ticks
    from   la première période de l'intervalle du calcul
    to     la dernière période de l'intervalle du calcul

    La fonction renvoie la valeur moyenne géométrique (un nombre)
  */
int geomean_calc(lua_State *L)
{
    double ret = 0;

    IIndicatorTickSource *src = IndicoreAccessors::getIIndicatorTickSource(L, 1);
    if (src == 0)
        return luaL_error(L, "le premier paramètre doit être la source.");

    if (lua_isnumber(L, 2) == 0)
        return luaL_error(L, "le deuxième paramètre doit être un nombre");
    int from = (int)lua_tonumber(L, 2);

    if (lua_isnumber(L, 3) == 0)
        return luaL_error(L, "le troisième paramètre doit être un nombre");
    int to = (int)lua_tonumber(L, 3);

    if (from < src->getFirstPeriod() || from >= src->size() ||
        to < src->getFirstPeriod() || to >= src->size())
        return luaL_error(L, "l'indice de période n'est pas dans l'intervalle");

    if (to <= from)
        return luaL_error(L, "le troisième paramètre doit être supérieur au deuxième");

    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;
}

Voilà, l'extension est créée. Il suffit de la compiler.

Déploiement et test

  • Placez geomean.dll compilé dans le dossier de l'application (par exemple Marketscope ou Indicator Debugger) où se trouve lua5.1.dll (généralement, il s'agit du dossier où l'application est installée) ;
  • Installez l'indicateur ;
  • Exécutez l'indicateur.

Problèmes connus

Exceptions C++

La version utilisée de LuaJIT ne prend pas en charge les exceptions C++ pour le code Microsoft Visual C++/Win 32. Vous ne devez donc pas utiliser un droit try/catch dans la fonction appelée de Lua ou rappeler Lua d'un bloc try/catch.

Vous pouvez également consulter les liens suivants pour en savoir plus sur ce problème : documentation sur LuaJIT, fil de discussion en anglais.

Cet article dans d'autres langues

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