原文地址:https://www.fmz.cn/digest-topic/8978
什么是海龜策略?
幾乎所有的寬客(Quant)都聽說過海龜交易策略,該策略以海龜交易法則為核心。海龜交易法則,起源于八十年代的美國,是一套簡單有效的交易法則。這個法則以及使用這個法則的人的故事被寫成了一本書——《海龜交易法則》,這是一本入門量化投資的經(jīng)典書籍。
股票證券的程序化、量化交易以前門檻可不低,以前軟件支持少,賬戶開戶門檻極高。FMZ.CN(國內(nèi)站)支持富途證券、中泰XTP,開通了富途證券就可以很方便的做程序化模擬盤、實盤測試。本篇我們就一起學(xué)習(xí)設(shè)計一個股票版本的多品種海龜交易策略,初期我們主要基于回測系統(tǒng)進行設(shè)計、研究,慢慢的擴展至富途證券的模擬盤(模擬賬戶)。
策略設(shè)計:
策略架構(gòu)我們參考http://FMZ.CN上開源的「商品期貨多品種海龜策略」。和商品期貨版本一樣,我們設(shè)計一個海龜交易邏輯管理對象的構(gòu)造函數(shù)TTManager。構(gòu)造的對象(obj)用來操作、管理每個股票的海龜交易邏輯的執(zhí)行。
```javascriptvar TTManager = {New: function(needRestore, symbol, initBalance, keepBalance, riskRatio, atrLen, enterPeriodA, leavePeriodA, enterPeriodB, leavePeriodB, useFilter,multiplierN, multiplierS, maxLots) {// subscribevar symbolDetail = _C(exchange.SetContractType, symbol)if (symbolDetail.VolumeMultiple == 0) {Log(symbolDetail)throw "股票合約信息異常"} else {Log("合約", symbolDetail.InstrumentName, "一手", symbolDetail.VolumeMultiple, "股, 最大下單量", symbolDetail.MaxLimitOrderVolume, ", 最小下單量", symbolDetail.VolumeMultiple)}var obj = {symbol: symbol,tradeSymbol: symbolDetail.InstrumentID,initBalance: initBalance,keepBalance: keepBalance,riskRatio: riskRatio,atrLen: atrLen,enterPeriodA: enterPeriodA,leavePeriodA: leavePeriodA,enterPeriodB: enterPeriodB,leavePeriodB: leavePeriodB,useFilter: useFilter,multiplierN: multiplierN,multiplierS: multiplierS}obj.maxLots = maxLotsobj.lastPrice = 0obj.symbolDetail = symbolDetailobj.status = {symbol: symbol,recordsLen: 0,vm: [],open: 0,cover: 0,st: 0,marketPosition: 0,lastPrice: 0,holdPrice: 0,holdAmount: 0,holdProfit: 0,switchCount: 0,N: 0,upLine: 0,downLine: 0,lastErr: "",lastErrTime: "",stopPrice: '',leavePrice: '',isTrading: false}...
股票市場和商品期貨市場又有些差別,下面我們來一起分析一下這些差別,然后對于策略進行具體的修改、設(shè)計。交易時間差別
我們需要單獨設(shè)計一個函數(shù),識別開盤休盤時間,如下函數(shù)設(shè)計,給構(gòu)造函數(shù)TTManager返回的對象obj增加方法:```javascript```javascript
obj.newDate = function() {var timezone = 8 var offset_GMT = new Date().getTimezoneOffset() var nowDate = new Date().getTime() var targetDate = new Date(nowDate + offset_GMT * 60 * 1000 + timezone * 60 * 60 * 1000)return targetDate}obj.isSymbolTrading = function() {// 使用 newDate() 代替 new Date() 因為服務(wù)器時區(qū)問題var now = obj.newDate()var day = now.getDay()var hour = now.getHours()var minute = now.getMinutes()StatusMsg = "非交易時段"if (day === 0 || day === 6) {return false}if((hour == 9 && minute >= 30) || (hour == 11 && minute < 30) || (hour > 9 && hour < 11)) {// 9:30-11:30StatusMsg = "交易時段"return true } else if (hour >= 13 && hour < 15) {// 13:00-15:00StatusMsg = "交易時段"return true } return false }
交易方向的差別
商品期貨有開倉、平倉。股票只有買、賣,沒有開倉平倉。股票類似于現(xiàn)貨,但是也有持倉,買入的股票會在GetPosition函數(shù)獲取的持倉列表中顯示。需要我們對交易下單的部分做設(shè)計,增加函數(shù):```javascript
obj.sell = function(e, contractType, lots, insDetail) {...
}obj.buy = function(e, contractType, opAmount, insDetail) {...
}
下單頭寸計算
商品期貨交易下單時是按照合約張數(shù)下單,一張合約根據(jù)其合約乘數(shù)代表一定量的商品(例如rb合約,一張代表10噸螺紋鋼)。股票雖說也是有按手計算的(根據(jù)板塊有的1手100股,有的500股,還有的200股)。但是下單的時候必須是股數(shù),并且要能被一手的股數(shù)整除。不能整除的會報錯。
這樣就需要對海龜交易法計算頭寸的部分做一定修改:
var atrs
= TA.ATR(records
, atrLen
)var N = _N(atrs
[atrs
.length
- 1], 4)var account
= _C(exchange
.GetAccount
)var unit
= parseInt((obj
.initBalance
-obj
.keepBalance
) * (obj
.riskRatio
/ 100) / N / obj
.symbolDetail
.VolumeMultiple
)var canOpen
= parseInt((account
.Balance
-obj
.keepBalance
) / (lastPrice
* 1.2) / obj
.symbolDetail
.VolumeMultiple
)unit
= Math
.min(unit
, canOpen
)unit
= unit
* obj
.symbolDetail
.VolumeMultiple
if (unit
< obj
.symbolDetail
.VolumeMultiple
) {obj
.setLastError("可開 " + unit
+ " 手 無法開倉, " + (canOpen
>= obj
.symbolDetail
.VolumeMultiple
? "風(fēng)控觸發(fā)" : "資金限制") + "。 可用: " + account
.Balance
)return}if (opCode
== 2) {throw "股票不支持做空"}
策略注釋
為了方便理解策略代碼,我們對策略通篇注釋。
var SlideTick
= 10
var Interval
= 1000
var TTManager
= {New
: function(needRestore
, symbol
, initBalance
, keepBalance
, riskRatio
, atrLen
, enterPeriodA
, leavePeriodA
, enterPeriodB
, leavePeriodB
, useFilter
,multiplierN
, multiplierS
, maxLots
) {var symbolDetail
= _C(exchange
.SetContractType
, symbol
) if (symbolDetail
.VolumeMultiple
== 0) { Log(symbolDetail
)throw "股票合約信息異常"} else {Log("合約", symbolDetail
.InstrumentName
, "一手", symbolDetail
.VolumeMultiple
, "股, 最大下單量", symbolDetail
.MaxLimitOrderVolume
, ", 最小下單量", symbolDetail
.VolumeMultiple
) }var obj
= {symbol
: symbol
,tradeSymbol
: symbolDetail
.InstrumentID
,initBalance
: initBalance
,keepBalance
: keepBalance
,riskRatio
: riskRatio
,atrLen
: atrLen
,enterPeriodA
: enterPeriodA
,leavePeriodA
: leavePeriodA
,enterPeriodB
: enterPeriodB
,leavePeriodB
: leavePeriodB
,useFilter
: useFilter
,multiplierN
: multiplierN
,multiplierS
: multiplierS
}obj
.maxLots
= maxLotsobj
.lastPrice
= 0obj
.symbolDetail
= symbolDetailobj
.status
= {symbol
: symbol
,recordsLen
: 0,vm
: [],open
: 0,cover
: 0,st
: 0,marketPosition
: 0,lastPrice
: 0,holdPrice
: 0,holdAmount
: 0,holdProfit
: 0,switchCount
: 0,N: 0,upLine
: 0,downLine
: 0,lastErr
: "",lastErrTime
: "",stopPrice
: '',leavePrice
: '',isTrading
: false}obj
.setLastError = function(err
) {if (typeof(err
) === 'undefined' || err
=== '') {obj
.status
.lastErr
= ""obj
.status
.lastErrTime
= ""return}var t
= new Date()obj
.status
.lastErr
= errobj
.status
.lastErrTime
= t
.toLocaleString()}obj
.getPosition = function(e
, contractTypeName
) {var allAmount
= 0var allProfit
= 0var allFrozen
= 0var posMargin
= 0var price
= 0var direction
= nullpositions
= _C(e
.GetPosition
) for (var i
= 0; i
< positions
.length
; i
++) {if (positions
[i
].ContractType
!= contractTypeName
) {continue}if (positions
[i
].Type
== PD_LONG) {posMargin
= positions
[i
].MarginLevelallAmount
+= positions
[i
].AmountallProfit
+= positions
[i
].ProfitallFrozen
+= positions
[i
].FrozenAmountprice
= positions
[i
].Pricedirection
= positions
[i
].Type
}}if (allAmount
=== 0) {return null}return {MarginLevel
: posMargin
,FrozenAmount
: allFrozen
,Price
: price
,Amount
: allAmount
,Profit
: allProfit
,Type
: direction
,ContractType
: contractTypeName
,CanCoverAmount
: allAmount
- allFrozen
}}obj
.newDate = function() {var timezone
= 8 var offset_GMT
= new Date().getTimezoneOffset() var nowDate
= new Date().getTime() var targetDate
= new Date(nowDate
+ offset_GMT
* 60 * 1000 + timezone
* 60 * 60 * 1000)return targetDate
}obj
.isSymbolTrading = function() {var now
= obj
.newDate()var day
= now
.getDay()var hour
= now
.getHours()var minute
= now
.getMinutes()StatusMsg
= "非交易時段"if (day
=== 0 || day
=== 6) {return false}if((hour
== 9 && minute
>= 30) || (hour
== 11 && minute
< 30) || (hour
> 9 && hour
< 11)) {StatusMsg
= "交易時段"return true } else if (hour
>= 13 && hour
< 15) {StatusMsg
= "交易時段"return true } return false }obj
.buy = function(e
, contractType
, opAmount
, insDetail
) {var initPosition
= obj
.getPosition(e
, contractType
) var isFirst
= truevar initAmount
= initPosition
? initPosition
.Amount
: 0 var positionNow
= initPosition
if(!IsVirtual() && opAmount
% insDetail
.LotSize
!= 0) { throw "每手?jǐn)?shù)量不匹配"}while (true) {var needOpen
= opAmount
if (isFirst
) {isFirst
= false} else {Sleep(Interval
*20)positionNow
= obj
.getPosition(e
, contractType
)if (positionNow
) {needOpen
= opAmount
- (positionNow
.Amount
- initAmount
)}} if (needOpen
< insDetail
.LotSize
|| (needOpen
% insDetail
.LotSize
!= 0 && !IsVirtual())) {break} var depth
= _C(e
.GetDepth
)var amount
= needOpene
.SetDirection("buy")var orderId
= e
.Buy(depth
.Asks
[0].Price
+ (insDetail
.PriceSpread
* SlideTick
), amount
, contractType
, 'Ask', depth
.Asks
[0])while (true) {Sleep(Interval
*20)var orders
= _C(e
.GetOrders
)if (orders
.length
=== 0) {break}for (var j
= 0; j
< orders
.length
; j
++) {e
.CancelOrder(orders
[j
].Id
)if (j
< (orders
.length
- 1)) {Sleep(Interval
*20)}}}}var ret
= nullif (!positionNow
) {return ret
}ret
= positionNow
return ret
}obj
.sell = function(e
, contractType
, lots
, insDetail
) {var initAmount
= 0var firstLoop
= trueif(!IsVirtual() && lots
% insDetail
.LotSize
!= 0) {throw "每手?jǐn)?shù)量不匹配"}while (true) {var n
= 0var total
= 0var positions
= _C(e
.GetPosition
)var nowAmount
= 0for (var i
= 0; i
< positions
.length
; i
++) {if (positions
[i
].ContractType
!= contractType
) {continue}nowAmount
+= positions
[i
].Amount
}if (firstLoop
) {initAmount
= nowAmountfirstLoop
= false}var amountChange
= initAmount
- nowAmount
if (typeof(lots
) == 'number' && amountChange
>= lots
) {break} for (var i
= 0; i
< positions
.length
; i
++) {if (positions
[i
].ContractType
!= contractType
) {continue}var amount
= positions
[i
].Amount
var depth
var opAmount
= 0var opPrice
= 0if (positions
[i
].Type
== PD_LONG) {depth
= _C(e
.GetDepth
)opAmount
= amountopPrice
= depth
.Bids
[0].Price
- (insDetail
.PriceSpread
* SlideTick
)}if (typeof(lots
) === 'number') {opAmount
= Math
.min(opAmount
, lots
- (initAmount
- nowAmount
))}if (opAmount
> 0) {if (positions
[i
].Type
== PD_LONG) {e
.SetDirection("closebuy")e
.Sell(opPrice
, opAmount
, contractType
, "平倉", 'Bid', depth
.Bids
[0])}n
++}if (typeof(lots
) === 'number') {break}}if (n
=== 0) {break}while (true) {Sleep(Interval
*20)var orders
= _C(e
.GetOrders
)if (orders
.length
=== 0) {break}for (var j
= 0; j
< orders
.length
; j
++) {e
.CancelOrder(orders
[j
].Id
)if (j
< (orders
.length
- 1)) {Sleep(Interval
*20)}}}}}obj
.reset = function(marketPosition
, openPrice
, N, leavePeriod
, preBreakoutFailure
) {if (typeof(marketPosition
) !== 'undefined') {obj
.marketPosition
= marketPositionobj
.openPrice
= openPriceobj
.preBreakoutFailure
= preBreakoutFailureobj
.N = Nobj
.leavePeriod
= leavePeriod
var pos
= obj
.getPosition(exchange
, obj
.tradeSymbol
)if (pos
) {obj
.holdPrice
= pos
.Priceobj
.holdAmount
= pos
.Amount
Log(obj
.symbol
, "倉位", pos
)} else {throw "恢復(fù)" + obj
.symbol
+ "的持倉狀態(tài)出錯, 沒有找到倉位信息"}Log("恢復(fù)", obj
.symbol
, "加倉次數(shù)", obj
.marketPosition
, "持倉均價:", obj
.holdPrice
, "持倉數(shù)量:", obj
.holdAmount
, "最后一次加倉價", obj
.openPrice
, "N值", obj
.N, "離市周期:", leavePeriod
, "上次突破:", obj
.preBreakoutFailure
? "失敗" : "成功")obj
.status
.open
= 1obj
.status
.vm
= [obj
.marketPosition
, obj
.openPrice
, obj
.N, obj
.leavePeriod
, obj
.preBreakoutFailure
]} else {obj
.marketPosition
= 0obj
.holdPrice
= 0obj
.openPrice
= 0obj
.holdAmount
= 0obj
.holdProfit
= 0obj
.preBreakoutFailure
= true obj
.N = 0obj
.leavePeriod
= leavePeriodA
}obj
.holdProfit
= 0obj
.lastErr
= ""obj
.lastErrTime
= ""}obj
.Status = function() {obj
.status
.N = obj
.Nobj
.status
.marketPosition
= obj
.marketPositionobj
.status
.holdPrice
= obj
.holdPriceobj
.status
.holdAmount
= obj
.holdAmountobj
.status
.lastPrice
= obj
.lastPrice
if (obj
.lastPrice
> 0 && obj
.holdAmount
> 0 && obj
.marketPosition
!== 0) {obj
.status
.holdProfit
= _N((obj
.lastPrice
- obj
.holdPrice
) * obj
.holdAmount
* obj
.symbolDetail
.VolumeMultiple
, 4) * (obj
.marketPosition
> 0 ? 1 : -1)} else {obj
.status
.holdProfit
= 0}obj
.status
.symbolDetail
= obj
.symbolDetail
return obj
.status
}obj
.Poll = function() {obj
.status
.isTrading
= obj
.isSymbolTrading(obj
.symbol
)if (!obj
.status
.isTrading
) {return}var suffix
= WXPush
? '@' : ''var insDetail
= exchange
.SetContractType(obj
.symbol
)if (!insDetail
) {return}var ticker
= exchange
.GetTicker()if (!ticker
) {obj
.setLastError("獲取tick失敗")return}if (IsVirtual()) {ticker
.Info
= {}ticker
.Info
.LotSize
= obj
.symbolDetail
.VolumeMultipleticker
.Info
.PriceSpread
= 0.01}var records
= exchange
.GetRecords()if (!records
) {obj
.setLastError("獲取K線失敗")return}obj
.status
.recordsLen
= records
.length
if (records
.length
< obj
.atrLen
) {obj
.setLastError("K線長度小于 " + obj
.atrLen
)return}var opCode
= 0 var lastPrice
= records
[records
.length
- 1].Closeobj
.lastPrice
= lastPrice
if (obj
.marketPosition
=== 0) {obj
.status
.stopPrice
= '--'obj
.status
.leavePrice
= '--'obj
.status
.upLine
= 0obj
.status
.downLine
= 0for (var i
= 0; i
< 2; i
++) {if (i
== 0 && obj
.useFilter
&& !obj
.preBreakoutFailure
) {continue}var enterPeriod
= i
== 0 ? obj
.enterPeriodA
: obj
.enterPeriodB
if (records
.length
< (enterPeriod
+ 1)) {continue}var highest
= TA.Highest(records
, enterPeriod
, 'High')var lowest
= TA.Lowest(records
, enterPeriod
, 'Low')obj
.status
.upLine
= obj
.status
.upLine
== 0 ? highest
: Math
.min(obj
.status
.upLine
, highest
)obj
.status
.downLine
= obj
.status
.downLine
== 0 ? lowest
: Math
.max(obj
.status
.downLine
, lowest
)if (lastPrice
> highest
) {opCode
= 1}if (opCode
!= 0) {obj
.leavePeriod
= (enterPeriod
== obj
.enterPeriodA
) ? obj
.leavePeriodA
: obj
.leavePeriodB
break}}} else {var spread
= obj
.marketPosition
> 0 ? (obj
.openPrice
- lastPrice
) : (lastPrice
- obj
.openPrice
)obj
.status
.stopPrice
= _N(obj
.openPrice
+ (obj
.N * StopLossRatio
* (obj
.marketPosition
> 0 ? -1 : 1)))if (spread
> (obj
.N * StopLossRatio
)) {opCode
= 3obj
.preBreakoutFailure
= trueLog(obj
.symbolDetail
.InstrumentName
, "止損平倉", suffix
)obj
.status
.st
++} else if (-spread
> (IncSpace
* obj
.N) && Math
.abs(obj
.marketPosition
) < obj
.maxLots
) {opCode
= obj
.marketPosition
> 0 ? 1 : 2}if (opCode
== 0 && records
.length
> obj
.leavePeriod
) {obj
.status
.leavePrice
= obj
.marketPosition
> 0 ? TA.Lowest(records
, obj
.leavePeriod
, 'Low') : TA.Highest(records
, obj
.leavePeriod
, 'High')if ((obj
.marketPosition
> 0 && lastPrice
< obj
.status
.leavePrice
) ||(obj
.marketPosition
< 0 && lastPrice
> obj
.status
.leavePrice
)) {obj
.preBreakoutFailure
= falseLog(obj
.symbolDetail
.InstrumentName
, "正常平倉", suffix
)opCode
= 3obj
.status
.cover
++}}}if (opCode
== 0) {return}if (opCode
== 3) {var pos
= obj
.getPosition(exchange
, obj
.tradeSymbol
)obj
.sell(exchange
, obj
.tradeSymbol
, pos
.Amount
, ticker
.Info
)obj
.reset()_G(obj
.symbol
, null)var account
= _C(exchange
.GetAccount
)return}if (Math
.abs(obj
.marketPosition
) >= obj
.maxLots
) {obj
.setLastError("禁止開倉, 超過最大持倉 " + obj
.maxLots
)return}var atrs
= TA.ATR(records
, atrLen
)var N = _N(atrs
[atrs
.length
- 1], 4)var account
= _C(exchange
.GetAccount
)var unit
= parseInt((obj
.initBalance
-obj
.keepBalance
) * (obj
.riskRatio
/ 100) / N / obj
.symbolDetail
.VolumeMultiple
)var canOpen
= parseInt((account
.Balance
-obj
.keepBalance
) / (lastPrice
* 1.2) / obj
.symbolDetail
.VolumeMultiple
)unit
= Math
.min(unit
, canOpen
)unit
= unit
* obj
.symbolDetail
.VolumeMultiple
if (unit
< obj
.symbolDetail
.VolumeMultiple
) {obj
.setLastError("可開 " + unit
+ " 手 無法開倉, " + (canOpen
>= obj
.symbolDetail
.VolumeMultiple
? "風(fēng)控觸發(fā)" : "資金限制") + "。 可用: " + account
.Balance
)return}if (opCode
== 2) {throw "股票不支持做空"}var ret
= obj
.buy(exchange
, obj
.tradeSymbol
, unit
, ticker
.Info
)if (ret
) {Log(obj
.symbolDetail
.InstrumentName
, obj
.marketPosition
== 0 ? "開倉" : "加倉", "離市周期", obj
.leavePeriod
, suffix
)obj
.N = Nobj
.openPrice
= ticker
.Lastobj
.holdPrice
= ret
.Price
if (obj
.marketPosition
== 0) {obj
.status
.open
++}obj
.holdAmount
= ret
.Amountobj
.marketPosition
+= opCode
== 1 ? 1 : -1obj
.status
.vm
= [obj
.marketPosition
, obj
.openPrice
, N, obj
.leavePeriod
, obj
.preBreakoutFailure
]_G(obj
.symbol
, obj
.status
.vm
)} else {obj
.setLastError("下單失敗")return}}var vm
= nullif (RMode
=== 0) {vm
= _G(obj
.symbol
)} else {vm
= JSON.parse(VMStatus
)[obj
.symbol
]}if (vm
) {Log("準(zhǔn)備恢復(fù)進度, 當(dāng)前合約狀態(tài)為", vm
)obj
.reset(vm
[0], vm
[1], vm
[2], vm
[3], vm
[4])} else {if (needRestore
) {Log("沒有找到" + obj
.symbol
+ "的進度恢復(fù)信息")}obj
.reset()}return obj
}
}function onexit() {Log("已退出策略...")
}function main() {if((!IsVirtual() && exchange
.GetCurrency() != "STOCK" && exchange
.GetName() != "Futures_Futu") || (IsVirtual() && exchange
.GetCurrency() != "STOCK_CNY" && exchange
.GetName() != "Futures_XTP")) {Log("currency:", exchange
.GetCurrency(), "name:", exchange
.GetName())throw "不支持"}SetErrorFilter("login|ready|流控|連接失敗|初始|Timeout|market not ready")while (!exchange
.IO("status")) {Sleep(3000)LogStatus("正在等待與交易服務(wù)器連接, " + _D())}var positions
= _C(exchange
.GetPosition
)if (positions
.length
> 0) {Log("檢測到當(dāng)前持有倉位, 系統(tǒng)將開始嘗試恢復(fù)進度...")Log("持倉信息", positions
)}Log("風(fēng)險系數(shù):", RiskRatio
, "N值周期:", ATRLength
, "系統(tǒng)1: 入市周期", EnterPeriodA
, "離市周期", LeavePeriodA
, "系統(tǒng)二: 入市周期", EnterPeriodB
, "離市周期", LeavePeriodB
, "加倉系數(shù):", IncSpace
, "止損系數(shù):", StopLossRatio
, "單品種最多開倉:", MaxLots
, "次")var initAccount
= _C(exchange
.GetAccount
)var realInitBalance
= initAccount
.Balance
if (CustomBalance
) {realInitBalance
= InitBalance
Log("自定義啟動資產(chǎn)為", realInitBalance
)}var keepBalance
= _N(realInitBalance
* (KeepRatio
/100), 3)Log("當(dāng)前資產(chǎn)信息", initAccount
, "保留資金:", keepBalance
)var tts
= []var filter
= []var arr
= Instruments
.split(',')for (var i
= 0; i
< arr
.length
; i
++) {var symbol
= arr
[i
].replace(/^\s+/g, "").replace(/\s+$/g, "");if (typeof(filter
[symbol
]) !== 'undefined') {Log(symbol
, "已經(jīng)存在, 系統(tǒng)已自動過濾")continue}filter
[symbol
] = truevar hasPosition
= falsefor (var j
= 0; j
< positions
.length
; j
++) {if (positions
[j
].ContractType
== symbol
) {hasPosition
= truebreak}}var obj
= TTManager
.New(hasPosition
, symbol
, realInitBalance
, keepBalance
, RiskRatio
, ATRLength
, EnterPeriodA
, LeavePeriodA
, EnterPeriodB
, LeavePeriodB
, UseEnterFilter
, IncSpace
, StopLossRatio
, MaxLots
)tts
.push(obj
)}var tblAssets
= nullvar nowAccount
= nullvar lastStatus
= ''while (true) {if (GetCommand() === "暫停/繼續(xù)") {Log("暫停交易中...")while (GetCommand() !== "暫停/繼續(xù)") {Sleep(1000)}Log("繼續(xù)交易中...")}while (!exchange
.IO("status")) {Sleep(3000)LogStatus("正在等待與交易服務(wù)器連接, " + _D() + "\n" + lastStatus
)}var tblStatus
= {type
: "table",title
: "持倉信息",cols
: ["合約名稱", "持倉方向", "持倉均價", "持倉數(shù)量", "持倉盈虧", "加倉次數(shù)", "開倉次數(shù)", "止損次數(shù)", "成功次數(shù)", "當(dāng)前價格", "N"],rows
: []}var tblMarket
= {type
: "table",title
: "運行狀態(tài)",cols
: ["合約名稱", "合約乘數(shù)", "保證金率", "交易時間", "移倉次數(shù)", "柱線長度", "上線", "下線", "止損價", "離市價", "異常描述", "發(fā)生時間"],rows
: []}var totalHold
= 0var vmStatus
= {}var ts
= new Date().getTime()var holdSymbol
= 0var tradingCount
= 0for (var i
= 0; i
< tts
.length
; i
++) {tts
[i
].Poll()var d
= tts
[i
].Status()if (d
.holdAmount
> 0) {vmStatus
[d
.symbol
] = d
.vmholdSymbol
++}if (d
.isTrading
) {tradingCount
++}tblStatus
.rows
.push([d
.symbolDetail
.InstrumentID
+ "/" + d
.symbolDetail
.InstrumentName
, d
.holdAmount
== 0 ? '--' : (d
.marketPosition
> 0 ? '多' : '空'), d
.holdPrice
, d
.holdAmount
, d
.holdProfit
, Math
.abs(d
.marketPosition
), d
.open
, d
.st
, d
.cover
, d
.lastPrice
, d
.N])tblMarket
.rows
.push([d
.symbolDetail
.InstrumentID
+ "/" + d
.symbolDetail
.InstrumentName
, d
.symbolDetail
.VolumeMultiple
, _N(d
.symbolDetail
.LongMarginRatio
, 4) + '/' + _N(d
.symbolDetail
.ShortMarginRatio
, 4), (d
.isTrading
? '是#0000ff' : '否#ff0000'), d
.switchCount
, d
.recordsLen
, d
.upLine
, d
.downLine
, d
.stopPrice
, d
.leavePrice
, d
.lastErr
, d
.lastErrTime
])totalHold
+= Math
.abs(d
.holdAmount
)}var now
= new Date()var elapsed
= now
.getTime() - tslastStatus
= '`' + JSON.stringify([tblStatus
, tblMarket
]) + '`\n輪詢耗時: ' + elapsed
+ ' 毫秒, 當(dāng)前時間: ' + _D() + ', 星期' + ['日', '一', '二', '三', '四', '五', '六'][now
.getDay()] + ", 持有品種個數(shù): " + holdSymbol
+ ", 手動恢復(fù)字符串: " + JSON.stringify(vmStatus
)LogStatus(lastStatus
)Sleep(LoopInterval
* 1000)}
}
回測測試、研究
我們選擇幾只股票回測:600519.SH,600690.SH,600006.SH,601328.SH,600887.SH,600121.SH,601633.SH。
其它參數(shù)設(shè)置:
回測時狀態(tài)欄信息輸出:
可以觀察到,海龜交易法這種趨勢跟蹤策略需要在有較大的行情時才會有較好的盈利。在行情反復(fù)震蕩時可能會有一定回撤。
漲幅較大的貴州茅臺貢獻(xiàn)了整體收益的絕大部分,看來選股也是十分重要的因素。并且根據(jù)狀態(tài)欄中顯示的統(tǒng)計數(shù)據(jù)來看,海龜交易法的止損次數(shù)要遠(yuǎn)高于策略成功盈利次數(shù)。這也是策略的思路核心,用較小的頭寸試錯。一旦抓住趨勢突破加倉,抓住肥尾。創(chuàng)造震蕩期損失數(shù)倍的盈利。
完整策略:https://www.fmz.cn/strategy/346551
該策略僅用于回測研究,實盤請自行優(yōu)化、修改。
總結(jié)
以上是生活随笔為你收集整理的说说海龟交易法则的基本原理,如何实现海龟交易策略?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。