Signal simple - Alerte pour nouvelle bougie

From FxCodeBaseWiki
Jump to: navigation, search

Tâche

Développons un signal simple qui affiche un message ou émet un son lorsqu'une nouvelle bougie de l'unité de temps choisie apparaît. Supposons que la bougie apparaisse à l'arrivée du premier tick appartenant à une nouvelle bougie.

Écriture du signal

Création du fichier

Commencez par créer un fichier vide :

  • Exécutez Lua Editor.
  • Cliquez sur File->Strategy->New.
  • Dans l'assistant, cliquez sur Cancel (nous allons créer la stratégie à partir de rien !).

Ne vous laissez pas le terme « stratégie » vous induire en erreur. Un signal est une stratégie qui ne trade pas. Il ne fait qu'afficher des alertes.

Un nouveau fichier de stratégie est créé. Enregistrez-le. Le nom du fichier correspondra à une abréviation de la stratégie (MA_ADVISOR pour la stratégie Simple Moving Average, par exemple). Appelons notre stratégie NEW_CANDLE.

  • Cliquez sur File->Save As. Étant donné que nous avons créé la stratégie, l'éditeur ouvrira le dossier strategies du SDK. Si ce n'est pas le cas, veuillez changer de dossier. Nous faisons de notre mieux pour nous adapter au changement de la boîte de dialogue d'ouverture/d'enregistrement de fichier à chaque nouvelle version de Windows, mais nous n'y parvenons pas toujours
  • Indiquez NEW_CANDLE.lua comme nom de fichier et cliquez sur OK.

Voilà, le fichier est créé.

Introduction de la stratégie

Introduire une stratégie signifie fournir toutes les informations requises pour afficher cette stratégie dans la liste des stratégies, demander à l'utilisateur d'indiquer les paramètres, etc.

Toutes les informations d'introduction devant être spécifiées dans la fonction Init() de la stratégie, écrivons maintenant ce code. Commençons par définir un nom et une description compréhensibles pour les utilisateurs.

function Init()
    strategy:name("Alert New Candle");
    strategy:description("The signal shows alerts when the first tick of a new candle appears");
end

Décrivons maintenant les paramètres devant être demandés. Nous avons besoin des paramètres suivants :

L'unité de temps de la bougie à observer

L'unité de temps n'est qu'un code. Par exemple, m1 signifie une bougie 1 minute, H1 une bougie 1 heure, etc. Il s'agit donc de données de chaîne. Ajoutons un paramètre de chaîne :

function Init()
    strategy:name("Alert New Candle");
    strategy:description("The signal shows alerts when the first tick of a new candle appears");
    
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
end

Nous ne souhaitons toutefois pas que l'utilisateur ait à se soucier de tous ces codes. Il est préférable de le laisser choisir l'unité de temps dans une liste. Nous pouvons utiliser la méthode addStringAlternative() pour créer une liste de valeurs de paramètres, comme nous l'avons fait dans l'indicateur GMA, mais nous pouvons également utiliser des repères de paramètres. Un drapeau de paramètre est une fonction d'Indicore qui permet aux indicateurs et stratégies de fournir à l'application hôte des informations sur la manière de gérer le paramètre :

function Init()
    strategy:name("Alert New Candle");
    strategy:description("The signal shows alerts when the first tick of a new candle appears");
    
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
end

L'application hôte sait à présent que le paramètre doit correspondre à une liste d'unités de temps.

Demandons à l'utilisateur s'il souhaite que l'alerte s'affiche.

Il s'agit d'un simple paramètre Oui/Non. Ces valeurs Oui/Non étant des valeurs « booléennes », créons un paramètre booléen :

function Init()
    ...
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
    strategy.parameters:addBoolean("ShowMessage", "Show message when new candle appears?", "", false);
end

Demandons à l'utilisateur s'il souhaite qu'un son soit émis.

Là encore, il s'agit d'un simple paramètre Oui/Non.

function Init()
    ...
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
    strategy.parameters:addBoolean("ShowMessage", "Show message when new candle appears?", "", false);
    strategy.parameters:addBoolean("PlaySound", "Play Sound when new candle appears?", "", true);
end

Toutefois, pour le son, nous devons également demander le nom du fichier audio et si l'utilisateur souhaite que ce son soit lu de manière récurrente jusqu'à ce qu'il réalise une action donnée dans la Station de trading. Veuillez noter que nous allons à nouveau utiliser le drapeau de paramètre pour indiquer à l'application que le fichier doit être un fichier audio.

function Init()
    ...
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);
    strategy.parameters:addBoolean("ShowMessage", "Show message when new candle appears?", "", false);
    strategy.parameters:addBoolean("PlaySound", "Play Sound when new candle appears?", "", true);
    strategy.parameters:addFile("Sound", "Sound file", "", "");
    strategy.parameters:setFlag("Sound", core.FLAG_SOUND);
    strategy.parameters:addBoolean("RecurrentSound", "Recurrent sound?", "", false);
end

Voilà, c'est tout. Ajoutons les groupes de paramètres pour faciliter la navigation, et la méthode Init() est prête :

function Init()
    strategy:name("Alert New Candle");
    strategy:description("The signal shows alerts when the first tick of a new candle appears");

    strategy.parameters:addGroup("Parameters");
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);

    strategy.parameters:addGroup("Alerts");
    strategy.parameters:addBoolean("ShowMessage", "Show message when new candle appears?", "", false);
    strategy.parameters:addBoolean("PlaySound", "Play Sound when new candle appears?", "", true);
    strategy.parameters:addFile("Sound", "Sound file", "", "");
    strategy.parameters:setFlag("Sound", core.FLAG_SOUND);
    strategy.parameters:addBoolean("RecurrentSound", "Recurrent sound?", "", false);
end

L'application hôte sait maintenant comment afficher notre stratégie dans la liste des stratégies et comment demander les paramètres lorsque l'utilisateur souhaite exécuter la stratégie.

Veuillez noter que nous ne disposons d'aucun paramètre pour l'instrument. Nous supposons que tout signal ou stratégie est toujours appliqué sur un instrument et un seul (ils peuvent néanmoins utiliser plusieurs instruments) et qu'il est activé à chaque arrivée d'un nouveau tick de cet instrument. Aussi, l'application hôte demandera de toute façon à l'utilisateur de l'instrument d'appliquer la stratégie et nous n'avons donc pas à spécifier ce paramètre de façon explicite.

Lancement de la stratégie

Créons maintenant la méthode Prepare() de la stratégie. Cette méthode est appelée :

  • lorsque l'utilisateur modifie les paramètres de la stratégie pendant la création de cette dernière. Dans ce cas, on attend uniquement de la stratégie qu'elle vérifie les paramètres et définisse le nom de l'instance ;
  • avant le démarrage de la stratégie. Dans ce cas, on attend de la stratégie qu'elle prépare toutes les données pour l'exécution.

Commençons par la première tâche. Nous devons vérifier les paramètres et définir le nom de la stratégie.

Un seul paramètre doit être vérifié. L'utilisateur ne doit PAS choisir les ticks comme unité de temps, sans quoi il recevra une alerte à chaque apparition d'un nouveau tick. Il est possible de recevoir plusieurs alertes par seconde.

function Prepare(onlyName)
    assert(instance.parameters.TF ~= "t1", "Please choose non-tick time frame");
end

Définissons maintenant le nom de l'instance de stratégie. Nous recommandons que le nom inclue le nom abrégé de la stratégie, le nom de l'instrument et tous les paramètres importants. Si seul le nom est requis (le premier cas d'appel de la méthode Prepare()), nous devons ajouter un retour (return) pour éviter que des opérations inutiles soient exécutées lors de la préparation de la stratégie pour son lancement.

function Prepare(onlyName)
    assert(instance.parameters.TF ~= "t1", "Please choose non-tick time frame");

    local name;
    name = profile:id() .. "(" .. instance.bid:instrument() .. "," .. instance.parameters.TF .. ")";
    instance:name(name);
    if onlyName then
        return ;
    end
end

En fait, nous n'avons besoin de rien à ce stade et pouvons continuer. Nous reviendrons toutefois ultérieurement à la fonction Prepare(), lorsque nous saurons que rajouter d'autre à ce niveau.

Mise à jour de la stratégie

La stratégie doit également disposer de la fonction Update(), qui est appelée à chaque arrivée d'un nouveau tick de l'unité de temps choisie. À la différence de la fonction Update() de l'indicateur, la stratégie ne dispose d'aucun paramètre à mettre à jour, la fonction n'étant appelée que lorsqu'un nouveau tick apparaît. Elle n'est jamais appelée pour des données historiques.

Pour accéder aux ticks, nous pouvons utiliser les ensembles instance.bid et instance.ask. Le dernier élément de ces ensembles correspond aux derniers bid et ask.

Ainsi, sur chaque tick, nous devons :

  • rechercher la fin de la bougie à laquelle le tick appartient, s'il s'agit du premier tick reçu ;
  • afficher toutes les alertes et calculer la fin de la nouvelle bougie, si le tick n'appartient pas à la bougie.

Pour conserver la fin de la bougie entre les appels de la fonction Update(), il suffit d'utiliser une variable globale :

local startOfCandle = nil;

function Update()
    local s;

    -- get candle
    s = core.getcandle(instance.parameters.TF, 
                       instance.bid:date(instance.bid:size() - 1),
                       core.host:execute("getTradingDayOffset"), 
                       core.host:execute("getTradingWeekOffset"));

    if startOfCandle == nil then
        startOfCandle = s;
    elseif s > startOfCandle then
        startOfCandle = s;
        -- TBD: Signal
    end
end

Qu'est-ce qui ne va pas avec ce code ? À première vue, rien. Mais examinons-le plus en détail.

Nous avons tout d'abord trois opérations :

  • un appel getcandle assez lourd ;
  • l'appel de l'hôte pour la séance ;
  • l'appel de l'hôte pour la semaine de trading.

Et elles sont toutes exécutées à chaque tick.

Optimisons donc le code.

Tout d'abord, les paramètres de séance et de semaine de trading ne peuvent pas être modifiés à tout moment. Nous ne pouvons les obtenir qu'une seule fois, dans la méthode Prepare() :

local tradingDayOffset, tradingWeekOffset;

function Prepare(onlyName)
    ...
    tradingDayOffset = core.host:execute("getTradingDayOffset");
    tradingWeekOffset = core.host:execute("getTradingWeekOffset");
end

function Update()
    ...
    s = core.getcandle(TF, instance.bid:date(NOW), tradingDayOffset, tradingWeekOffset);
    ...
end

Examinons ensuite plus attentivement la fonction core.getcandle. Elle renvoie à la fois le début et la fin de la bougie (ou le début de la bougie suivante). Nous pouvons donc nous contenter de calculer la fin de chaque bougie une fois et d'attendre le tick de la bougie suivante :

local endOfCandle = nil;

function Update()
    local s, e;

    if endOfCandle == nil then
        s, e = core.getcandle(TF, instance.bid:date(NOW), tradingDayOffset, tradingWeekOffset);
        endOfCandle = e;
    elseif instance.bid:date(NOW) >= endOfCandle then
        s, e = core.getcandle(TF, instance.bid:date(NOW), tradingDayOffset, tradingWeekOffset);
        endOfCandle = e;
        ...
    end
end

Bien mieux. Ajoutons maintenant les alertes et sons au cas où l'utilisateur les demanderait. Utilisez la table terminal pour afficher les alertes. Prêtez attention à l'appel core.host:execute("convertTime"). La date et l'heure renvoyées par les ensembles de cours correspondent toujours au fuseau horaire EST. L'utilisateur peut définir un autre fuseau horaire dans la Station de trading. Aussi, pour que la date de la bougie corresponde à la date affichée dans la station, nous devons convertir le fuseau horaire EST en celui actuellement sélectionné dans la Station de trading.

function Update()
    ...
    elseif instance.bid:date(NOW) >= endOfCandle then
    ...
        if instance.parameters.ShowMessage then
            local message;
            message = string.format("Candle of %s/%s (%s) is started", 
                                     instance.bid:instrument(), TF, 
                                     core.formatDate(core.host:execute("convertTime", core.TZ_EST, core.TZ_TS, s)));
            terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], message, instance.bid:date(NOW));
        end

        if instance.parameters.PlaySound then
            terminal:alertSound(instance.parameters.Sound, instance.parameters.RecurrentSound);
        end

    end
end

Voilà, c'est tout.

Version finale

Veuillez consulter ci-dessous le code source complet de notre stratégie :

function Init()
    strategy:name("Alert New Candle");
    strategy:description("The signal shows alerts when the first tick of a new candle appears");

    strategy.parameters:addGroup("Parameters");
    strategy.parameters:addString("TF", "Time frame of candle", "", "H1");
    strategy.parameters:setFlag("TF", core.FLAG_PERIODS);

    strategy.parameters:addGroup("Alerts");
    strategy.parameters:addBoolean("ShowMessage", "Show message when new candle appears?", "", false);
    strategy.parameters:addBoolean("PlaySound", "Play Sound when new candle appears?", "", true);
    strategy.parameters:addFile("Sound", "Sound file", "", "");
    strategy.parameters:setFlag("Sound", core.FLAG_SOUND);
    strategy.parameters:addBoolean("RecurrentSound", "Recurrent sound?", "", false);
end

local name;
local TF;
local tradingDayOffset, tradingWeekOffset;

function Prepare(onlyName)
    assert(instance.parameters.TF ~= "t1", "Please choose non-tick time frame");

    name = profile:id() .. "(" .. instance.bid:instrument() .. "," .. instance.parameters.TF .. ")";
    instance:name(name);
    if onlyName then
        return ;
    end

    tradingDayOffset = core.host:execute("getTradingDayOffset");
    tradingWeekOffset = core.host:execute("getTradingWeekOffset");
    TF = instance.parameters.TF;
end

local endOfCandle = nil;

function Update()
    local s, e;

    if endOfCandle == nil then
        s, e = core.getcandle(TF, instance.bid:date(NOW), tradingDayOffset, tradingWeekOffset);
        endOfCandle = e;
    elseif instance.bid:date(NOW) >= endOfCandle then
        s, e = core.getcandle(TF, instance.bid:date(NOW), tradingDayOffset, tradingWeekOffset);
        endOfCandle = e;
        if instance.parameters.ShowMessage then
            local message;
            message = string.format("Candle of %s/%s (%s) is started", instance.bid:instrument(), TF, core.formatDate(core.host:execute("convertTime", core.TZ_EST, core.TZ_TS, s)));
            terminal:alertMessage(instance.bid:instrument(), instance.bid[NOW], message, instance.bid:date(NOW));
        end

        if instance.parameters.PlaySound then
            terminal:alertSound(instance.parameters.Sound, instance.parameters.RecurrentSound);
        end
    end
end

Cet article dans d'autres langues

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