资源加载中... loading...

商品期货「订单流」系列文章(二):Delta指标

Author: ianzeng123, Created: 2024-08-01 13:11:37, Updated: 2024-08-12 16:57:26

img

上篇文章中,我们详细解释了订单流的概念及其计算原理。基于这一微观指标,我们可以挖掘出一系列动态高效的因子,这些因子能够帮助我们及时辨别多空双方的力量悬殊对比,从而实时识别市场涨跌的转换节奏。通过这种方式,我们不仅能够量化市场的节奏变化,还可以更精准地把握交易时机,制定更为合理的交易策略。此外,这些因子还为我们提供了深入分析市场情绪和行为模式的基础,有助于提高我们的决策效率和盈利能力。我们希望的是,从理解和应用订单流分析出发,可以为交易者提供一种全新的视角,帮助他们在复杂的市场环境中获得竞争优势。

Delta指标介绍

今天我们要介绍根据订单流挖掘出来的第一个指标:Delta值。在固定时间段内,Delta是指该根K线中,不同价格汇总的主动买单总量(左侧),减去主动卖单总量(右侧),去用来衡量主动买卖力量的强弱关系。Delta可以是正也可以是负,极少情况下会出现0。如下图所示,我们将左边买单值进行汇总为(313 + 210 + 469 + 1323 + 173) = 2488,卖单值汇总为(330 + 665 + 1980 + 734) = 3709,两者相减就Delta值-1221。

img

Delta就像多空双方的战力值对比,可以从量化角度辨别多空双方是谁占据主导地位。在通常情况下,如果市场由多头主导,Delta为正值,表示主动买入的资金较多;如果市场由空头主导,Delta为负值,表示主动卖出的资金较多。虽然Delta的概念在数学上看似简单,仅仅是基本的减法运算,但它在实际应用中却具有极其重要的作用。我们利用Delta来衡量市场趋势的强度,并据此分析趋势是否有放缓或转向的可能。而当Delta接近为0的时候,这意味着意味着市场上可能有同等力量的主动性买单和卖单同时介入,显示出市场参与者的活跃程度和市场方向的不确定性。

Delta作为一个动态变化的数值,我们可以用来实时观察市场多空力量转换。一个比较常用的用法是观察连续3根K线的Delta值:在盘整震荡阶段,Delta值可能正负交替;而在蓄势待发的单边趋势准备阶段,Delta值可能连续呈现一致性。当Delta连续三次为正时,表明买方力量逐渐增强,一波上涨的趋势正在酝酿;而当连续三次为负,卖方力量逐步强大,下跌趋势可能即将爆发。尤其是在一波单边趋势逐渐放缓的时候,通过观察Delta值的变化,有可能可以做到及时的方向转换,防止利润的流失。

img

需要注意的是,Delta的数值紧密依赖于选定的时间框架。例如,在较短的时间周期图表(如1分钟K线图)上,Delta值往往较小,这可能是因为短时间内的交易量较少,市场行为可能不那么显著。同时,市场的活跃程度也会影响Delta的大小,在交易较为清淡的时段,Delta值也可能会较小。另外由于Delta本质上是一种估算值,面对市场快速波动时,我们可以将其与其他关键的技术指标结合起来使用。这种综合分析方法可以显著提升我们对交易时机的判断精度,从而做出更为精准的交易决策。

累积Delta指标

Delta指标是固定时间内的多空力量对比,具有一定的片面性;如果我们想预测本交易日内买卖力量强弱对比,我们可以使用累积Delta指标。该指标是累积所有观察时间段内的Delta数值,将所有净增量相加,得到累积Delta指标的总和。

img

相对于Delta指标仅能观察趋势的动态转换,累积Delta值在一定程度上,可以用来预测价格的涨跌。当累积Delta指标为正,表明市场整体上呈现净买入压力,用户更愿意更高价格去主动买入商品,交易者对未来上涨的期望更高;相反情况下,当累积Delta指标为负,显示市场存在净卖出压力,交易者对未来价格走势呈现下跌预测,更可能主动卖出商品。

我们可以对比Delta指标和累积Delta指标和价格的皮尔森相关关系,结果显示,Delta指标和价格走势呈现的相关性较低(几乎毫无相关),而累积Delta和价格走势呈现较高的相关性(大于0.9)。这一结果解释了我们前面的描述,虽然Delta指标可以用来判断关键节点的趋势转换,但是在震荡波动区间,Delta指标和价格的相关性逐渐被稀释;而累积Delta指标表明交易者对趋势的整体把握。交易是有节奏的,但是交易更有目的。如果说Delta指标代表交易的节奏变化,而累积Delta数值可以在很大程度反应交易者真正的意图,我们就以累积Delta指标构建一个策略指标,进行一个量化模型的构建。

img

Delta指标和价格的皮尔森相关关系

img

累积Delta指标和价格的皮尔森相关关系

基于累积Delta指标的量化策略

本策略通过线性回归分析价格与累积delta(买卖差值)之间的关系,以生成交易信号。与传统的均线方法相比,线性回归具有更高的时效性,能够更准确地反映市场动态。策略首先收集价格和成交量数据,计算当前的delta和累积delta值,并基于线性回归预测价格。然后,根据预测价格与实际价格的差值,生成买入或卖出信号。当预测价格高于实际价格且差值超过阈值时,发出买入信号;反之,则发出卖出信号。这种方法利用了价格与累积delta之间的强相关性,在一定程度上克服了均线的滞后性,从而提高了交易决策的准确性。

/*backtest
start: 2024-07-16 21:00:00
end: 2024-07-17 15:00:00
period: 1m
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
mode: 1
args: [["contract","i888"],["validDiff",6],["stopWin",6],["stopLoss",6]]
*/

var deltaList = [];
var preepoch = 0;
var curdelta = 0;
var corList = [];
var difference = null;

// 初始化变量
var deltaList = []; // 存储交易量差异列表
var preepoch = 0; // 前一个时间段
var curdelta = 0; // 当前交易量差异
var corList = []; // 存储相关系数列表
var difference = null; // 存储价格差异

// 创建新期货交易过滤器
var NewFuturesTradeFilter = function(period) {
    var self = {};

    self.pre = null; // 存储前一个 ticker 数据
    self.longsignal = false; // 多头信号
    self.shortsignal = false; // 空头信号
    self.records = []; // 存储交易记录
    self.tradetime = false; // 交易时间标志
    
    // 处理行情数据并计算交易量差异
    self.feed = function(ticker, rData) {
        if (!self.pre) {
            self.pre = ticker; // 如果没有前一个数据,则将当前数据赋值给 pre
        }
        var action = ''; // 初始化交易动作
        if (ticker.Last >= self.pre.Sell) {
            action = 'buy'; // 当前价格高于或等于前一个卖出价,买入信号
        } else if (ticker.Last <= self.pre.Buy) {
            action = 'sell'; // 当前价格低于或等于前一个买入价,卖出信号
        } else {
            if (ticker.Last >= ticker.Sell) {
                action = 'buy'; // 当前价格高于或等于当前卖出价,买入信号
            } else if (ticker.Last <= ticker.Buy) {
                action = 'sell'; // 当前价格低于或等于当前买入价,卖出信号
            } else {
                action = 'both'; // 当前价格在买卖区间内
            }
        }

        // 检查成交量是否减少
        if (ticker.Volume < self.pre.Volume) {
            self.pre.Volume = 0; // 重置前一个成交量
            Log('更新交易日#ff0000'); // 记录交易日更新
            deltaList = []; // 清空 deltaList
        }
        var amount = ticker.Volume - self.pre.Volume; // 计算当前成交量

        // 如果有交易信号且成交量增加
        if (action != '' && amount > 0) {
            var epoch = parseInt(ticker.Time / period) * period; // 计算当前时间段
            
            if(preepoch != 0 && preepoch != epoch){
                self.tradetime = true; // 设置交易时间标志为 true
                deltaList.push(curdelta); // 将当前交易量差异添加到 deltaList

                var total = 0; // 初始化总和
                var cumulativeArray = deltaList.map(num => total += num); // 计算 deltaList 的累积和
                var closeList = rData.map(item => item.Close); // 获取收盘价列表

                closeList.shift(); // 去掉第一个元素,确保长度一致
                
                const minLength = Math.min(cumulativeArray.length, closeList.length); // 确定最小长度
                const trimmedDeltaList = cumulativeArray.slice(-minLength); // 获取最新的 deltaList
                const trimmedCloseList = closeList.slice(-minLength); // 获取最新的收盘价列表

                // 计算皮尔逊相关系数的函数
                function calculatePearsonCorrelation(x, y) {
                    const n = x.length; // 数据点数量
                    const sumX = x.reduce((a, b) => a + b, 0); // x 的总和
                    const sumY = y.reduce((a, b) => a + b, 0); // y 的总和
                    const sumXY = x.reduce((sum, xi, i) => sum + xi * y[i], 0); // x 和 y 的乘积总和
                    const sumX2 = x.reduce((sum, xi) => sum + xi * xi, 0); // x 的平方和
                    const sumY2 = y.reduce((sum, yi) => sum + yi * yi, 0); // y 的平方和

                    const numerator = n * sumXY - sumX * sumY; // 计算分子
                    const denominator = Math.sqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY)); // 计算分母

                    return denominator === 0 ? 0 : numerator / denominator; // 返回相关系数
                }

                // 计算 delta 和收盘价的相关系数
                const correlation = calculatePearsonCorrelation(trimmedDeltaList, trimmedCloseList);
                corList.push(correlation); // 将相关系数添加到 corList

                // 绘制 K 线图
                $.PlotMultRecords("K线", "K线", rData, {layout: 'single', col: 12, height: '600px'});   

                // 绘制 delta 图表
                $.PlotMultLine("Delta", "Delta", total, epoch, {layout: 'single', col: 12, height: '600px'}); 
                
                // 如果相关系数大于 0.4,进行线性回归预测
                if(correlation > 0.4){
                    var nowdeltalist = trimmedDeltaList; // 当前 delta 列表
                    var nowcloselist = trimmedCloseList; // 当前收盘价列表

                    var predelta = nowdeltalist[nowdeltalist.length - 1]; // 上一个 delta 值
                    var preclose = nowcloselist[nowcloselist.length - 1]; // 上一个收盘价

                    nowdeltalist.pop(); // 移除最后一个 delta 值
                    nowcloselist.pop(); // 移除最后一个收盘价

                    if (nowdeltalist.length !== nowcloselist.length) {
                        Log("数据长度不一致"); // 检查数据长度是否一致
                    }

                    // 计算线性回归系数
                    let n = nowdeltalist.length; // 数据点数量
                    let sumX = 0, sumY = 0, sumXY = 0, sumXX = 0;

                    for (let i = 0; i < n; i++) {
                        sumX += nowdeltalist[i]; // 计算 x 的总和
                        sumY += nowcloselist[i]; // 计算 y 的总和
                        sumXY += nowdeltalist[i] * nowcloselist[i]; // 计算 x 和 y 的乘积总和
                        sumXX += nowdeltalist[i] * nowdeltalist[i]; // 计算 x 的平方和
                    }

                    let slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); // 计算斜率
                    let intercept = (sumY - slope * sumX) / n; // 计算截距

                    // 计算预测值
                    let predictedClose = slope * predelta + intercept; // 根据斜率和截距计算预测收盘价

                    // 计算预测值与实际值的差异
                    difference = preclose - predictedClose;

                } else {
                    difference = null; // 若相关系数不满足条件,则差异设为 null
                }

                var pricTick = exchange.SetContractType(contract).PriceTick; // 获取价格最小变动值

                // 判断多头信号
                if(difference && difference > pricTick * validDiff){
                    self.shortsignal = true; // 满足条件,设置空头信号
                } else {
                    self.shortsignal = false; // 不满足条件,重置空头信号
                }

                // 判断空头信号
                if(difference && difference < -pricTick * validDiff){
                    self.longsignal = true; // 满足条件,设置多头信号
                } else {
                    self.longsignal = false; // 不满足条件,重置多头信号
                }

                // 绘制相关系数图表
                $.PlotMultLine("相关系数", "correlation", correlation, epoch, {layout: 'single', col: 12, height: '600px'});  
            } else {
                self.tradetime = false; // 重置交易时间标志
                self.longsignal = false; // 重置多头信号
                self.shortsignal = false; // 重置空头信号
            }

            preepoch = epoch; // 更新前一个时间段

            var bar = null; // 初始化 K 线数据

            // 如果没有记录或者当前时间段大于最后一条记录的时间段
            if (self.records.length == 0 || self.records[self.records.length - 1].time < epoch) {
                bar = {
                    time: epoch, // 当前时间
                    data: {}, // 数据初始化
                    open: ticker.Last, // 开盘价
                    high: ticker.Last, // 最高价
                    low: ticker.Last, // 最低价
                    close: ticker.Last // 收盘价
                };
                self.records.push(bar); // 将 K 线数据添加到记录中
            } else {
                bar = self.records[self.records.length - 1]; // 获取最后一条记录
                bar.high = Math.max(bar.high, ticker.Last); // 更新最高价
                bar.low = Math.min(bar.low, ticker.Last); // 更新最低价
                bar.close = ticker.Last; // 更新收盘价
            }

            // 初始化当前价格数据
            if (typeof bar.data[ticker.Last] === 'undefined') {
                bar.data[ticker.Last] = {
                    buy: 0,
                    sell: 0
                };
            }

            // 更新当前价格的买入和卖出量
            if (action == 'both') {
                bar.data[ticker.Last]['buy'] += amount; // 买入量增加
                bar.data[ticker.Last]['sell'] += amount; // 卖出量增加
            } else {
                bar.data[ticker.Last][action] += amount; // 根据动作增加买入或卖出量
            }

            // 计算当前 K 线的买入和卖出总量
            var sellVol = 0;
            var buyVol = 0;
            for (var i in bar.data) {
                sellVol += bar.data[i].sell; // 计算总卖出量
                buyVol += bar.data[i].buy; // 计算总买入量
            }

            curdelta = buyVol - sellVol; // 计算当前交易量差异
        }
        self.pre = ticker; // 更新前一个 ticker 数据
        return self; // 返回过滤器实例
    };

    return self; // 返回过滤器
};

function main() {
    if (exchange.GetName().indexOf('CTP') === -1) {
        throw "只支持商品期货CTP";
    }
    SetErrorFilter("login|timeout|GetTicker|ready|流控|连接失败|初始|Timeout");
    while (!exchange.IO("status")) {
        Sleep(3000);
        LogStatus("正在等待与交易服务器连接, " + _D());
    }

    var symbolDetail = _C(exchange.SetContractType, contract);
    Log('交割日期:', symbolDetail['StartDelivDate']);
    Log('最小下单量:', symbolDetail['MaxLimitOrderVolume']);
    Log('最小价差:', symbolDetail['PriceTick']);
    Log('一手:', symbolDetail["VolumeMultiple"], '份');
    Log('合约代码:', symbolDetail['InstrumentID']);

    var filt = NewFuturesTradeFilter(60000);

    $.CTA(contract, function(st) {
        var ticker = exchange.GetTicker();
        var r = exchange.GetRecords();
        var priceTick = exchange.SetContractType(contract).PriceTick;

        if (ticker) {
            var obj = filt.feed(ticker, r);

            if (obj && st.position.amount === 0 && obj.longsignal && obj.tradetime) {
                Log('多头开仓#ff0000');
                return 1;
            }

            if (st.position.amount > 0 && r[r.length - 1].Close - st.position.price >= stopWin * priceTick) {
                Log('多头盈利平仓#ff0000');
                return -1;
            }

            if (st.position.amount > 0 && r[r.length - 1].Close - st.position.price < -stopLoss * priceTick) {
                Log('多头亏损平仓#ff0000');
                return -1;
            }

            if (obj && st.position.amount === 0 && obj.shortsignal && obj.tradetime) {
                Log('空头开仓#0000ff');
                return -1;
            }

            if (st.position.amount < 0 && r[r.length - 1].Close - st.position.price <= -stopWin * priceTick) {
                Log('空头盈利平仓#0000ff');
                return 1;
            }

            if (st.position.amount < 0 && r[r.length - 1].Close - st.position.price > stopLoss * priceTick) {
                Log('空头亏损平仓#0000ff');
                return 1;
            }
        }
    });
}

通过使用铁矿石主力合约,使用真实的Tick数据进行回测,策略结果显示具有较好的预测效果,大家可根据自己喜欢的品种,进行策略参数的调试和具体逻辑的完善。

img


更多内容