在期货交易中,如何在市场的波动中获取最大的利润并降低风险是每个交易者都关心的问题。‘盈利加仓,亏损减仓’策略正是为了解决这一问题而设计的。本文将详细介绍这一策略的含义、重要性以及在实际交易中的实现方式,重点说明加仓和减仓判断的标准以及所使用的指标。
一些指标的好坏确实存在一定的疑问,但是我们结合量化技术,通过“盈利加仓,亏损减仓”更好地抓住趋势,在即使胜率不高的情况下,努力实现一个较好的盈亏比,从而获取长远的收益。
“盈利加仓,亏损减仓”策略的核心思想是:当交易出现盈利时,逐步增加仓位以放大盈利;当交易出现亏损时,逐步减少仓位以控制损失。具体而言,当市场价格按照预期方向运行并产生盈利时,通过加仓来增加持仓量,从而在市场持续有利时获取更高的利润;而当市场价格逆向运行并产生亏损时,通过减仓来减少持仓量,从而在市场不利时减少损失。
本策略在开仓后,根据市场走势进行加仓和减仓操作,具体的实现细节包括开仓、加仓、减仓、平仓和移仓等操作。以下是策略的具体实现步骤:
在策略初始化时,设定了各种参数和变量,例如开仓次数、累计加仓次数、本轮加仓次数、成功次数、失败次数、累计利润、累计损失、持仓品种、持仓数量、持仓价格、持仓利润、持仓方向、最新开仓价格、胜率、盈亏比、未开仓金额、减仓次数等。通过这些变量,可以对交易过程进行全面的跟踪和管理。
当出现买入或卖出信号时,策略会计算可开仓的手数,并执行开仓操作。如果成功开仓,则记录开仓价格和开仓时间等信息。
在持仓过程中,当市场价格按照预期方向运行并产生盈利时,策略会根据设定的加仓条件进行加仓操作。具体而言,当最新加仓价格与当前市场价格之间的价差超过一定阈值,并且当前持仓产生盈利时,策略会判断是否符合加仓条件。如果符合,则执行加仓操作,并更新相关变量。
当市场价格逆向运行并产生亏损时,策略会根据设定的减仓条件进行减仓操作。具体而言,当最新加仓价格与当前市场价格之间的价差超过一定阈值,并且当前持仓产生亏损时,策略会判断是否符合减仓条件。如果符合,则执行减仓操作,并更新相关变量。减仓手数会根据减仓次数动态调整,以确保减仓过程的合理性和有效性。
当持仓达到设定的平仓条件时,策略会执行平仓操作。例如,当出现相反的交易信号时,策略会判断是否符合平仓条件。如果符合,则执行平仓操作,并记录平仓后的资金情况和利润情况。
在某些情况下,策略需要将持仓从一个合约移到另一个合约中。移仓操作能够确保策略在不同合约之间的顺利切换,以应对市场的变化。
在策略执行过程中,策略会实时记录各种交易数据,并对策略的表现进行评价。例如,通过开仓次数、成功次数、累计利润、失败次数、累计亏损、胜率和盈亏比等指标,可以全面评估策略的效果。
以下是策略的具体代码实现,展示了策略的各个关键步骤:
/*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);
}
}
}
shortMean
和longMean
)来生成交易信号。当短期均线向上穿过长期均线时,产生做多信号;当短期均线向下穿过长期均线时,产生做空信号。ATR
)来计算波动率(N
)。ATR用于确定开仓和加仓的价格间隔。symbolDetail
),包括保证金比例、合约乘数等,来计算风险控制和资金管理的参数。spread
)。持多单时,spread
= openPrice
- lastPrice
;持空单时,spread
= lastPrice
- openPrice
。MaxLots
)。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
}
});
}
minspread
)。持多单时,minspread
= openPrice
- lastPrice
;持空单时,minspread
= lastPrice
- openPrice
。N
)和减仓系数(MinSpace
)决定,并随减仓次数(curminNumber
)动态调整。具体代码实现:
// 减仓
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
}
});
}
策略的表现通过以下指标进行评价:
策略的效果通过实时记录和计算这些指标进行评估和优化。
本次我们使用双均线指标作为开仓信号的判断,并检验“原始双均线策略”,“双均线+加仓策略”,和“双均线+加减仓策略”三个策略的收益,胜率和盈亏比,从而验证策略的有效性。
根据策略回测结果,可以看到虽然策略(一)实现了最高的胜率,但是其收益远远小于策略(二)和策略(三),具体原因是其盈亏比小于后两个策略;另外,经过策略(二)和策略(三)对比可以发现,通过添加“减仓方法”可以实现更好的利润保护,从而实现较高的收益,因此一定的实践可行性。
‘盈利加仓,亏损减仓’策略通过在市场有利时增加仓位和在市场不利时减少仓位,实现了风险控制和利润最大化。策略的实现过程中,通过均线和ATR等指标生成交易信号,并设定了具体的加仓和减仓判断标准,确保了交易过程的合理性和有效性。通过对策略的全面评价,可以进一步优化和改进策略,实现更高的交易收益和更稳健的资金管理。
注:本策略仅作为教学展示用,任何策略都有其局限性,大家可参考策略思路进行优化改进。