/*backtest start: 2020-01-01 00:00:00 end: 2023-03-24 00:00:00 period: 1d basePeriod: 1h exchanges: [{"eid":"Futures_Binance","currency":"ETH_USDT","fee":[0.018,0.036]}] */ //地址https://www.fmz.com/strategy/405725 const QuotePrecision = 10;//下单价格精度 const BasePrecision = 10;//下单量精度 const long = 'long';//做多-期货 const closelong = 'closelong';//做多-平仓 const short = 'short';//做空-期货 const closeshort = 'closeshort';//做空-平仓 const isTest = true;//是不是币安测试API const minPriceNum = 2;//保留几位小数点(买卖价格最低设置类似1660.17usdt) const marginLevel = 1;//杆杠大小 let lastKLineTime = 0;//最后一个结束的k线时间戳(比如现在10:30,那就是9点时间戳) const priceAdd = 0.01;//交易时改变1%设置价格 let lastOrderTime = 0;//最后一次下订单时间戳-秒 const serviceCharge = 0.0004;//手续费0.04% let signalData = {};//订单数据-用于订单交易成功时使用 let maintMarginPro = 0.005;//维持保证金比率0.50% let maintMarginNum = 0;//维持保证金速算额 (USDT) const cfgName1 = "k线图表"; var registerInfo = {}; var chart = null; var arrCfg = []; const maxBufferLen = 10;// let beginMoney = 0;//启动时的总资产 let exchangeNum = 0;//本次交易次数(开仓+平仓算2次) const lastNumPro_MarketPrice = 0.02;//因为用市价不好把握价格,空间留大点 const buyMinPro = 0.02;//剩余钱不到总钱2%,不操作,避免小额交易 let period = 0;//K线周期-秒 const atrNum = 6; const stopKLineNum = 0;//出现止损,休息()个K线 const pp = 12;//数值越大,格子差距越小 const StopGrid = 16;//止损网格价格倍数 let lastStopTime = 0; const checkTime = 1500;//每隔多少秒检测一下代码-1小时 let lastCheckTime = 0;//上次检测时间戳-秒 let currency; let nowMarkPrice = 0; let nowPosition; let nowAccount; function main() { testLog("调用main"); let extension = { layout: 'single',//single:图表不会叠加(不会以分页标签方式显示),会单独显示(平铺显示),默认为分组 'group' col: 12,//设置图表的页面宽度,1-12 height: '700px'//图表的高度 } Sleep(1000); while (true) { setNowData(); let bars = _C(exchange.GetRecords);//只能获得最近300个周期的数据? PlotMultRecords(bars, cfgName1, "k线", extension);//画k线图 drawLine(bars);//画sma线,策略 Sleep(1000 * 10);//每秒运行下代码 } } /** 活动最新数据 */ function setNowData() { setMarkPrice(); setNowPosition(); setNowAccount(); } // 初始化函数 function init() { _CDelay(1000 * 10);// 调整_C()函数重试时间间隔为n秒 testLog("初始化!"); // 设置精度(小数点位数,回测不支持) exchange.SetPrecision(QuotePrecision, BasePrecision); exchange.SetContractType("swap");//永续合约 if (isTest) { exchange.SetBase("https://testnet.binancefuture.com");// 切换为测试的地址 } let arr = exchange.GetCurrency().split('_');//ETH_USDT=>ETH_USDT currency = arr[0] + arr[1]; Log('交易对:' + currency); setNowData(); log_ticker("初始化"); log_account("初始化"); beginMoney = getAllMoney(); chart = Chart(arrCfg); period = _C(exchange.GetPeriod);//K线周期 let periodTxt = getTimeTxt(period); testLog("K线周期:" + periodTxt); } /** 画sma均线 */ function drawLine(bars) { // 使用现货交易所对象测试,获取K线数据。如果使用期货交易所对象测试,需要先设置合约 if (!bars) { return; } if (lastKLineTime == 0) {//第一次启动,从头开始 var begin = atrNum - 1; } else { begin = bars.length - 2;//理论上只看最新一个数据就行,但为了防止误差,看2个 } if (begin < 0) { return; } let atr = talib.ATR(bars, atrNum);//计算平均价格振幅 for (let i = begin; i < bars.length; i++) { let bar = bars[i]; if (bar.Time <= lastKLineTime) {//已经计算、画图过的 continue; } let avg_pra = atr[i] / bar.Close let hl2 = (bar.High + bar.Low) / 2; //计算网格 let grid = hl2 * avg_pra / pp; let p1 = hl2 + grid * StopGrid; let p2 = hl2 + grid * 2; let p0 = hl2; let p3 = hl2 - grid * 2; let p4 = hl2 - grid * StopGrid; //------------画k线图等 if (i < bars.length - 1) { lastKLineTime = bar.Time; //最后一条数据不完整,结束画 PlotMultLine(cfgName1, "p1", p1, bar.Time, '#FF9800'); PlotMultLine(cfgName1, "p2", p2, bar.Time, '#265ec5'); PlotMultLine(cfgName1, "p0", p0, bar.Time, '#7c7c7c'); PlotMultLine(cfgName1, "p3", p3, bar.Time, '#265ec5'); PlotMultLine(cfgName1, "p4", p4, bar.Time, '#FF9800'); continue; } //------------以最后一条数据获得的sma值进行交易判断(for循环只进最后一次) // ======================== 策略操作 - 开始 ============================ if (hasOrder()) {//有未完成的订单 return; } let nowTime = getNowTime(); if ((lastCheckTime + checkTime) <= nowTime) { lastCheckTime = nowTime;//每隔多少秒检测一下代码 } else { return; } if ((lastStopTime + stopKLineNum * period * 1000) > bar.Time) { return; } //只做多 if (bar.Close < p4) { let funTxt = 'Fun_止损'; let isSuccess = ExchangeFun(closelong, funTxt); if (isSuccess) { lastStopTime = bar.Time; } } else if (bar.Close < p3) { let funTxt = 'Fun_做多'; ExchangeFun(long, funTxt); } else if (bar.Close > p2) { let funTxt = 'Fun_止盈'; ExchangeFun(closelong, funTxt); } } } /** 有没有未完成的订单 */ function hasOrder() { let orders = _C(exchange.GetOrders);//获取所有未完成的订单 if (!orders) { return true; } if (orders.length == 0 && signalData && signalData.FunTxt) { let order = _C(exchange.GetOrder, signalData.Id); if (order) { exchangeNum++; let nowTime = getNowTime(); let timeTxtxt = getTimeTxt(nowTime - lastOrderTime); let txt = signalData.FunTxt + ',报价:' + order.Price + ',均价:' + order.AvgPrice + ',量:' + order.Amount + '%' + ",次数:" + exchangeNum + ",Action:" + signalData.Action + ",耗时:" + timeTxtxt; Log("end订单成功----" + txt); log_account("end----"); PlotMultFlag(cfgName1, "flag1", new Date().getTime(), txt, signalData.FunTxt); signalData = {}; } } if (orders.length > 0) { return true; } else { return false; } } //做多做空等实际功能 function ExchangeFun(action, funTxt, percent = 1) { let amount; let tradeInfo; let markPrice = getMarkPrice();//标记价格 if (action == long || action == closeshort) {//买 var price = markPrice * (1 + priceAdd); } else if (action == short || action == closelong) {//卖 price = markPrice * (1 - priceAdd); } price = _floor(markPrice, minPriceNum); let min = getMinNum();//eth最小操作0.001个 if (action == long) {//做多 if (!canBuy()) {//避免小额交易 return false; } amount = getBuyNum(price, 0);//购买多少个eth if (amount >= min) { beginTrans(); exchange.SetMarginLevel(marginLevel);//设置杆杠大小 exchange.SetDirection("buy"); tradeInfo = exchange.Buy(-1, amount);//市价 } } else if (action == short) {//做空 if (!canBuy()) {//避免小额交易 return false; } amount = getBuyNum(price, 1);//做空卖多少个eth if (amount >= min) { beginTrans(); exchange.SetMarginLevel(marginLevel);//设置杆杠大小 exchange.SetDirection("sell"); tradeInfo = exchange.Sell(-1, amount); } } else if (action == closelong) {//平仓-做多 amount = getCoinNum(0) * percent;//卖掉多少个期货eth if (amount >= min) { beginTrans(); exchange.SetDirection("closebuy"); tradeInfo = exchange.Sell(-1, amount); } } else if (action == closeshort) {//平仓-做空 amount = getCoinNum(1) * percent; if (amount >= min) { beginTrans(); exchange.SetDirection("closesell"); tradeInfo = exchange.Buy(-1, amount); } } if (tradeInfo) { signalData = { 'Action': action, 'Id': tradeInfo, 'FunTxt': funTxt }; log_ticker("end----,订单Action:" + action + ",price:" + price); return true; } return false; } /** 开始交易 */ function beginTrans() { lastOrderTime = getNowTime(); log_account("bengin----"); } function log_account(txt) { let acc = GetAcc(); let markPrice = getMarkPrice(); testLog(txt + ",可用余额:" + acc.Balance + ",标记价格:" + markPrice + ",账户:" + JSON.stringify(acc)); let position = getNowPosition(); if (position.length > 0) { let data = position[0];//默认持仓就1个 let typeTxt = ''; if (data.Type == 0) { typeTxt = '做多'; } else if (data.Type == 1) { typeTxt = '做空'; } let leverageTxt = ''; if (data.Info) {//回测时没有? leverageTxt = ",杠杆:" + data.Info.leverage; } testLog("持仓信息 eth:" + data.Amount + ",Price:" + data.Price // , ",利润:", data.Profit + ",Type:" + typeTxt + leverageTxt + ",详情:" + JSON.stringify(data)); } } /** 获得账号信息 */ function GetAcc() { return nowAccount; } /** 设置账号信息 */ function setNowAccount() { let acc = _C(exchange.GetAccount); if (acc.Info) { acc.Info.assets = null; acc.Info.positions = null; } nowAccount = acc; } function log_ticker(txt) { testLog(txt); } /** 预计可以购买多少个eth 0做多,1做空 */ function getBuyNum(price, type) { let acc = GetAcc(); let balance = acc.Balance;//有多少usdt if (balance < 100000) { maintMarginPro = 0.005;//维持保证金比率 maintMarginNum = 0;//维持保证金速算额 (USDT) } else if (balance < 250000) { maintMarginPro = 0.0065; maintMarginNum = 150; } else if (balance < 2000000) { maintMarginPro = 0.01; maintMarginNum = 1025; } else if (balance < 10000000) { maintMarginPro = 0.02; maintMarginNum = 21025; } else { //暂不处理 } balance -= maintMarginNum; let markPrice = getMarkPrice();//标记价格 let lossType;//开仓亏损:订单方向 //尽量多买,避免小额多次交易 if (type == 0) {//做多 lossType = 1; } else if (type == 1) {//做空 lossType = -1; } if (type == 0) {//做多 var usePrice = price; } else if (type == 1) {//做空 usePrice = Math.max(price, markPrice); } // 开仓亏损=合约数量* 绝对值{min[0, 订单方向* (标记价格- 订单价格)]} let losePro = Math.abs(Math.min(0, lossType * (markPrice - price))); // 订单价格: 限价单:我挂的价格 市价单:卖一 // 买单(做多),最大可用资金可用/{订单价格/杠杆倍数+绝对值(min0,标记价格-订单价格)} // 卖单(做空),最大可用资金可用/{max(标记价,订单价格)/杠杆倍数+绝对值(min0,订单价格-标记价)} let amount = balance / (usePrice / marginLevel + losePro);//计算开仓亏损 //扣的钱按全部购买算 amount /= (1 + serviceCharge);//计算手续费 amount /= (1 + maintMarginPro);//计算维持保证金 amount *= (1 - lastNumPro_MarketPrice);//保留点 amount = _floor(amount); return amount; } /** 期货仓库里有多少个eth,type:0做多的仓,1做空的仓 */ function getCoinNum(type) { var position = getNowPosition(); if (position.length > 0) { let data = position[0];//默认持仓就1个 // let num = _floor(data.Amount); let num = data.Amount; if (data.Type == type) { return num; } } return 0; } /** 剩余钱不到总钱2%,不操作,避免小额交易 */ function canBuy() { var position = getNowPosition(); if (position.length > 0) { let acc = GetAcc(); let allMoney = getAllMoney(); if (acc.Balance / allMoney < (buyMinPro + maintMarginPro)) { return false; } } return true; } /** 估计当前总资产 */ function getAllMoney() { let acc = GetAcc(); let allMoney = acc.Balance;//可用余额 allMoney += acc.FrozenBalance;//冻结余额 var position = getNowPosition(); if (position.length > 0) { let data = position[0];//默认持仓就1个 let markPrice = getMarkPrice(); if (data.Type == 0) {//做多 allMoney += data.Amount * markPrice; } else if (data.Type == 1) {//做空 allMoney += data.Amount * data.Price;//做空前的本金 allMoney += (data.Price - markPrice) * data.Amount;//做空的收益 } } return allMoney; } /** 获得标记价格 */ function getMarkPrice() { return nowMarkPrice; } /** 获得标记价格 */ function setMarkPrice() { //模拟回测 let ticker = _C(exchange.GetTicker);//获得行情数据最多10分钟 nowMarkPrice = ticker.Last;//最后一次交易价格 } /** 最少买0.001个eth */ function getMinNum() { return 0.001; } /** 向下取整,保留3位小数点(开仓手数:最低设置0.001个ETH) */ function _floor(num, min = 3) { num = Math.floor(num * Math.pow(10, min)) / Math.pow(10, min); num = parseFloat(num);//string转Float return num; } /** 返回当前时间戳-秒 */ function getNowTime() { return Unix(); } /** 画k线图 */ function PlotMultRecords(bars, cfgName, seriesName, extension) { var index = -1; var eleIndex = -1; do { var cfgInfo = registerInfo[cfgName]; if (typeof (cfgInfo) == "undefined") { var cfg = { name: cfgName, __isStock: true, title: { text: cfgName }, tooltip: { xDateFormat: '%Y-%m-%d %H:%M:%S, %A' }, legend: { enabled: true, }, plotOptions: { candlestick: { color: '#d75442', upColor: '#6ba583' } }, rangeSelector: { buttons: [{ type: 'hour', count: 1, text: '1h' }, { type: 'hour', count: 3, text: '3h' }, { type: 'hour', count: 8, text: '8h' }, { type: 'all', text: 'All' }], selected: 2, inputEnabled: true }, series: [{ type: 'candlestick', name: seriesName, id: seriesName, data: [] }], } if (typeof (extension) != "undefined") { cfg.extension = extension; } registerInfo[cfgName] = { "cfgIdx": arrCfg.length, "seriesIdxs": [{ seriesName: seriesName, index: arrCfg.length, type: "candlestick", preBarTime: 0 }], }; arrCfg.push(cfg); updateSeriesIdx(); } chart.update(arrCfg);//刷新所有图表 _.each(registerInfo[cfgName].seriesIdxs, function (ele, i) { if (ele.seriesName == seriesName && ele.type == "candlestick") { index = ele.index; eleIndex = i; } }); if (index == -1) { arrCfg[registerInfo[cfgName].cfgIdx].series.push({ type: 'candlestick', name: seriesName, id: seriesName, data: [] }); registerInfo[cfgName].seriesIdxs.push({ seriesName: seriesName, index: arrCfg.length, type: "candlestick", preBarTime: 0 }); updateSeriesIdx(); } } while (index == -1) for (var i = 0; i < bars.length; i++) { let bar = bars[i]; let data = [bar.Time, bar.Open, bar.High, bar.Low, bar.Close];//写入图表的一个周期数据 let preBarTime = registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime; if (bar.Time == preBarTime) { chart.add(index, data, -1);//更新当前周期 } else if (bar.Time > preBarTime) { registerInfo[cfgName].seriesIdxs[eleIndex].preBarTime = bar.Time chart.add(index, data);//添加历史(不变了) } } } function updateSeriesIdx() { var index = 0 var map = {} _.each(arrCfg, function (cfg) { _.each(cfg.series, function (series) { var key = cfg.name + "|" + series.name map[key] = index index++ }) }) for (var cfgName in registerInfo) { _.each(arrCfg, function (cfg, cfgIdx) { if (cfg.name == cfgName) { registerInfo[cfgName].cfgIdx = cfgIdx } }) for (var i in registerInfo[cfgName].seriesIdxs) { var seriesName = registerInfo[cfgName].seriesIdxs[i].seriesName var key = cfgName + "|" + seriesName if (typeof (map[key]) != "undefined") { registerInfo[cfgName].seriesIdxs[i].index = map[key] } if (registerInfo[cfgName].seriesIdxs[i].type == "candlestick") { registerInfo[cfgName].seriesIdxs[i].preBarTime = 0 } else if (registerInfo[cfgName].seriesIdxs[i].type == "line") { registerInfo[cfgName].seriesIdxs[i].preDotTime = 0 } else if (registerInfo[cfgName].seriesIdxs[i].type == "flag") { registerInfo[cfgName].seriesIdxs[i].preFlagTime = 0 } } } if (!chart) { chart = Chart(arrCfg) } chart.update(arrCfg) chart.reset() _G("registerInfo", registerInfo) _G("arrCfg", arrCfg) for (var cfgName in registerInfo) { for (var i in registerInfo[cfgName].seriesIdxs) { var buffer = registerInfo[cfgName].seriesIdxs[i].buffer var index = registerInfo[cfgName].seriesIdxs[i].index if (buffer && buffer.length != 0 && registerInfo[cfgName].seriesIdxs[i].type == "line" && registerInfo[cfgName].seriesIdxs[i].preDotTime == 0) { _.each(buffer, function (obj) { chart.add(index, [obj.ts, obj.dot]) registerInfo[cfgName].seriesIdxs[i].preDotTime = obj.ts }) } else if (buffer && buffer.length != 0 && registerInfo[cfgName].seriesIdxs[i].type == "flag" && registerInfo[cfgName].seriesIdxs[i].preFlagTime == 0) { _.each(buffer, function (obj) { chart.add(index, obj.data) registerInfo[cfgName].seriesIdxs[i].preFlagTime = obj.ts }) } } } } function checkBufferLen(buffer, maxLen) { while (buffer.length > maxLen) { buffer.shift() } } /** 画线条 */ function PlotMultLine(cfgName, seriesName, dot, ts, color, extension) { var index = -1; var eleIndex = -1; do { var cfgInfo = registerInfo[cfgName] if (typeof (cfgInfo) == "undefined") { var cfg = { name: cfgName, __isStock: true, title: { text: cfgName }, xAxis: { type: 'datetime' }, series: [{ type: 'line', name: seriesName, id: seriesName, color: color, data: [], }] }; if (extension) { cfg.extension = extension; } registerInfo[cfgName] = { "cfgIdx": arrCfg.length, "seriesIdxs": [{ seriesName: seriesName, index: arrCfg.length, type: "line", color: color, buffer: [], preDotTime: 0 }], }; arrCfg.push(cfg); updateSeriesIdx(); } chart.update(arrCfg); _.each(registerInfo[cfgName].seriesIdxs, function (ele, i) { if (ele.seriesName == seriesName && ele.type == "line") { index = ele.index; eleIndex = i; } }) if (index == -1) { arrCfg[registerInfo[cfgName].cfgIdx].series.push({ type: 'line', name: seriesName, id: seriesName, color: color, data: [], }); registerInfo[cfgName].seriesIdxs.push({ seriesName: seriesName, index: arrCfg.length, type: "line", color: color, buffer: [], preDotTime: 0 }); updateSeriesIdx(); } } while (index == -1) if (typeof (ts) == "undefined") { ts = new Date().getTime(); } var buffer = registerInfo[cfgName].seriesIdxs[eleIndex].buffer; if (registerInfo[cfgName].seriesIdxs[eleIndex].preDotTime != ts) { registerInfo[cfgName].seriesIdxs[eleIndex].preDotTime = ts; chart.add(index, [ts, dot]); buffer.push({ ts: ts, dot: dot }); checkBufferLen(buffer, maxBufferLen); } else { chart.add(index, [ts, dot], -1); buffer[buffer.length - 1].dot = dot; } } /** 画flag */ function PlotMultFlag(cfgName, seriesName, ts, text, title, shape, color, onSeriesName) { if (typeof (cfgName) == "undefined" || typeof (registerInfo[cfgName]) == "undefined") { throw "need cfgName!"; } var index = -1; var eleIndex = -1; do { chart.update(arrCfg); _.each(registerInfo[cfgName].seriesIdxs, function (ele, i) { if (ele.seriesName == seriesName && ele.type == "flag") { index = ele.index; eleIndex = i; } }); if (index == -1) { arrCfg[registerInfo[cfgName].cfgIdx].series.push({ type: 'flags', name: seriesName, onSeries: onSeriesName || arrCfg[registerInfo[cfgName].cfgIdx].series[0].id, data: [] }); registerInfo[cfgName].seriesIdxs.push({ seriesName: seriesName, index: arrCfg.length, type: "flag", buffer: [], preFlagTime: 0 }); updateSeriesIdx(); } } while (index == -1) if (typeof (ts) == "undefined") { ts = new Date().getTime(); } var buffer = registerInfo[cfgName].seriesIdxs[eleIndex].buffer; var obj = { x: ts, color: color, shape: shape, title: title, text: text }; if (registerInfo[cfgName].seriesIdxs[eleIndex].preFlagTime != ts) { registerInfo[cfgName].seriesIdxs[eleIndex].preFlagTime = ts; chart.add(index, obj); buffer.push({ ts: ts, data: obj }); checkBufferLen(buffer, maxBufferLen); } else { chart.add(index, obj, -1); buffer[buffer.length - 1].data = obj; } } function testLog(txt) { Log(txt); } function getNowPosition() { return nowPosition; } function setNowPosition() { nowPosition = _C(exchange.GetPosition); } /** 把时间秒改成天\小时等 */ function getTimeTxt(time) { if (time < 60) { var timeTxt = time; var key = '秒'; } else if (time < 3600) { timeTxt = time / 60; key = '分钟'; } else if (time < 3600 * 24) { timeTxt = time / 3600; key = '小时'; } else { timeTxt = time / 3600 / 24; key = '天'; } timeTxt = Math.floor(timeTxt * 10) / 10 + key;//保留1位小数点 return timeTxt; }
enrichuk 你的这个逻辑是什么?没看明白,全程是平仓后直接开多,未见开空过,无脑开多,套了一直扛?
sxj3388 回测漂亮,实盘亏?
kuailefeng2020 回测K线精度不够,所以跑出来效果很好,实际你用5分钟k线,精度比D1的k线精度高得多,测试下来净是亏。
如我初来 使用这个进行回测之后。开始较为稳定的亏损了 /*backtest start: 2022-04-19 00:00:00 end: 2023-04-21 00:00:00 period: 1h basePeriod: 1m exchanges: [{"eid":"Futures_Binance","currency":"LTC_USDT","balance":200,"fee":[0.02,0.04],"balance":1000}] */
如我初来 不少变量的初始化感觉有点怪,例如下面的。。强迫症选手,看着难受 if (type == 0) {//做多 var usePrice = price; } else if (type == 1) {//做空 usePrice = Math.max(price, markPrice); }
xiaode123 直接跑3年会报错,大概是市场不够了 如果一年年来 20-21年,收益49倍 21-22年,收益232倍 22-2023-03-24年,收益89倍 一共100万倍,更吓人了😅
xiaode123 加了isTest,可以去币安测试网实盘了(本人就跑了一会,出bug是正常的)
xiaode123 这是回测版本,实盘的币安测试网和正式的没去调,因为我不信这数据,就没去改了
xiaode123 fmz回测年化10w倍收益,太吓人,怀疑是fmz回测机制恰好碰到我的代码,出bug了,真拿去跑别怪我啊!!!
sxj3388 有时候没按规则来好象,没单子后c>p2也在做多,c<p3时多单也没止损,原因好象是实时盘中的p2,p3位置变高了.这个策略主做振荡行情,因为大多数时间是振荡。可另配一个趋势策略互补。
xiaode123 Fun_做多就是开仓,止盈止损就是平仓
sxj3388 开平仓规则是哪几行代码?没搞懂。
xiaode123 反正跑一会效果没这么明显
如我初来 我猜也是很奇怪的问题导致的, 准备翻译成java,本地回测下