暗号通貨界における定量取引の初心者の方は、こちらをぜひご覧ください - 暗号通貨界における定量取引への近道 (V)
前回の記事では、シンプルなグリッド戦略の取引ロジック分析について説明しました。この記事では、この学習戦略の設計を完成させていきます。
-
トランザクションロジック分析
前回の記事では、グリッドの各グリッド ラインを横断し、現在の価格がグリッド ラインの上または下を通過するかどうかを判断すれば、トランザクション アクションをトリガーできることを説明しました。しかし、実際には、まだ多くの論理的な詳細があります。戦略の書き方を理解していない初心者は、「ロジックは非常にシンプルで、コードは数行で済むはずだ」という誤った認識を抱きがちですが、実際の書き方では、詳細はたくさんあります。」考慮する必要がある最初の詳細は、無限グリッドの設計です。前回の記事では、初期のグリッドデータ構造を生成する関数を設計しました。
createNet何?この関数は、有限数のグリッド ラインを持つグリッド データ構造を生成します。では、戦略の実行中に価格がこのグリッド データ構造の境界を超えた場合 (最高価格の上部グリッド ラインと最低価格の下部グリッド ラインを超えた場合) はどうなるでしょうか?
したがって、まずグリッド データ構造に拡張メカニズムを追加する必要があります。戦略の実行を開始するコードである戦略メイン関数の記述を開始します。
var diff = 50 // 全局变量,网格间距,可以设计成参数,方便讲解,我们把这个参数写死在代码里。 function main() { // 实盘开始运行后,从这里开始执行策略代码 var ticker = _C(exchange.GetTicker) // 获取市场最新的行情数据ticker,ticker这个数据的结构参看FMZ API文档:https://www.fmz.com/api#ticker var net = createNet(ticker.Last, diff) // 我们上篇设计的初始构造网格数据结构的函数,这里构造一个网格数据结构net while (true) { // 然后程序逻辑就进入了这个while死循环,策略执行到此将不停的循环执行这里{}符号之内的代码 ticker = _C(exchange.GetTicker) // 死循环代码部分的第一行,获取最新的行情数据,更新给ticker变量 // 检查网格范围 while (ticker.Last >= net[net.length - 1].price) { net.push({ buy : false, sell : false, price : net[net.length - 1].price + diff, }) } while (ticker.Last <= net[0].price) { var price = net[0].price - diff if (price <= 0) { break } net.unshift({ buy : false, sell : false, price : price, }) } // 还有其它代码... } }グリッド データ構造を拡張可能にするコードは次のとおりです (上記のコードから抜粋)。
// 检查网格范围 while (ticker.Last >= net[net.length - 1].price) { // 如果价格超过网格最高价格的网格线 net.push({ // 就在网格最高价格的网格线之后加入一个新的网格线 buy : false, // 初始化卖出标记 sell : false, // 初始化买入标记 price : net[net.length - 1].price + diff, // 在之前最高价格的基础上再加一个网格间距 }) } while (ticker.Last <= net[0].price) { // 如果价格低于网格最低价格的网格线 var price = net[0].price - diff // 区别于向上添加,要注意向下添加新网格线的价格不能小于等于0,所以这里要判断 if (price <= 0) { // 小于等于0就不添加了,跳出这层循环 break } net.unshift({ // 就在网格最低价格的网格线之前添加一个新的网格线 buy : false, sell : false, price : price, }) }次のステップでは、トランザクショントリガーを詳細に実装する方法を検討します。
var diff = 50 var amount = 0.002 // 增加一个全局变量,也可以设计成参数,当然为了简便讲解,我们也写死在策略代码, // 这个参数控制每次网格线上触发交易时的交易量 function main() { var ticker = _C(exchange.GetTicker) var net = createNet(ticker.Last, diff) var preTicker = ticker // 在主循环(死循环)开始前,设置一个变量,记录上一次的行情数据 while (true) { ticker = _C(exchange.GetTicker) // 检查网格范围 while (ticker.Last >= net[net.length - 1].price) { net.push({ buy : false, sell : false, price : net[net.length - 1].price + diff, }) } while (ticker.Last <= net[0].price) { var price = net[0].price - diff if (price <= 0) { break } net.unshift({ buy : false, sell : false, price : price, }) } // 检索网格 for (var i = 0 ; i < net.length ; i++) { // 遍历网格数据结构中的所有网格线 var p = net[i] if (preTicker.Last < p.price && ticker.Last > p.price) { // 上穿,卖出,当前节点已经交易过不论SELL BUY ,都不再交易 if (i != 0) { var downP = net[i - 1] if (downP.buy) { exchange.Sell(-1, amount, ticker) downP.buy = false p.sell = false continue } } if (!p.sell && !p.buy) { exchange.Sell(-1, amount, ticker) p.sell = true } } else if (preTicker.Last > p.price && ticker.Last < p.price) { // 下穿,买入 if (i != net.length - 1) { var upP = net[i + 1] if (upP.sell) { exchange.Buy(-1, amount * ticker.Last, ticker) upP.sell = false p.buy = false continue } } if (!p.buy && !p.sell) { exchange.Buy(-1, amount * ticker.Last, ticker) p.buy = true } } } preTicker = ticker // 把当前的行情数据记录在preTicker中,在下一次循环中,作为“上一次”行情数据和最新的对比,判断上穿下穿 Sleep(500) } }以下が見られます:
- グリッドラインを越える条件:
preTicker.Last < p.price && ticker.Last > p.price - グリッドラインを越える条件:
preTicker.Last > p.price && ticker.Last < p.price
前回の記事でお話しした内容は次のとおりです。
クロスアップまたはクロスダウンは、取引を実行できるかどうかを判断する最初のステップにすぎず、グリッド ライン データのマークを決定することも必要です。
上向きの交差の場合、価格は現在のグリッドラインと最も近いグリッドラインの買いマークよりも低いと判断されます。買いマークの値が真であれば、前のグリッドラインが買われたことを意味します。前のグリッド ラインはリセットされます。ルートの買いマークは false になり、現在のグリッド ラインの売りマークは false にリセットされます。
前の条件を判断した後、トリガーされない場合は判断を続けます。現在のグリッドラインの買い/売りマークが両方とも偽の場合、現在のグリッドラインは取引可能であることを意味します。上向きの交差であるため、ここで売り操作を実行します。実行後、現在のグリッド ラインの売りフラグを true としてマークします。
地下道の処理のロジックは同じです (これは初心者が考える必要があります)。
- グリッドラインを越える条件:
完全な戦略バックテスト
バックテストデータを見るために関数を書く。showTblデータを表示します。
function showTbl(arr) {
var tbl = {
type : "table",
title : "网格",
cols : ["网格信息"],
rows : []
}
var arrReverse = arr.slice(0).reverse()
_.each(arrReverse, function(ele) {
var color = ""
if (ele.buy) {
color = "#FF0000"
} else if (ele.sell) {
color = "#00FF00"
}
tbl.rows.push([JSON.stringify(ele) + color])
})
LogStatus(_D(), "\n`" + JSON.stringify(tbl) + "`", "\n 账户信息:", exchange.GetAccount())
}
完全な戦略コード:
/*backtest
start: 2021-04-01 22:00:00
end: 2021-05-22 00:00:00
period: 1d
basePeriod: 1m
exchanges: [{"eid":"OKEX","currency":"ETH_USDT","balance":100000}]
*/
var diff = 50
var amount = 0.002
function createNet(begin, diff) {
var oneSideNums = 10
var up = []
var down = []
for (var i = 0 ; i < oneSideNums ; i++) {
var upObj = {
buy : false,
sell : false,
price : begin + diff / 2 + i * diff,
}
up.push(upObj)
var j = (oneSideNums - 1) - i
var downObj = {
buy : false,
sell : false,
price : begin - diff / 2 - j * diff,
}
if (downObj.price <= 0) { // 价格不能小于等于0
continue
}
down.push(downObj)
}
return down.concat(up)
}
function showTbl(arr) {
var tbl = {
type : "table",
title : "网格",
cols : ["网格信息"],
rows : []
}
var arrReverse = arr.slice(0).reverse()
_.each(arrReverse, function(ele) {
var color = ""
if (ele.buy) {
color = "#FF0000"
} else if (ele.sell) {
color = "#00FF00"
}
tbl.rows.push([JSON.stringify(ele) + color])
})
LogStatus(_D(), "\n`" + JSON.stringify(tbl) + "`", "\n 账户信息:", exchange.GetAccount())
}
function main() {
var ticker = _C(exchange.GetTicker)
var net = createNet(ticker.Last, diff)
var preTicker = ticker
while (true) {
ticker = _C(exchange.GetTicker)
// 检查网格范围
while (ticker.Last >= net[net.length - 1].price) {
net.push({
buy : false,
sell : false,
price : net[net.length - 1].price + diff,
})
}
while (ticker.Last <= net[0].price) {
var price = net[0].price - diff
if (price <= 0) {
break
}
net.unshift({
buy : false,
sell : false,
price : price,
})
}
// 检索网格
for (var i = 0 ; i < net.length ; i++) {
var p = net[i]
if (preTicker.Last < p.price && ticker.Last > p.price) { // 上穿,卖出,当前节点已经交易过不论SELL BUY ,都不再交易
if (i != 0) {
var downP = net[i - 1]
if (downP.buy) {
exchange.Sell(-1, amount, ticker)
downP.buy = false
p.sell = false
continue
}
}
if (!p.sell && !p.buy) {
exchange.Sell(-1, amount, ticker)
p.sell = true
}
} else if (preTicker.Last > p.price && ticker.Last < p.price) { // 下穿,买入
if (i != net.length - 1) {
var upP = net[i + 1]
if (upP.sell) {
exchange.Buy(-1, amount * ticker.Last, ticker)
upP.sell = false
p.buy = false
continue
}
}
if (!p.buy && !p.sell) {
exchange.Buy(-1, amount * ticker.Last, ticker)
p.buy = true
}
}
}
showTbl(net)
preTicker = ticker
Sleep(500)
}
}
戦略バックテスト:
グリッド戦略の特徴は、トレンド相場では大きな浮動損失が発生し、ボラティリティの高い相場ではリターンが回復するのみであることがわかります。
したがって、グリッド戦略はリスクフリーではありません。スポット戦略は依然として持続可能ですが、先物契約グリッド戦略はよりリスクが高く、グリッドパラメータの保守的な設定が必要です。
为啥判断上穿下穿条件的时候,每根网格线都要判断啊,这里面感觉有个逻辑漏洞啊,不应该是上穿卖出的时候只要遍历高于目前价格的网格线吗? 还有exchange.Sell(-1, amount, ticker)这个函数怎么和api文档里的不一样啊,我看api文档里写的是exchange.Sell(Price, Amount),为啥你有三个参数啊,搞不懂啊,好复杂啊,我人都晕了~
FMZ的API函数中可以产生日志输出的函数例如:Log(...)、exchange.Buy(Price, Amount)、exchange.CancelOrder(Id)等都可以在必要参数后跟一些附带输出参数。https://www.fmz.com/api#exchange.cancelorderid
上穿和下跌时,exchange.Buy(-1, amount * ticker.Last, ticker),amount*ticker.Last是啥意思,为啥sell没有呢?
https://www.fmz.com/strategy/291160
last_tick = []
line = []
grid_buy_list = []
def net(now_price):
global line
print(now_price)
line = [now_price*(1+0.003*i) for i in range(-1000,1000)]
Log(line)
def ontick():
global last_tick
global line
global grid_buy_list
account = exchange.GetAccount()
ticker = exchange.GetTicker()
last_tick.append(ticker['Last'])
if len(last_tick) == 1:return
elif len(last_tick) == 100:del last_tick[0]
for i in range(len(line)):
if last_tick[-1] > line[i] and last_tick[-2] < line[i] and len(grid_buy_list)!= 0 and i > min(grid_buy_list) and account['Stocks'] >= 0.001:
exchange.Sell(last_tick[-1],0.01)
del grid_buy_list[grid_buy_list.index(min(grid_buy_list))]
Log(exchange.GetAccount())
elif last_tick[-1] < line[i] and last_tick[-2] > line[i] and i not in grid_buy_list:
exchange.Buy(last_tick[-1],0.01)
grid_buy_list.append(i)
Log(exchange.GetAccount())
def main():
net(exchange.GetTicker()['Last'])
Log(exchange.GetAccount())
while(True):
ontick()
Sleep(1000)
- 1






