对于熟悉期货市场的朋友来说,2023年的纯碱行情无疑是令人难忘的。年初,市场从3000点的高位开始,由于远兴增产的传闻,价格急剧下降至年中的1500点,上演了一波经典的逼多行情,导致某私募基金传出大幅亏损的消息。随后,市场经历了为期五个月的大幅震荡,直到年末,又出现了从1600点拉升至2700点的逼空行情,再次成为传奇。如果你认为这就是2023年的终点,那么妖碱永不会让人失望,因为在12月份移仓到05合约时,市场又从2300点跌至2000点。这一系列的操作据说是由某家传奇期货操盘手所为,其操盘资金也实现了百倍增长的传说。这位操盘手的具体策略并不复杂,在消息推动的情况下,他采用了滚动盈利加仓的手法,实现了利滚利的效果。
许多人好奇,如果跟随这位操盘手的步伐,2023年能实现多少利润。今天我们尝试使用量化方法进行模拟。这个策略理念源于数字货币领域的“币钱平衡”,其实它的最初起源是金融市场的“股债平衡”。由于两者天生的对立性,股票和债券通常处于此消彼长的关系,对冲交易可以降低持仓的风险。除非像英国传奇老哥尼克李森那样,遇到日本大地震这样的股债双杀行情。在期货市场中,我们尝试将原始资金分为两半,一半用来开仓,另一半用来平衡仓位。期货合约的涨跌会影响仓位资金的涨跌,我们根据其与可供资金的比例,来进行仓位的平衡,实现盈利加仓和亏损减仓的操作。
经过在优宽量化平台使用分钟级别数据进行模拟运行,结果显示,从年初的100万资金增长到了约1.57亿资金,整整增长了157倍,这种上帝视角的快乐是难以言喻的。
作为一个开放性平台,我们当然要分享策略源码,并附上注释,感兴趣的朋友可以针对自己喜欢的品种在特定的时间段进行尝试。
该策略基于双移动平均线的交叉信号进行交易决策,主要逻辑如下:
策略初始化:
计算多头和空头信号:
talib
库计算短期(shortPeriod
)和长期(longPeriod
)的移动平均线。开平仓逻辑:
动态仓位管理:
绘图:
该策略通过移动平均线交叉信号判断市场趋势,结合动态仓位调整以提高在趋势行情中的持仓效率,并在震荡行情中降低损失。
/*backtest
start: 2023-01-01 00:00:00
end: 2023-12-31 00:00:00
period: 1d
basePeriod: 1m
exchanges: [{"eid":"Futures_CTP","currency":"FUTURES"}]
args: [["longPeriod",7]]
*/
var symbol = 'SA888'
var shortPeriod = 5
var longPeriod = 7
function main() {
// 设置错误过滤器,忽略特定的网络和服务器错误
SetErrorFilter("502:|503:|tcp|character|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF|reused|(CTP_T@10010)|(CTP_T@7090)");
// 初始化K线图,用于后续的数据绘制
var c = KLineChart();
// 获取初始账户信息,用于后续计算盈亏
var initAccount = _C(exchange.GetAccount);
// 定义多头和空头检查信号,初始值为false
var checklong = false;
var checkshort = false;
// 调用CTA策略函数,并传入交易品种symbol
$.CTA(symbol, function(st) {
// 获取K线数据
var r = st.records;
// 如果K线数据不足需要的条数时,则退出函数
if (r.length < longPeriod) {
return;
}
// 使用talib库计算长短周期的移动平均线
var long_line = talib.MA(r, longPeriod);
var short_line = talib.MA(r, shortPeriod);
// 判断多头和空头信号
var long_signal = short_line[r.length - 2] > long_line[r.length - 2]; // 多头信号
var short_signal = short_line[r.length - 2] < long_line[r.length - 2]; // 空头信号
// 获取合约信息并计算多头和空头的保证金比例
var symbolInfo = exchange.SetContractType(st.symbol);
var longratio = symbolInfo.LongMarginRatio + 0.08;
var shortratio = symbolInfo.ShortMarginRatio + 0.08;
// 获取当前最新价格
var lastprice = r[r.length - 1].Close;
// 获取当前账户信息
var accountInfo = _C(exchange.GetAccount);
// 计算总盈亏
var totalprofit = accountInfo.Equity - initAccount.Equity;
// 记录总盈亏信息
LogProfit(totalprofit, "权益", '&');
// 如果当前持有空仓并且出现多头信号,平掉空仓
if (st.position.amount < 0 && long_signal) {
Log('清仓空仓: ', exchange.GetPositions(st.symbol)[0]['Price'] - lastprice);
checkshort = false;
return -st.position.amount;
}
// 如果没有持仓并且出现多头信号,开多仓
if (st.position.amount == 0 && long_signal) {
Log('多仓建仓');
opennum = Math.floor(st.account.Balance / (2 * lastprice * symbolInfo.VolumeMultiple * longratio));
checklong = true;
return opennum;
}
// 如果已经持有多仓,且未出现空头信号,尝试平衡仓位
if (checklong && !short_signal && st.position.amount > 0) {
let curmargin = exchange.GetPositions(st.symbol)[0]['Margin']; // 当前持仓保证金
let availmoney = st.account.Equity - curmargin; // 可用资金
let adjustpos = (availmoney - curmargin) / (2 * lastprice * symbolInfo.VolumeMultiple * longratio); // 调整仓位数量
let curprofit = lastprice - exchange.GetPositions(st.symbol)[0]['Price']; // 当前盈利
// 当仓位不足且盈利超过5时,加仓
if (adjustpos > 0 && Math.floor(adjustpos) >= 1 && curprofit > 5) {
Log('钱多仓少,平衡仓位,加仓:', Math.floor(adjustpos));
return Math.floor(adjustpos);
}
// 当仓位过多时,减仓
if (adjustpos < 0 && Math.ceil(adjustpos) <= -1) {
Log('钱少仓多,平衡仓位,减仓:', Math.ceil(adjustpos));
return Math.ceil(adjustpos);
}
}
// 如果当前持有多仓且出现空头信号,平掉多仓
if (st.position.amount > 0 && short_signal) {
Log('清仓多仓: ', lastprice - exchange.GetPositions(st.symbol)[0]['Price']);
checklong = false;
return -st.position.amount;
}
// 如果没有持仓并且出现空头信号,开空仓
if (st.position.amount == 0 && short_signal) {
Log('空仓建仓');
opennum = Math.floor(st.account.Balance / (2 * lastprice * symbolInfo.VolumeMultiple * shortratio));
checkshort = true;
return -opennum;
}
// 如果已经持有空仓,且未出现多头信号,尝试平衡仓位
if (checkshort && !long_signal && st.position.amount < 0) {
let curmargin = exchange.GetPositions(st.symbol)[0]['Margin'];
let availmoney = st.account.Equity - curmargin;
let adjustpos = (availmoney - curmargin) / (2 * lastprice * symbolInfo.VolumeMultiple * shortratio);
let curprofit = exchange.GetPositions(st.symbol)[0]['Price'] - lastprice;
// 当仓位不足且盈利超过5时,加仓
if (adjustpos > 0 && Math.floor(adjustpos) >= 1 && curprofit > 5) {
Log('钱多仓少,平衡仓位,加仓:', Math.floor(adjustpos), curprofit);
return -Math.floor(adjustpos);
}
// 当仓位过多时,减仓
if (adjustpos < 0 && Math.ceil(adjustpos) <= -1) {
Log('钱少仓多,平衡仓位,减仓:', Math.ceil(adjustpos));
return -Math.ceil(adjustpos);
}
}
// 绘制K线图和移动平均线
for (var i = 0; i < r.length; i++) {
var bar = r[i];
c.begin(bar);
c.plot(long_line[i], "短线", {overlay: true}); // 绘制短期移动平均线
c.plot(short_line[i], "长线", {overlay: true}); // 绘制长期移动平均线
c.close();
}
});
}
最后,大家可能会好奇这个策略是否稳定。结果很明确,它极不稳定。在2024年的行情中,这个策略实现了大亏损。看来,一个策略的成功需要天时地利人和,缺一不可。但我们可以使用量化手段针对自己喜欢的品种进行更多探索性的工作,也许下一个百倍的行情,就在命运轮转的此刻等待着我们。
注: 本文仅做探索性尝试,不构成任何投资性建议。