在指标中使用其他时间框架的价格数据(前一日收盘值指标)

From FxCodeBaseWiki
Jump to: navigation, search

为什么要求使用其他时间框架的数据?

有时候,指标要求使用更长时间框架内的数据,例如,我们可以使用前一日收盘的数据来进行计算。然而,即使可能在应用了指标的价格历史数据中找到此数据,但是…试想一下:要获取前一日的最高价/最低价,就必须载入前一日全天的数据。如果当前的历史数据是记载分钟数据的,最糟糕的情况是我们至少要有 2880 根棒线,才可能找到所需数据(当天的 1440 根分钟棒线和前一日全天的 1440 根分钟棒线)。这对于加载和稍后的计算而言是相当庞大的数量。不过,系统可以获取日数据,我们何不使用它呢?让我们来操作一下。

不过,我们需要小心处理许多要点。好的,让我们开发一个可以在图表上显示前一日收盘价的指标,并详细讨论每一点。

什么是“前一日”?

在指标内处理日期相当简单。日期值是一个数字,即自 1899 年 12 月 30 日午夜起经过的天数。这个格式也称为 Windows Ole Automation 日期格式。该数字的整数部分是天数,小数部分是当天时间在一天中的比例。所以为了查找公历日的午夜日期和时间,我们必须将该值向下取整(去除小数部分)。要将日期转换到 N 日前,我们必须在日期中减去 N。因此如果福汇的交易日使用“公历时钟”(即,交易日从午夜开始),想要在来源中找到前一日的棒线 x 就变得十分简单:math.floor(source:date(x)) - 1.

遗憾的是,福汇的交易日与公历交易日不同步。目前,交易日始于前一日的 17:00(美国东部标准时间),终于今日的 17:00(美国东部标准时间)。对于不同配置,此日期还可能有所不同。幸好,指标核心代码可帮助我们处理特定日期的蜡烛线临界时间的计算问题。使用 core.getcandle 函数,可以传回特定时间和日期所属的特定时间框架蜡烛线的日期和时间。正如您看到的,此函数需要两个参数,这两个参数让我们将函数“调整”至一个特定配置:相对于公历日的交易日偏移和交易周偏移。您必须通过 host:execute("getTradingDayOffset")host:execute("getTradingWeekOffset") 宿主程序调用来获取特定主应用程序和连线的交易日和交易周设定。

如下所示即为能够正确获取前一日日期的函数:

function getYesterday(date)
    local offset;
    local weekoffset;

    offset = core.host:execute("getTradingDayOffset");
    weekoffset = core.host:execute("getTradingWeekOffset");

    local today, yesterday;
    today = core.getcandle("D1", date, offset, weekoffset);
    yesterday = today - 1;
    return yesterday;
end

上面的代码好吗?还不够好。还有一点我们必须处理,即“非交易”周期,在这些时期既无法交易又接收不到价格。事实上,一周之内只有周一至周五存在蜡烛线。没有什么“周六”蜡烛线。因此当我们的分钟属于周一的日蜡烛线时,函数可能会传回一个根本不存在的周日蜡烛线!因此,对于周一的数据,我们必须取上周五的收盘价。

要正确处理这个问题,还有一个有用的函数:core.isnontrading,该函数检查日期是否属于非交易周期(美国东部标准时间周五 17:00 - 美国东部标准时间周日 17:00 点),并传回此非交易周期结束时的日期和时间。因此,为了找到正确的“前一日”蜡烛线,我们必须查明公历上的“前一日”是否进入了非交易周期,并将“前一日”的日期转换为过去的日期以跳过该非交易周期。

让我们稍微修正一下函数:

function getYesterday(date)
    local offset;
    local weekoffset;

    offset = core.host:execute("getTradingDayOffset");
    weekoffset = core.host:execute("getTradingWeekOffset");

    local today, yesterday;
    today = core.getcandle("D1", date, offset, weekoffset);
    yesterday = today - 1;

    local nontrading, nontradingend;
    nontrading, nontradingend = core.isnontrading(yesterday, offset);
    if nontrading then
        -- 如果公历上的前一日是非交易日(美国东部标准时间周五 17:00 - 美国东部标准时间周日 17:00)
        -- 根据非交易周期的结束时间(美国东部标准时间周日 17:00)往回调整三天
        yesterday = nontradingend - 3;
    end

    return yesterday;
end

现在,有了 getYesterday() 函数,我们可以方便地找到需要多少天。最早的一天是 getYesterday(source:date(source:first())),即与最早的可用棒线对应的“前一日”棒线。那最新的呢?最新有两种情况。来源集合既可以是历史数据(即,所载入的到指定日期为止的集合,在图表窗口上不会改变),也可以是“实时数据”(即,所载入的到“当前日期”为止的集合,随着新的棒线出现会继续扩展)。根据这两种情况,“to”(终止)日期既可以是最新可用棒线的“前一日”(getYesterday(source:date(source:size() - 1)),也可以是当前日期的前一日。要指定“当前日期”,我们使用 0 值。

加载数据

使用宿主程序加载数据

现在我们知道需要哪些数据了。下一步就是获取此数据。汇图宝提供了一个宿主程序 (host) 接口,让您执行针对应用程序的函数。其中一个函数便是“getHistory”。通过调用该函数,汇图宝可以加载采用指定时间框架的指定商品在指定时间范围内的数据。请参见 host:execute(“getHistory”, …) 了解详细信息。

local days;     -- 保存日数据的变量

-- 该函数加载合适的日历史数据
function loadData()
    if source:size() < source:first() then
        return ;
    end

    local from, to;
    -- 以来源中第一根可用棒线
    -- 的正确前一日为起始日期加载数据
    from = getYesterday(source:date(source:first()));
    -- 如果来源数据截止至“当前日期”,加载截止至“当前日期”的数据,或
    -- 加载截止至集合中
    -- 最新日期的数据。
    if source:isAlive() then
        to = 0;
    else
        to = getYesterday(source:date(source:size()  1));
    end
    days = host:execute("getHistory", 1, source:instrument(), "D1", from, to, source:isBid());
end

调用“getHistory”后,汇图宝才开始加载数据,这一点很重要。这将花一点时间,不过汇图宝不会等到数据全部加载完毕,从而避免指标挂起。数据加载完毕后,汇图宝将通过调用 AsyncOperationFinished 函数向指标发出通知。此外,汇图宝不关注指标状态,并将继续调用要更新的指标。

所以我们要做两点。

当数据正在加载时,防止指标更新

首先,当数据正在加载时,我们必须防止指标更新。

为防止指标被重复计算,当数据正在加载时,我们可以从 update 函数返回。只要添加“loading”(正在加载)全局变量,所有函数都会“知道”数据正在加载。

local days;         -- 日来源数据
local loading;      -- “正在加载日数据”标志

function Prepare()
    ...
    days = nil;
    loading = false;
    ...
end

function Update(period, mode)
    if loading then 
        -- 如果正在加载数据,不执行任何操作
        return ;
    end

    if days == nil then
        loadData();
        return ;
    end
    ...
end        

function AsyncOperationFinished(cookie, success, error)
    if cookie == 1 then
        assert(success, error);
        loading = false;
    end
    return 0;
end

function loadData()
    if loading then
        return ;
    end

    local from, to;

    -- 以来源中第一根可用棒线
    -- 的正确前一日为起始日期加载数据
    from = getYesterday(source:date(source:first()));
    -- 如果来源数据截止至“当前日期”,加载截止至“当前日期”的数据,或
    -- 加载截止至集合中
    -- 最新日期的数据。
    if source:isAlive() then
        to = 0;
    else
        to = getYesterday(source:date[source:size() - 1]);
    end
    loading = true;
    days = host:execute("getHistory", 1, source:instrument(), "D1", from, to, source:isBid());
end

加载数据后强制更新

现在,当数据加载完毕后,我们就需要强制更新指标。要强制重新计算指定周期内的指标,可使用 instance:updateFrom() 函数。此外,当 AsyncOperationFinished 传回 core.ASYNC_REDRAW 值时,汇图宝会在图表窗口内重新绘制指标。

既然我们还未计算任何数据,我们必须从最早的数据开始更新我们的指标。只要稍微修正 AsyncOperationFinished

function AsyncOperationFinished(cookie, success, error)
    if cookie == 1 then
        assert(success, error);
        loading = false;
        instance:updateFrom(source:first());
        return core.ASYNC_REDRAW;
    end
    return 0;
end

获取数据

几乎快完成了。最后一个问题是,如何获取数据。我们可以计算某日的日期。当然,我们可以循环运行日历史数据并找到该日期,但这要花很多时间,不是吗?然而,有一个函数可以通过 log2(n) 运算查找日期 - core.findDate()

因此我们可以建立指标的第一个版本。让我们来试一下。

function Init()
    indicator:name("Show Yesterday Close");
    indicator:description("The indicator shows yesterday's close value on the chart");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addColor("clr", "Indicator Line Color", "", core.rgb(255, 255, 0));
end

local source;       -- 指标来源数据
local first;        -- 第一个可用的来源数据
local days;         -- 日来源数据
local loading;      -- “正在加载日数据”标志
local loadedFrom;   -- 所加载日数据的起始日期
local SYC;          -- 前一日收盘结果

local host;
local offset;
local weekoffset;


function Prepare()
    local name;

    name = profile:id() .."(" .. instance.source:name() .. ")";
    instance:name(name);

    source = instance.source;
    first = source:first();
    loading = false;
    days = nil;

    host = core.host;
    offset = host:execute("getTradingDayOffset");
    weekoffset = host:execute("getTradingWeekOffset");

    SYC = instance:addStream("SYC", core.Line, name, "SYC", instance.parameters.clr, first);
end

function Update(period, mode)
    if period < first then
        return ;
    end
    if loading then
        return ;
    end

    -- 如果日数据为空,加载数据
    if days == nil then
        SYC:setBookmark(1, source:first());
        loadData();
        return ;
    end

    -- 尝试在日数据集合中找到值
    local yesterday;
    yesterday = getYesterday(source:date(period));
    if yesterday < loadedFrom then
        SYC:setBookmark(1, period);
        loadData();
        return ;
    end

    local i;
    i = core.findDate(days, yesterday, false);
    if i >= 0 then
        SYC[period] = days.close[i];
    end
end

function AsyncOperationFinished(cookie, success, error)
    if cookie == 1 then
        assert(success, error);
        loading = false;
        local i = SYC:getBookmark(1);
        if i < source:first() then
            i = source:first();
        end
        instance:updateFrom(i);
        return core.ASYNC_REDRAW;
    end
    return 0;
end

-- 该函数加载合适的日历史数据
function loadData()
    if source:size() < source:first() then
        return ;
    end

    if loading then
        return ;
    end

    local from, to;

    if days == nil then
        -- 数据加载初始化

        -- 以来源中第一根可用棒线
        -- 的正确前一日为起始日期加载数据
        from = getYesterday(source:date(source:first()));
        -- 如果来源数据截止至“当前日期”,加载截止至“当前日期”的数据,或
        -- 加载截止至集合中
        -- 最新日期的数据。
        if source:isAlive() then
            to = 0;
        else
            to = getYesterday(source:date(source:size() - 1));
        end
        loading = true;
        loadedFrom = from;
        days = host:execute("getHistory", 1, source:instrument(), "D1", from, to, source:isBid());
    else
        from = getYesterday(source:date(source:first()));
        to = days:date(0);
        loading = true;
        loadedFrom = from;
        host:execute("extendHistory", 1, days, from, to);
    end
end

-- 传回指定日期的
-- “前一日”的开始点
function getYesterday(date)
    local today, yesterday;

    -- 获取今日蜡烛线的开始点,并向上舍入到秒
    today = core.getcandle("D1", date, offset, weekoffset);
    today = math.floor(today * 86400 + 0.5) / 86400;

    -- 获取公历上的前一日
    yesterday = today - 1;

    local nontrading, nontradingend;
    nontrading, nontradingend = core.isnontrading(yesterday, offset);
    if nontrading then
        -- 如果公历上的前一日是非交易日(美国东部标准时间周五 17:00 - 美国东部标准时间周日 17:00)
        -- 根据非交易周期的结束时间(美国东部标准时间周日 17:00)往回调整三天
        yesterday = nontradingend - 3;
    end
    return yesterday;
end

现在,将指标应用到图表上。

Yesterdayclose img1.png

真的可以运作!!!不过,等一等,当我们试着向过去加载更多数据时,它很明显地停止运作了!

Yesterdayclose img2.png

如何处理回滚

发生了什么?好的,请记住,我们加载了从最早的已有蜡烛线的前一日开始的数据。当我们开始 将我们的图表回滚时,更多数据被加载了。但是谁更新了我们的日棒线集合?当然,没有人更新。所以,当来源集合扩展至过去时, 我们必须强制汇图宝加载新数据。好消息是我们还有一个很棒的宿主程序接口调用: host:execute(“extendHistory”), 它可以帮助我们将更多的数据加载到已经加载完毕的历史数据,而不是再次载入整个历史数据。因为我们可以只向过去扩展 最初的来源历史数据,这样很容易就可以检查是否加载了新数据。当加载了新数据时,我们要找的“前一日”值 比我们之前加载日历史数据的起始日期要早。

所以,我们要对代码作出如下修正:

  1. 保留我们之前加载日历史数据的“from”(起始)日期。
  2. 如果日数据集合已经存在,就应该扩展该集合,而不是加载较早的数据。

因此添加新的 loadedFrom 全局变量,并更改 UpdateloadData 函数。

以下是新的指标版本:

function Init()
    indicator:name("Show Yesterday Close");
    indicator:description("The indicator shows yesterday's close value on the chart");
    indicator:requiredSource(core.Tick);
    indicator:type(core.Indicator);

    indicator.parameters:addColor("clr", "Indicator Line Color", "", core.rgb(255, 255, 0));
end

local source;       -- 指标来源数据
local first;        -- 第一个可用的来源数据
local days;         -- 日来源数据
local loading;      -- “正在加载日数据”标志
local loadedFrom;   -- 我们已经加载的最早的前一日棒线
local SYC;          -- 前一日收盘结果

local host;
local offset;
local weekoffset;

function Prepare()
    local name;

    name = profile:id() .."(" .. instance.source:name() .. ")";
    instance:name(name);

    source = instance.source;
    first = source:first();
    loading = false;
    days = nil;

    host = core.host;
    offset = host:execute("getTradingDayOffset");
    weekoffset = host:execute("getTradingWeekOffset");
    SYC = instance:addStream("SYC", core.Line, name, "SYC", instance.parameters.clr, first);
end

function Update(period, mode)
    if period < first then
        return ;
    end
    if loading then
        return ;
    end

    -- 如果日数据为空,加载数据
    if days == nil then
        loadData();
        return ;
    end

    -- 尝试在日数据集合中找到值
    local yesterday;
    yesterday = getYesterday(source:date(period));

    -- 检查是否尚未加载此根日棒线
    if yesterday < loadedFrom then
        loadData();
        return ;
    end

    -- 找到日数据
    local i;
    i = core.findDate(days, yesterday, false);
    if i >= 0 then
        SYC[period] = days.close[i];
    end
end

function AsyncOperationFinished(cookie, success, error)
    if cookie == 1 then
        assert(success, error);
        loading = false;
        instance:updateFrom(source:first());
        return core.ASYNC_REDRAW;
    end
    return 0;
end

-- 该函数加载合适的日历史数据
function loadData()
    if source:size() < source:first() then
        return ;
    end

    if loading then
        return ;
    end

    local from, to;

    if days == nil then
        -- 数据加载初始化

        -- 以来源中第一根可用棒线
        -- 的正确前一日为起始日期加载数据
        from = getYesterday(source:date(source:first()));
        -- 如果来源数据截止至“当前日期”,加载截止至“当前日期”的数据,或
        -- 加载截止至集合中
        -- 最新日期的数据。
        if source:isAlive() then
            to = 0;
        else
            to = getYesterday(source:date(source:size() - 1));
        end
        loading = true;
        loadedFrom = from;
        days = host:execute("getHistory", 1, source:instrument(), "D1", from, to, source:isBid());
    else
        from = getYesterday(source:date(source:first()));
        to = days:date(0);
        loading = true;
        loadedFrom = from;
        host:execute("extendHistory", 1, days, from, to);
    end
end

-- 传回指定日期的
-- “前一日”的开始点
function getYesterday(date)
    local today, yesterday;

    -- 获取今日蜡烛线的开始点,并向上舍入到秒
    today = core.getcandle("D1", date, offset, weekoffset);
    today = math.floor(today * 86400 + 0.5) / 86400;

    -- 获取公历上的前一日
    yesterday = today - 1;

    local nontrading, nontradingend;
    nontrading, nontradingend = core.isnontrading(yesterday, offset);
    if nontrading then
        -- 如果公历上的前一日是非交易日(美国东部标准时间周五 17:00 - 美国东部标准时间周日 17:00)
        -- 根据非交易周期的结束时间(美国东部标准时间周日 17:00)往回调整三天
        yesterday = nontradingend - 3;
    end
    return yesterday;
end

安装指标并尝试运行。太棒了,现在,当上一版在回滚期间停止运作时,新版本还是能够正常运作!恭喜您!

Yesterdayclose img3.png

说明

如何使用 SDK 调试器调试这类指标?

首先,您必须准备数据。您可以使用汇图宝保存价格数据文件。在两个方案(例如,30 分钟和 1 日)中都打开某种商品,然后以 Indicore SDK 数据格式保存两者的历史数据(文档->输出至 Indicore…)。您也可以在 SDK 调试器中,使用报价管理器服务器自动下载数据(Tools[工具]->Load Quotes[加载报价])。

当您开始运行指标时,请在指标参数内选择时间框架较小的历史数据。同时请您勾选指标参数内的“Predeliver Data”(历史数据)复选框,这样调试器就能像汇图宝一样,向指标显示所有可用历史数据。如果没有选择此选项,调试器仅会显示截止至当前模拟的棒线的历史数据。当指标执行“getHistory”调用时,即会出现数据提示。在该提示中选择先前在汇图宝中保存的日数据或使用报价管理器服务器中的数据。

如何处理 1 日以外的时间框架?

为了使示例指标更简单,我使用了 1 日时间框架。不过,你可能想用其他时间框架的数据来操作,例如,使用 1 小时的收盘价来代替日收盘价。为此,您可以:

  • 让用户选择时间框架。将字符串参数添加至指标参数列表,然后对该参数设定时间框架选择器标志。这样的话,在 Prepare 函数中,对应参数将包含用户选择的时间框架的代码。
function Init()
    ...
    indicator.parameters:addString("TF", "Timeframe", "", "m1");
    indicator.parameters:setFlag("TF", core.FLAG_PERIODS);
    ...
end
  • 计算以“日”为单位的指定时间框架的蜡烛线长度。使用 [c]core.getcandle()[/c] 函数来获取任意蜡烛线的起始日期以及下一根蜡烛线的起始日期。这两个值的差值便是以日为单位的蜡烛线长度。为避免舍入误差,我强烈建议用秒代替日来计算所有日期。
local candleLength;  -- 以秒为单位的蜡烛线长度

function Prepare()
    ...
    offset = host:execute("getTradingDayOffset");
    weekoffset = host:execute("getTradingWeekOffset");
    ...
    local thisCandle, nextCandle;
    thisCandle, nextChandle = core.getcandle(instance.parameters.TF, core.now(), offset, weekoffset);
    candleLength = math.floor((nextCandle - thisCandle) * 86400 + 0.5);
    ...
end
  • 在您的“getPreviousBar”函数中使用先前计算的蜡烛线长度来代替 1 日值。请注意,尽管蜡烛线的长度可能不同,非交易周期仍然以日计算。所以,处理的时候务必小心。
function getPreviousBar(date)
    local curr, prev;

    -- 获取今日蜡烛线的开始点,并向上舍入到秒
    curr = core.getcandle(instance.parameters.TF, date, offset, weekoffset);
    today = math.floor(today * 86400 + 0.5);

    -- 获取公历上的前一日
    prev = (curr  candleLength) / 86400;

    local nontrading, nontradingend;
    nontrading, nontradingend = core.isnontrading(prev, offset);
    if nontrading then
        prev = prev - 2;
        prev = math.floor(prev * 86400 + 0.5);

        -- 获取公历上的前一日
        prev = (prev  candleLength) / 86400;
    end
    return prev;
end


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