簡單信號 - 新蠟燭線提示

From FxCodeBaseWiki
Jump to: navigation, search

任務

讓我們開發一個簡單信號,當我們選擇的時間框架內出現新的蠟燭線時,該信號可以顯示消息或發出提示音。假設屬於一根新蠟燭線的第一個價格點 (tick) 出現時,即代表出現新蠟燭線。

編寫信號

建立檔案

首先,建立一個空檔案:

  • 執行 Lua 編輯器 (Lua Editor)。
  • 按一下 File(檔案)->Strategy(策略)->New(新建)
  • 在精靈表單內,按一下 Cancel(取消),我們將從頭編寫策略!

不要被「策略」這個術語所迷惑。信號是指不進行交易,只顯示提示的策略。

新的策略檔案已建立。現在儲存該檔案。檔案名稱使用策略的簡稱(例如,示例中的移動平均線策略命名為 MA_ADVISOR)。我們的策略可以稱為 NEW_CANDLE。

  • 按一下 File(檔案)->Save As(另存新檔)。因為我們已經建立了策略,編輯器將開啟 SDK 的策略資料夾。如果沒有開啟,請您變更資料夾。在每個新的 Windows 版本中,「檔案開啟/儲存」對話方塊的行為都會發生變化,雖然我們已經盡力應對此情況,但有時仍然不太成功。
  • 在檔案名稱中輸入 NEW_CANDLE.lua,然後按一下 OK(確定)

相關介紹就是這些,檔案已建立。

引入策略

引入策略是指提供所需的所有資訊,以便在策略清單中顯示策略,向使用者請求參數,等等。

所有「引入」資訊必須在策略的 Init() 函數中指定,所以我們來編寫該函數。首先,定義「簡單易懂」的名稱和說明。

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

現在說明必須請求的參數。我們需要以下參數:

要監視的蠟燭線的時間框架

時間框架就是一個代碼,例如,m1 代表 1 分鐘蠟燭線,H1 代表 1 小時蠟燭線,等等。所以,它屬於字串資料。添加一個字串參數:

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

然而,我們不想讓使用者關注上述所有代碼。更好的方法是讓使用者從清單中選擇時間框架。我們可以使用 addStringAlternative() 方法建立參數值清單,此操作與我們對 GMA 指標進行的操作一樣,不過我們可以使用參數標誌。參數標誌是 Indicore 的一個功能,可幫助指標和策略向主應用程式提供關於如何處理參數的線索。我們來看看:

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

現在主應用程式就會知道該參數一定是時間框架的清單。

詢問使用者是否想看到提示。

這是一個簡單的 Yes/No(是/否)參數。這類 Yes/No(是/否)值被稱為布林值,因此建立一個布林參數:

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

詢問使用者是否想聽到提示音。

這也是一個簡單的 Yes/No(是/否)參數。

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

但是對於提示音,我們還必須詢問音效檔案的名稱,以及使用者是否想一直重複播放提示音,直到他/她在交易平台中執行了特定指令。請注意,我們需要再次使用參數標誌,使應用程式知道該檔案是音效檔案。

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

相關介紹就是這些。為了更方便地進行導航,可以添加參數群組,Init() 方法已經就緒:

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

現在,主應用程式知道如何在所有策略的清單中顯示我們的策略,以及當使用者想要執行策略時如何請求參數。

請注意,我們沒有任何商品參數。我們假設任何信號或策略都會應用於一種商品,且僅應用於該商品(不過,它們可以使用任意數量的商品),而且每當該商品產生新的價格點時,信號或策略就會被啟動。因此,主應用程式無論如何都會詢問使用者需要對哪種商品應用策略,我們無需明確指定此類參數。

啟動策略

現在,建立策略的 Prepare() 方法:出現以下情況時,即呼叫該方法:

  • 當策略正在建立時,使用者改變策略的參數。在這種情況下,策略需檢查參數,並設定執行個體的名稱。
  • 啟動策略之前。在這種情況下,策略需要準備所有供將來執行的資料。

讓我們先開始第一個任務。我們必須檢查策略並建立策略名稱。

我們只需要檢查一個參數。使用者不得將 tick(價格點)選作時間框架,否則,每次出現新的價格點時,使用者都會收到提示。可能一秒鐘之內就會出現好多次。

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

現在建立策略執行個體的名稱。建議該名稱包含策略的簡稱、商品名稱和所有重要參數。如果只要求名稱(呼叫 Prepare() 方法的第一種情況),我們必須返回,以免在策略準備開始執行時執行不必要的操作。

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

事實上,此時我們無需執行其他任何操作,只要繼續即可。不過,如果稍後我們發現還需要在此執行某些操作,我們將返回 Prepare() 函數。

更新策略

策略還必須具有 Update() 函數,每當選擇的時間框架內出現新的價格點,就會呼叫該函數。不同於指標的 Update() 函數,策略沒有更新參數,因為只有當一個的價格點出現時,才會呼叫該函數。不會為了任何歷史資料呼叫該函數。

要存取價格點,我們可以使用 instance.bidinstance.ask 集合。這些集合的最後一個元素就是最新的賣出價和買進價。

因此,對於每個價格點,我們必須:

  • 如果它是我們接收到的第一個價格點,找到它所屬的蠟燭線的結束點。
  • 如果價格點不屬於該蠟燭線,顯示所有提示並計算新蠟燭線的結束點。

若要保存在兩次呼叫 Update() 函數之間出現的蠟燭線結束點,只需使用一個全域變數:

local startOfCandle = nil;

function Update()
    local s;

    -- 獲取蠟燭線
    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;
        -- 待定:信號
    end
end

這段程式碼有什麼問題?乍看之下似乎沒有問題。但再仔細看一下這段程式碼。

首先,我們有三項操作:

  • 非常頻繁的 getcandle 呼叫。
  • 向宿主程式呼叫交易日資訊。
  • 向宿主程式呼叫交易週資訊。

所有操作都是對每個價格點執行的。

讓我們優化此程式碼。

首先,交易日和交易週參數在任何時候都無法變更。我們可以在 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

然後,我們再仔細看一下 core.getcandle 函數。它傳回蠟燭線的開始點和結束點(或者下一根蠟燭線的開始點)。因此,我們可以對每根蠟燭線的結束點只進行一次計算,然後等待下一根蠟燭線的價格點:

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

好多了。現在讓我們添加提示和提示音以滿足使用者的這類請求。使用 terminal 表來顯示提示。請注意對 core.host:execute("convertTime") 的呼叫。價格集合傳回的日期和時間總是美國東部標準時間。使用者可以在交易平台中設定另一個時區。因此,為了讓蠟燭線的日期與使用者在交易平台中看到的日期保持一致,我們必須將美國東部標準時間轉換為當前選擇的交易平台時區。

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

相關介紹就是這些。

最終版

請參見以下完整的策略原始程式碼:

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

其他語言版本

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