很多靠直觉玩期货的朋友可能对Aberration策略还是有点陌生,这个曾经创下100%以上年收益率的传奇交易系统由Keith Fitschen于1986年发明。在1993年,他将该系统发布在美国《Future Truth》杂志上,自策略发布之日起,其绩效一直排名靠前,在1997年、2001年和2005年已发布交易系统的业绩排名中均名列第一。 这是一款中长线的交易系统,属于趋势追踪型策略,它在谷物、肉类、金属、能源、外汇、股指期货等8个品种之间灵活运转,通过长线地追踪趋势来获得盈利。而由于它同时在多个不相关或相关性很低的市场中进行交易,因此只需在一种或几种市场中获得趋势的高额盈利,就能弥补在其他不稳定市场中的亏损。它的交易频率一般是每年交易某一品种3-4次,60%的时间都持仓,平均每笔交易持仓60天。 该策略的开平仓策略是这样的:首先利用35日移动平均线和35日内收盘价的标准差,得出上中下三条轨道,当上一个Bar的收盘价上穿了上轨的时候开多,下穿了下轨的时候开空,下穿了中轨的时候平掉多头,上穿了中轨的时候平掉空头。 该策略的思想很简单,就是利用移动平均线和标准差设置通道,然后随时判断是否突破了通道,从而捕捉到趋势。而当趋势逐渐减弱的时候,由于中轨会先趋于缓和,因此当K线反向穿过中轨的时候,就可以认为趋势已经结束,止盈出场。而如果有趋势的误判,在K线反向穿过中轨的时候,也可以及时止损。 该策略发布的年代较早,在现在的交易行情下可能已经不怎么适用,但趋势捕捉的思想却是经久不衰的。
Abberation交易系统由Keith Fitschen于1986年发明,并于1993年发布到Future Trust杂志上。有趣的是Keith并不是交易员出生,他在美国空军服役超过20年,专攻武器的导航系统,在时间序列数据的处理上有深厚的功力。 Abberation交易系统的特点是被动的趋势跟随,采用长期信号,在一个品种上每年交易3-4次,持有时间超过总交易时间的60%,并且可以根据不同的资金规模,分配不同的品种数目。它通过长线交易捕捉趋势来获取巨额利润,同时因为交易在多个不相关的市场,当某一品种回撤时,另一品种可能获利。在一年的时间里,总是有某一个或多个品种能获得巨额利润。这些大额的利润弥补了那些没趋势市场的小额亏损。想着马上要看到赚钱的策略,我抑制不住内心的激动啊。 具体来说,Abberation系统利用3条轨道进行交易。首先计算该品种过去N日收盘价的算术平均MA(close)作为中轨(MID),以收盘价的标准差std(close)作为波动性的衡量,计算上轨MID+mstd(close)以及下轨MID-mstd(close)。当价格突破上轨时做多,当价格回到中轨时平仓;反之,当价格突破下轨时做空,当价格回到中轨时平仓。
Aberration也是一个通道突破系统,只不过其上下通道是由波动率决定。
1、上下轨的确定: Aberration由三条通道线组成,其中中轨为一定周期的移动平均线(AveMa),上下轨为中轨的基础上上下加减一定的价格标准差(StdValue),也就是我们熟知的布林线,系统构造十分简洁优美。
具体计算过程如下: (1)计算中轨:AveMa=Average(Close[1],Length); (2)计算标准差: StdValue = StandardDev(Close[1],Length); (3) 计算上轨:UpperBand=Avema+StdDevUpStdValue; (StdDevUp为上轨参数) (4) 计算下轨:LowerBand=Avema-StdDevDnStdValue; (StdDevDn为下轨参数)
2、买卖条件: 多头:收盘价格突破上轨做多开仓,跌破中轨离场; 空头:收盘价格跌破下轨做空开仓,突破中轨离场。
function Aberration(q, e, symbol, period, upRatio, downRatio, opAmount) {
var self = {}
self.q = q
self.e = e
self.symbol = symbol
self.upTrack = 0
self.middleTrack = 0
self.downTrack = 0
self.nPeriod = period
self.upRatio = upRatio
self.downRatio = downRatio
self.opAmount = opAmount
self.marketPosition = 0
self.lastErrMsg = ''
self.lastErrTime = ''
self.lastBar = {
Time: 0,
Open: 0,
High: 0,
Low: 0,
Close: 0,
Volume: 0
}
self.symbolDetail = null
self.lastBarTime = 0
self.tradeCount = 0
self.isBusy = false
self.setLastError = function(errMsg) {
self.lastErrMsg = errMsg
self.lastErrTime = errMsg.length > 0 ? _D() : ''
}
self.getStatus = function() {
return [self.symbol, self.opAmount, self.upTrack, self.downTrack, self.middleTrack, _N(self.lastBar.Close), (self.marketPosition == 0 ? "--" : (self.marketPosition > 0 ? "多#ff0000" : "空#0000ff")), self.tradeCount, self.lastErrMsg, self.lastErrTime]
}
self.getMarket = function() {
return [self.symbol, _D(self.lastBarTime), _N(self.lastBar.Open), _N(self.lastBar.High), _N(self.lastBar.Low), _N(self.lastBar.Close), self.lastBar.Volume]
}
self.restore = function(positions) {
for (var i = 0; i < positions.length; i++) {
if (positions[i].ContractType == self.symbol) {
self.marketPosition += positions[i].Amount * ((positions[i].Type == PD_LONG || positions[i].Type == PD_LONG_YD) ? 1 : -1)
}
}
if (self.marketPosition !== 0) {
self.tradeCount++
Log("恢复", self.symbol, "当前持仓为", self.marketPosition)
}
}
self.poll = function() {
if (self.isBusy) {
return false
}
if (!$.IsTrading(self.symbol)) {
self.setLastError("不在交易时间内")
return false
}
if (!self.e.IO("status")) {
self.setLastError("未连接交易所")
return false
}
var detail = self.e.SetContractType(self.symbol)
if (!detail) {
self.setLastError("切换合约失败")
return false
}
if (!self.symbolDetail) {
self.symbolDetail = detail
Log("合约", detail.InstrumentName.replace(/\s+/g, ""), ", 策略一次开仓:", self.opAmount, "手, 一手", detail.VolumeMultiple, "份, 最大下单量", detail.MaxLimitOrderVolume, "保证金率:", detail.LongMarginRatio.toFixed(4), detail.ShortMarginRatio.toFixed(4), "交割日期", detail.StartDelivDate);
}
var records = self.e.GetRecords()
if (!records || records.length == 0) {
self.setLastError("获取柱线失败")
return false
}
var bar = records[records.length - 1]
self.lastBar = bar
if (records.length <= self.nPeriod) {
self.setLastError("柱线长度不够")
return false
}
if (self.lastBarTime < bar.Time) {
var sum = 0
var pos = records.length - self.nPeriod - 1
for (var i = pos; i < records.length - 1; i++) {
sum += records[i].Close
}
var avg = sum / self.nPeriod
var std = 0
for (i = pos; i < records.length - 1; i++) {
std += Math.pow(records[i].Close - avg, 2)
}
std = Math.sqrt(std / self.nPeriod)
self.upTrack = _N(avg + (self.upRatio * std))
self.downTrack = _N(avg - (self.downRatio * std))
self.middleTrack = _N(avg)
self.lastBarTime = bar.Time
}
var msg
var act = ""
if (self.marketPosition == 0) {
if (bar.Close > self.upTrack) {
msg = '做多 触发价: ' + bar.Close + ' 上轨:' + self.upTrack;
act = "buy"
} else if (bar.Close < self.downTrack) {
msg = '做空 触发价: ' + bar.Close + ' 下轨:' + self.downTrack;
act = "sell"
}
} else {
if (self.marketPosition < 0 && bar.Close > self.middleTrack) {
msg = '平空 触发价: ' + bar.Close + ' 平仓线:' + self.middleTrack;
act = "closesell"
} else if (self.marketPosition > 0 && bar.Close < self.middleTrack) {
msg = '平多 触发价: ' + bar.Close + ' 平仓线:' + self.middleTrack;
act = "closebuy"
}
}
if (act == "") {
return true
}
Log(self.symbol + ', ' + msg + (NotifyWX ? '@' : ''))
self.isBusy = true
self.tradeCount += 1
if (self.lastErrMsg != '') {
self.setLastError('')
}
self.q.pushTask(self.e, self.symbol, act, self.opAmount, function(task, ret) {
self.isBusy = false
if (!ret) {
return
}
if (task.action == "buy") {
self.marketPosition = 1
} else if (task.action == "sell") {
self.marketPosition = -1
} else {
self.marketPosition = 0
}
})
}
return self
}
function main() {
if (exchange.GetName() !== 'Futures_CTP') {
throw "只支持传统商品期货(CTP)"
}
SetErrorFilter("login|ready|初始化")
LogStatus("Ready...")
if (Reset) {
LogProfitReset()
LogReset()
}
// Ref: https://www.fmz.com/bbs-topic/362
if (typeof(exchange.IO("mode", 0)) == 'number') {
Log("切换行情模式成功")
}
LogStatus("等待与期货商服务器连接..")
while (!exchange.IO("status")) {
Sleep(500)
}
LogStatus("获取资产信息")
var tblRuntime = {
type: 'table',
title: '交易信息',
cols: ['品种', '每次开仓量', '上轨', '下轨', '中轨', '最后成交价', '仓位', '交易次数', '最后错误', '错误时间'],
rows: []
};
var tblMarket = {
type: 'table',
title: '行情信息',
cols: ['品种', '当前周期', '开盘', '最高', '最低', '最后成交价', '成交量'],
rows: []
};
var tblPosition = {
type: 'table',
title: '持仓信息',
cols: ['品种', '杠杆', '方向', '均价', '数量', '持仓盈亏'],
rows: []
};
var positions = _C(exchange.GetPosition)
if (positions.length > 0 && !AutoRestore) {
throw "程序启动时不能有持仓, 但您可以勾选自动恢复来进行自动识别 !"
}
var initAccount = _C(exchange.GetAccount)
var detail = JSON.parse(exchange.GetRawJSON())
if (positions.length > 0) {
initAccount.Balance += detail['CurrMargin']
}
var initNetAsset = detail['CurrMargin'] + detail['Available']
var initAccountTbl = $.AccountToTable(exchange.GetRawJSON(), "初始资金")
if (initAccountTbl.rows.length == 0) {
initAccountTbl.rows = [
['Balance', '可用保证金', initAccount.Balance],
['FrozenBalance', '冻结资金', initAccount.FrozenBalance]
]
}
var nowAcccount = initAccount
var nowAcccountTbl = initAccountTbl
var symbols = Symbols.replace(/\s+/g, "").split(',')
var pollers = []
var prePosUpdate = 0
var suffix = ""
var needUpdate = false
var holdProfit = 0
function updateAccount(acc) {
nowAcccount = acc
nowAcccountTbl = $.AccountToTable(exchange.GetRawJSON(), "当前资金")
if (nowAcccountTbl.rows.length == 0) {
nowAcccountTbl.rows = [
['Balance', '可用保证金', nowAcccount.Balance],
['FrozenBalance', '冻结资金', nowAcccount.FrozenBalance]
]
}
}
var q = $.NewTaskQueue(function(task, ret) {
needUpdate = true
Log(task.desc, ret ? "成功" : "失败")
var account = task.e.GetAccount()
if (account) {
updateAccount(account)
}
})
_.each(symbols, function(symbol) {
var pair = symbol.split(':')
pollers.push(Aberration(q, exchange, pair[0], NPeriod, Ks, Kx, (pair.length == 1 ? AmountOP : parseInt(pair[1]))))
})
if (positions.length > 0 && AutoRestore) {
_.each(pollers, function(poll) {
poll.restore(positions)
})
}
var isFirst = true
while (true) {
var cmd = GetCommand()
if (cmd) {
var js = cmd.split(':', 2)[1]
Log("执行调试代码:", js)
try {
eval(js)
} catch (e) {
Log("Exception", e)
}
}
tblRuntime.rows = []
tblMarket.rows = []
var marketAlive = false
_.each(pollers, function(poll) {
if (poll.poll()) {
marketAlive = true
}
tblRuntime.rows.push(poll.getStatus())
tblMarket.rows.push(poll.getMarket())
})
q.poll()
Sleep(LoopInterval * 1000)
if ((!exchange.IO("status")) || (!marketAlive)) {
if (isFirst) {
LogStatus("正在等待开盘...", _D())
}
continue
}
isFirst = false
var now = new Date().getTime()
if (marketAlive && (now - prePosUpdate > 30000 || needUpdate)) {
var pos = exchange.GetPosition()
if (pos) {
holdProfit = 0
prePosUpdate = now
tblPosition.rows = []
for (var i = 0; i < pos.length; i++) {
tblPosition.rows.push([pos[i].ContractType, pos[i].MarginLevel, ((pos[i].Type == PD_LONG || pos[i].Type == PD_LONG_YD) ? '多#ff0000' : '空#0000ff'), pos[i].Price, pos[i].Amount, _N(pos[i].Profit)])
holdProfit += pos[i].Profit
}
if (pos.length == 0 && needUpdate) {
LogProfit(_N(nowAcccount.Balance - initAccount.Balance, 4), nowAcccount)
}
}
needUpdate = false
if (RCMode) {
var account = exchange.GetAccount()
if (account) {
updateAccount(account)
var detail = JSON.parse(exchange.GetRawJSON())
var netAsset = detail['PositionProfit'] + detail['CurrMargin'] + detail['Available']
var risk = detail['CurrMargin'] / (detail['CurrMargin'] + detail['Available'] + detail['PositionProfit'])
suffix = ", 账户初始净值约: " + _N(initNetAsset, 2) + " , 风控最小净值要求" + MinNetAsset + " , 当前账户净值约: " + _N(netAsset, 2) + ", 盈亏约: " + _N(netAsset - initNetAsset, 3) + " 元, 风险: " + ((risk * 100).toFixed(3)) + "% #ff0000"
if (netAsset < MinNetAsset) {
Log("风控模块触发, 中止运行并平掉所有仓位, 当前净值约 ", netAsset, ", 要求低于最小净值:", MinNetAsset)
if (RCCoverAll) {
Log("开始平掉所有仓位")
$.NewPositionManager().CoverAll()
}
throw "中止运行"
}
}
}
}
LogStatus('`' + JSON.stringify([tblRuntime, tblPosition, tblMarket, initAccountTbl, nowAcccountTbl]) + '`\n价格最后更新: ' + _D() + ', 持仓最后更新: ' + _D(prePosUpdate) + '\n当前持仓总盈亏: ' + _N(holdProfit, 3) + suffix)
}
}
由于该系统只有构建投资组合才能发挥系统的威力。
1、该策略品种和周期适应性都十分出色,适应于大部分交易品种; 2、该策略回撤较大,需要做好严格的资金管理和风险控制;