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

商品期货中‘盈利加仓,亏损减仓’策略示例

Author: ianzeng123, Created: 2024-07-18 16:51:14, Updated: 2024-07-22 16:59:34

img

在期货交易中,如何在市场的波动中获取最大的利润并降低风险是每个交易者都关心的问题。‘盈利加仓,亏损减仓’策略正是为了解决这一问题而设计的。本文将详细介绍这一策略的含义、重要性以及在实际交易中的实现方式,重点说明加仓和减仓判断的标准以及所使用的指标。

一些指标的好坏确实存在一定的疑问,但是我们结合量化技术,通过“盈利加仓,亏损减仓”更好地抓住趋势,在即使胜率不高的情况下,努力实现一个较好的盈亏比,从而获取长远的收益。

引言

含义

“盈利加仓,亏损减仓”策略的核心思想是:当交易出现盈利时,逐步增加仓位以放大盈利;当交易出现亏损时,逐步减少仓位以控制损失。具体而言,当市场价格按照预期方向运行并产生盈利时,通过加仓来增加持仓量,从而在市场持续有利时获取更高的利润;而当市场价格逆向运行并产生亏损时,通过减仓来减少持仓量,从而在市场不利时减少损失。

重要性

  1. 风险控制:在交易过程中,风险管理至关重要。该策略通过在亏损时及时减仓,可以有效控制风险,避免因单一头寸的巨大亏损而导致的整体资金损失。
  2. 利润最大化:通过在盈利时逐步加仓,交易者可以充分利用市场的有利走势,放大盈利。这样不仅提高了资金的使用效率,还增加了交易策略的收益率。
  3. 提高胜率和盈亏比:该策略能够在减少亏损的同时增加盈利,显著提高交易的胜率和盈亏比,从而在长期交易中实现稳健的资金增长。

策略的实现

策略概述

本策略在开仓后,根据市场走势进行加仓和减仓操作,具体的实现细节包括开仓、加仓、减仓、平仓和移仓等操作。以下是策略的具体实现步骤:

* 初始化

在策略初始化时,设定了各种参数和变量,例如开仓次数、累计加仓次数、本轮加仓次数、成功次数、失败次数、累计利润、累计损失、持仓品种、持仓数量、持仓价格、持仓利润、持仓方向、最新开仓价格、胜率、盈亏比、未开仓金额、减仓次数等。通过这些变量,可以对交易过程进行全面的跟踪和管理。

* 开仓操作

当出现买入或卖出信号时,策略会计算可开仓的手数,并执行开仓操作。如果成功开仓,则记录开仓价格和开仓时间等信息。

* 加仓操作

在持仓过程中,当市场价格按照预期方向运行并产生盈利时,策略会根据设定的加仓条件进行加仓操作。具体而言,当最新加仓价格与当前市场价格之间的价差超过一定阈值,并且当前持仓产生盈利时,策略会判断是否符合加仓条件。如果符合,则执行加仓操作,并更新相关变量。

* 减仓操作

当市场价格逆向运行并产生亏损时,策略会根据设定的减仓条件进行减仓操作。具体而言,当最新加仓价格与当前市场价格之间的价差超过一定阈值,并且当前持仓产生亏损时,策略会判断是否符合减仓条件。如果符合,则执行减仓操作,并更新相关变量。减仓手数会根据减仓次数动态调整,以确保减仓过程的合理性和有效性。

* 平仓操作

当持仓达到设定的平仓条件时,策略会执行平仓操作。例如,当出现相反的交易信号时,策略会判断是否符合平仓条件。如果符合,则执行平仓操作,并记录平仓后的资金情况和利润情况。

* 移仓操作

在某些情况下,策略需要将持仓从一个合约移到另一个合约中。移仓操作能够确保策略在不同合约之间的顺利切换,以应对市场的变化。

* 策略评价指标

在策略执行过程中,策略会实时记录各种交易数据,并对策略的表现进行评价。例如,通过开仓次数、成功次数、累计利润、失败次数、累计亏损、胜率和盈亏比等指标,可以全面评估策略的效果。

具体代码实现

以下是策略的具体代码实现,展示了策略的各个关键步骤:

/*backtest
start: 2024-01-03 09:00:00
end: 2024-07-16 14:01:00
period: 1h
basePeriod: 15m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
args: [["IncSpace",0.25],["MinSpace",0.8],["MaxLots",3],["ATRPeriod",30]]
*/


var _q = $.NewTaskQueue();
var openNumber = 0 //开仓次数
var cumaddNumber = 0 //累计加仓次数
var curaddNumber = 0 //本轮加仓次数
var winNumber = 0 //成功次数
var lossNumber = 0 //失败次数
var cumProfit = 0 //累计利润
var cumLoss = 0 //累计损失
var holdSymbol = '' //持仓品种
var holdAmount = 0 //持仓数量
var holdPrice = 0 //持仓价格
var holdProfit = 0 //持仓利润
var holdDir = '' //持仓方向
var openPrice = 0 //最新开仓价格,判断加仓
var winRate = 0 //胜率
var plRatio = 0 //盈亏比
var InitBalance = 0 //未开仓金额
var curminNumber  = 0 //减仓次数

function main(){
    SetErrorFilter("login|ready|流控|连接失败|初始|Timeout");
    Log("风险系数:", RiskRatio, "N值周期:", ATRPeriod, "加仓系数:", IncSpace, "单品种最多开仓:", MaxLots, "次");
    var pretime = 0
    var checklock = true
    while (true) {
        if(exchange.IO("status")) {
            var symbolDetail = _C(exchange.SetContractType, Instruments)
            var record = exchange.GetRecords()

            var lastPrice = record[record.length - 1].Close
            var shortMean = TA.MA(record, shortPeriod)
            var longMean = TA.MA(record, longPeriod)
            var longSignal = shortMean[shortMean.length - 2] > longMean[longMean.length - 2]
            var shortSignal = shortMean[shortMean.length - 2] < longMean[longMean.length - 2]

            // 检查主力合约
            var DRecords = exchange.GetRecords(PERIOD_D1)
            var curTime = DRecords[DRecords.length - 1].Time
            var mainSymbol = symbolDetail.InstrumentID;

            var curPos = exchange.GetPosition()
            var atr = TA.ATR(DRecords, ATRPeriod)
            var N = atr[atr.length - 1]

            // 开仓/加仓手数计算
            var account = _q.GetAccount(exchange);
            var availmoney = account.Balance * (1 - KeepRatio/100)
            var unit = parseInt((availmoney) * (RiskRatio / 100) / N / symbolDetail.VolumeMultiple); //风险制约手数
            var canOpen = parseInt((availmoney) / (longSignal ? symbolDetail.LongMarginRatio : symbolDetail.ShortMarginRatio) / (lastPrice * 1.2) / symbolDetail.VolumeMultiple); //金额制约手数
            unit = Math.min(unit, canOpen);

            if (unit < symbolDetail.MinLimitOrderVolume) {
                Log("可开 " + unit + " 手 无法开仓, " + (canOpen >= symbolDetail.MinLimitOrderVolume ? "风控触发" : "资金限制") + "可用: " + account.Balance);
                return
            }

            if(checklock){
                var initAccount = _q.GetAccount(exchange);
                var initMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
                InitBalance = initAccount.Balance + initMargin;
            }

            // 开仓
            if(curPos && curPos.length == 0 && (longSignal || shortSignal)){
                
                _q.pushTask(exchange, mainSymbol, (longSignal ? "buy" : "sell"), unit, function(task, ret) {
                    Log('现有仓位:',curPos.length, "做多信号:", longSignal, "做空信号:", shortSignal)
                    if(ret){
                        checklock = false
                        openNumber += 1
                        openPrice = ret.price
                    }
                    
                });
            }

            // 移仓+平仓+加仓+减仓
            if(curPos && curPos.length > 0){

                holdPrice = curPos[0].Price
                holdAmount = curPos[0].Amount
                holdProfit = (curPos[0].Type % 2) == 0 ? (lastPrice - holdPrice) * holdAmount * symbolDetail.VolumeMultiple : (holdPrice - lastPrice) * holdAmount * symbolDetail.VolumeMultiple
                holdDir = curPos[0].Type % 2 == 0 ? "多" : "空"
                holdSymbol = curPos[0].ContractType

                // 移仓
                var moveSignal = pretime != curTime && holdSymbol != mainSymbol
                
                if (moveSignal) {
                    _q.pushTask(exchange, holdSymbol, "coverall", 0, function(task, ret) {
                        if (ret) {
                            Log(holdSymbol, "移仓平仓成功 #ff0000");
                            pretime = curTime
                            curaddNumber = 0 //本轮加仓次数归零
                            var afterAccount = _q.GetAccount(exchange);
                            var afterMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
                            var afterBalance = afterAccount.Balance + afterMargin;

                            var curprofit = afterBalance - InitBalance

                            if(curprofit > 0){
                                cumProfit += curprofit
                                winNumber += 1
                            }else{
                                cumLoss += curprofit
                                lossNumber += 1
                            }

                            curaddNumber = 0
                            curminNumber = 0
                            holdSymbol = '' 
                            holdAmount = 0 
                            holdPrice = 0 
                            holdProfit = 0 
                            holdDir = '' 
                            openPrice = 0 
                            checklock = true
                        }

                        return
                    })
                }


                //平仓
                var coverLongSignal = shortSignal && holdDir == '多'
                var coverShortSignal = longSignal && holdDir == '空'

                if(coverLongSignal || coverShortSignal){
                    _q.pushTask(exchange, mainSymbol, "coverall", 0, function(task, ret) {
                        if(ret){
                            var afterAccount = _q.GetAccount(exchange);
                            var afterMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
                            var afterBalance = afterAccount.Balance + afterMargin;

                            var curprofit = afterBalance - InitBalance

                            Log('平仓后资金:', afterAccount.Balance, afterMargin, afterBalance)
                            Log('平仓后利润:' + curprofit, (curprofit > 0 ? "#FF0000" : "#00FF00"));

                            if(curprofit > 0){
                                cumProfit += curprofit
                                winNumber += 1
                            }else{
                                cumLoss += curprofit
                                lossNumber += 1
                            }
                            curaddNumber = 0
                            holdSymbol = '' 
                            holdAmount = 0 
                            holdPrice = 0 
                            holdProfit = 0 
                            holdDir = '' 
                            openPrice = 0 
                            curminNumber = 0
                            checklock = true
                        }

                        return
                    })
                }

                //加仓 
                var spread = holdDir == '多' ? (openPrice - lastPrice) : (lastPrice - openPrice) //最新加仓价格
                var addInterval = IncSpace * N

                var addLongSignal = -spread > addInterval && holdProfit > 0 && longSignal && curaddNumber < MaxLots && holdDir == '多' && holdAmount > 0
                var addShortSignal = -spread > addInterval && holdProfit > 0 && shortSignal && cumaddNumber < MaxLots && holdDir == '空' && holdAmount > 0

                if(addLongSignal){
                    _q.pushTask(exchange, mainSymbol, "buy", unit, function(task, ret) {
                        Log('满足加仓条件,进行多头方向加仓#ff0000')
                        if(ret){
                            openPrice = ret.price 
                            curaddNumber += 1
                            cumaddNumber += 1
                        }
                    });
                }

                if(addShortSignal){
                    _q.pushTask(exchange, mainSymbol, "sell", unit, function(task, ret) {
                        Log('满足加仓条件,进行空头方向加仓#ff0000')
                        if(ret){
                            openPrice = ret.price 
                            curaddNumber += 1
                            cumaddNumber += 1
                        }
                    });
                }

                // 减仓
                var minspread = holdDir == '多' ? (openPrice - lastPrice) : (lastPrice - openPrice) // 最新加仓价格
                var minusInterval = MinSpace * N * (1 + 0.25 * curminNumber) // 减仓间隔动态调整

                var minLongSignal = minspread > minusInterval && holdAmount > 0 && holdDir == '多' 
                var minShortSignal = minspread > minusInterval && holdAmount > 0 && holdDir == '空' 

                // 动态调整减仓手数
                var minusUnit = Math.max(1, parseInt(holdAmount * 0.5 )) // 根据减仓次数动态调整减仓手数

                if(minLongSignal || minShortSignal){
                    var action = minLongSignal ? "closebuy" : "closesell";
                    _q.pushTask(exchange, mainSymbol, action, minusUnit, function(task, ret) {
                        if(ret){
                            Log('满足减仓条件,进行减仓#00ff00')
                            curminNumber += 1
                            if(holdAmount == minusUnit){
                                Log('清仓完毕')
                                var afterAccount = _q.GetAccount(exchange);
                                var afterMargin = JSON.parse(exchange.GetRawJSON()).CurrMargin;
                                var afterBalance = afterAccount.Balance + afterMargin;

                                var curprofit = afterBalance - InitBalance

                                Log('平仓后资金:', afterAccount.Balance, afterMargin, afterBalance)
                                Log('平仓后利润:' + curprofit, (curprofit > 0 ? "#FF0000" : "#00FF00"));

                                if(curprofit > 0){
                                    cumProfit += curprofit
                                    winNumber += 1
                                }else{
                                    cumLoss += curprofit
                                    lossNumber += 1
                                }
                                curaddNumber = 0
                                curminNumber = 0
                                holdSymbol = '' 
                                holdAmount = 0 
                                holdPrice = 0 
                                holdProfit = 0 
                                holdDir = '' 
                                openPrice = 0 
                                checklock = true
                            }
                        }

                        Sleep(1000 * 60 * 15)
                    });
                }

            }

            var tblStatus = {
                type: "table",
                title: "持仓信息",
                cols: ["合约名称", "持仓方向", "持仓均价", "持仓数量", "持仓盈亏", "本轮加仓次数"],
                rows: []
            };

            var tblStrategy = {
                type: "table",
                title: "策略评价",
                cols: ["开仓次数", "成功次数", "累计利润", "失败次数", "累计亏损", "胜率", "盈亏比"],
                rows: []
            };

            if(openNumber){
                winRate = winNumber / openNumber
            }

            winRate = openNumber ? winNumber / openNumber : 0

            if(cumLoss == 0 && cumProfit != 0 ) plRatio = 1
            if(cumLoss != 0 && cumProfit == 0) plRatio = -1
            if(cumProfit && cumLoss) plRatio = cumProfit / cumLoss

            tblStatus.rows.push([holdSymbol, holdDir, holdPrice, holdAmount, holdProfit, curaddNumber]);
            tblStrategy.rows.push([openNumber, winNumber, cumProfit, lossNumber, cumLoss, winRate, Math.abs(plRatio)]);
            var now = new Date();

            lastStatus = '`' + JSON.stringify([tblStatus, tblStrategy]) + '`\n' + '`' + ' 当前时间: ' + _D() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'][now.getDay()] + ", 交易任务: " + _q.size();
            LogStatus(lastStatus);
            _q.poll();

            Sleep(LoopInterval * 1000);
        }
    }
}

关键实现细节

指标的使用

  1. 均线(MA):策略使用了短期和长期均线(shortMeanlongMean)来生成交易信号。当短期均线向上穿过长期均线时,产生做多信号;当短期均线向下穿过长期均线时,产生做空信号。
  2. 真实波动幅度(ATR):策略使用了平均真实波动幅度(ATR)来计算波动率(N)。ATR用于确定开仓和加仓的价格间隔。
  3. 合约信息参数:策略通过获取合约的详细信息(symbolDetail),包括保证金比例、合约乘数等,来计算风险控制和资金管理的参数。

加仓判断标准

  1. 价差条件:计算持仓方向的最新加仓价格与当前市场价格之间的价差(spread)。持多单时,spread = openPrice - lastPrice;持空单时,spread = lastPrice - openPrice
  2. 盈利条件:只有在当前持仓产生盈利的情况下才进行加仓。
  3. 信号条件:加仓时需满足当前市场信号与持仓方向一致。例如,持多单时需满足做多信号,持空单时需满足做空信号。
  4. 次数限制:加仓次数不超过设定的最大加仓次数(MaxLots)。
  5. 加仓间隔:加仓间隔由波动率(N)和加仓系数(IncSpace)决定。

具体代码实现:

// 加仓
var spread = holdDir == '多' ? (openPrice - lastPrice) : (lastPrice - openPrice) // 最新加仓价格
var addInterval = IncSpace * N

var addLongSignal = -spread > addInterval && holdProfit > 0 && longSignal && curaddNumber < MaxLots && holdDir == '多' && holdAmount > 0
var addShortSignal = -spread > addInterval && holdProfit > 0 && shortSignal && curaddNumber < MaxLots && holdDir == '空' && holdAmount > 0

if(addLongSignal){
    _q.pushTask(exchange, mainSymbol, "buy", unit, function(task, ret) {
        Log('满足加仓条件,进行多头方向加仓#ff0000')
        if(ret){
            openPrice = ret.price 
            curaddNumber += 1
            cumaddNumber += 1
        }
    });
}

if(addShortSignal){
    _q.pushTask(exchange, mainSymbol, "sell", unit, function(task, ret) {
        Log('满足加仓条件,进行空头方向加仓#ff0000')
        if(ret){
            openPrice = ret.price 
            curaddNumber += 1
            cumaddNumber += 1
        }
    });
}

减仓判断标准

  1. 价差条件:计算持仓方向的最新加仓价格与当前市场价格之间的价差(minspread)。持多单时,minspread = openPrice - lastPrice;持空单时,minspread = lastPrice - openPrice
  2. 减仓间隔:减仓间隔由波动率(N)和减仓系数(MinSpace)决定,并随减仓次数(curminNumber)动态调整。
  3. 减仓手数:根据减仓次数动态调整减仓手数,确保减仓过程的合理性和有效性。

具体代码实现:

// 减仓
var minspread = holdDir == '多' ? (openPrice - lastPrice) : (lastPrice - openPrice) // 最新加仓价格
var minusInterval = MinSpace * N * (1 + 0.25 * curminNumber) // 减仓间隔动态调整

var minLongSignal = minspread > minusInterval && holdAmount > 0 && holdDir == '多' 
var minShortSignal = minspread > minusInterval && holdAmount > 0 && holdDir == '空' 

// 动态调整减仓手数
var minusUnit = Math.max(1, parseInt(holdAmount * 0.5 )) // 根据

减仓次数调整减仓手数

if(minLongSignal){
    _q.pushTask(exchange, mainSymbol, "sell", minusUnit, function(task, ret) {
        Log('满足减仓条件,进行减仓#ff0000')
        if(ret){
            curminNumber += 1
            cumminNumber += 1
        }
    });
}

if(minShortSignal){
    _q.pushTask(exchange, mainSymbol, "buy", minusUnit, function(task, ret) {
        Log('满足减仓条件,进行减仓#ff0000')
        if(ret){
            curminNumber += 1
            cumminNumber += 1
        }
    });
}

策略评价

策略的表现通过以下指标进行评价:

  • 胜率:成功交易次数占总交易次数的百分比。
  • 盈亏比:总盈利与总亏损的比率。
  • 累计利润:所有成功交易的总利润。
  • 累计亏损:所有失败交易的总亏损。

策略的效果通过实时记录和计算这些指标进行评估和优化。

实践展示

本次我们使用双均线指标作为开仓信号的判断,并检验“原始双均线策略”,“双均线+加仓策略”,和“双均线+加减仓策略”三个策略的收益,胜率和盈亏比,从而验证策略的有效性。

原始双均线策略

img img

双均线+加仓策略

img
img

双均线+加减仓策略

img
img

根据策略回测结果,可以看到虽然策略(一)实现了最高的胜率,但是其收益远远小于策略(二)和策略(三),具体原因是其盈亏比小于后两个策略;另外,经过策略(二)和策略(三)对比可以发现,通过添加“减仓方法”可以实现更好的利润保护,从而实现较高的收益,因此一定的实践可行性。

结论

‘盈利加仓,亏损减仓’策略通过在市场有利时增加仓位和在市场不利时减少仓位,实现了风险控制和利润最大化。策略的实现过程中,通过均线和ATR等指标生成交易信号,并设定了具体的加仓和减仓判断标准,确保了交易过程的合理性和有效性。通过对策略的全面评价,可以进一步优化和改进策略,实现更高的交易收益和更稳健的资金管理。

:本策略仅作为教学展示用,任何策略都有其局限性,大家可参考策略思路进行优化改进。


更多内容