Name
网格变形策略之单边网格
Author
Zero
Strategy Description
网格可以自定义方向 先买后卖: 网格会从首价格开始向下挂买单, 每个买单间隔 "价格间隔" 这个参数, 挂单数量为"单笔数量", 挂够 "总数量" 个买单, 有任意买单成交以后, 程序会在买价基础上加 "价差(元)" 这个参数的的值的价格挂出卖单, 卖出, 卖出以后,重新按原来这个网格的价格挂买入单 先卖后买: 操作刚好相反
策略最大的风险就是单边行情, 价格波动超出网格范围.
网格带有自动止损和移动功能
Strategy Arguments
Argument | Default | Description |
---|---|---|
OpType | 0 | 网格方向: 先买后卖 |
FirstPriceAuto | true | 首价格自动 |
FirstPrice | 100 | 首价格 |
AllNum | 10 | 总数量 |
PriceGrid | true | 价格间隔 |
PriceDiff | 2 | 价差(元) |
AmountType | 0 | 订单大小: 买卖同量 |
AmountOnce | 0.1 | 单笔数量 |
BAmountOnce | 0.1 | 买单大小 |
SAmountOnce | 0.1 | 卖单大小 |
AmountCoefficient | *1 | 量差 |
AmountDot | 3 | 量小数点最长位数 |
EnableProtectDiff | false | 开启价差保护 |
ProtectDiff | 20 | 入市价差保护 |
CancelAllWS | true | 停止时取消所有挂单 |
CheckInterval | 2000 | 轮询间隔 |
Interval | 1300 | 失败重试间隔 |
RestoreProfit | false | 恢复上次盈利 |
LastProfit | false | 上次盈利 |
ProfitAsOrg | false | 上次盈利算入均价 |
EnableAccountCheck | true | 启用资金检验 |
EnableStopLoss | false | 开启止损 |
StopLoss | 100 | 最大浮动亏损(元) |
StopLossMode | 0 | 止损后操作: 回收并退出 |
EnableStopWin | false | 开启止盈 |
StopWin | 120 | 最大浮动盈利(元) |
StopWinMode | 0 | 止盈后操作: 回收并退出 |
AutoMove | false | 自动移动 |
MaxDistance | 20 | 最大距离(元) |
MaxIdle | 7200 | 最大空闲(秒) |
EnableDynamic | false | 开启动态挂单 |
DynamicMax | 30 | 订单失效距离(元) |
ResetData | true | 启动时清空所有数据 |
Precision | 5 | 价格小数位长度 |
XPrecision | 5 | 下单量小数位长度 |
MinStock | 0.001 | 最小交易量 |
Button | Default | Description |
---|---|---|
收网 | button | 停止并平衡到初始状态 |
Source (javascript)
function hasOrder(orders, orderId) {
for (var i = 0; i < orders.length; i++) {
if (orders[i].Id == orderId) {
return true;
}
}
return false;
}
function cancelPending() {
var ret = false;
while (true) {
if (ret) {
Sleep(Interval);
}
var orders = _C(exchange.GetOrders);
if (orders.length == 0) {
break;
}
for (var j = 0; j < orders.length; j++) {
exchange.CancelOrder(orders[j].Id, orders[j]);
ret = true;
}
}
return ret;
}
function valuesToString(values, pos) {
var result = '';
if (typeof(pos) === 'undefined') {
pos = 0;
}
for (var i = pos; i < values.length; i++) {
if (i > pos) {
result += ' ';
}
if (values[i] === null) {
result += 'null';
} else if (typeof(values[i]) == 'undefined') {
result += 'undefined';
} else {
switch (values[i].constructor.name) {
case 'Date':
case 'Number':
case 'String':
case 'Function':
result += values[i].toString();
break;
default:
result += JSON.stringify(values[i]);
break;
}
}
}
return result;
}
function Trader() {
var vId = 0;
var orderBooks = [];
var hisBooks = [];
var orderBooksLen = 0;
this.Buy = function(price, amount, extra) {
if (typeof(extra) === 'undefined') {
extra = '';
} else {
extra = valuesToString(arguments, 2);
}
vId++;
var orderId = "V" + vId;
orderBooks[orderId] = {
Type: ORDER_TYPE_BUY,
Status: ORDER_STATE_PENDING,
Id: 0,
Price: price,
Amount: amount,
Extra: extra
};
orderBooksLen++;
return orderId;
};
this.Sell = function(price, amount, extra) {
if (typeof(extra) === 'undefined') {
extra = '';
} else {
extra = valuesToString(arguments, 2);
}
vId++;
var orderId = "V" + vId;
orderBooks[orderId] = {
Type: ORDER_TYPE_SELL,
Status: ORDER_STATE_PENDING,
Id: 0,
Price: price,
Amount: amount,
Extra: extra
};
orderBooksLen++;
return orderId;
};
this.GetOrders = function() {
var orders = _C(exchange.GetOrders);
for (orderId in orderBooks) {
var order = orderBooks[orderId];
if (order.Status !== ORDER_STATE_PENDING) {
continue;
}
var found = false;
for (var i = 0; i < orders.length; i++) {
if (orders[i].Id == order.Id) {
found = true;
break;
}
}
if (!found) {
orders.push(orderBooks[orderId]);
}
}
return orders;
}
this.GetOrder = function(orderId) {
if (orderId && orderId.toString().indexOf("V") != 0) {
return exchange.GetOrder(orderId);
}
if (typeof(hisBooks[orderId]) !== 'undefined') {
return hisBooks[orderId];
}
if (typeof(orderBooks[orderId]) !== 'undefined') {
return orderBooks[orderId];
}
return null;
};
this.Len = function() {
return orderBooksLen;
};
this.RealLen = function() {
var n = 0;
for (orderId in orderBooks) {
if (orderBooks[orderId].Id > 0) {
n++;
}
}
return n;
};
this.Poll = function(ticker, priceDiff) {
var orders = _C(exchange.GetOrders);
for (orderId in orderBooks) {
var order = orderBooks[orderId];
if (order.Id > 0) {
var found = false;
for (var i = 0; i < orders.length; i++) {
if (order.Id == orders[i].Id) {
found = true;
}
}
if (!found) {
order.Status = ORDER_STATE_CLOSED;
hisBooks[orderId] = order;
delete(orderBooks[orderId]);
orderBooksLen--;
continue;
}
}
var diff = _N(order.Type == ORDER_TYPE_BUY ? (ticker.Buy - order.Price) : (order.Price - ticker.Sell));
var pfn = order.Type == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell;
if (order.Id == 0 && diff <= priceDiff) {
var realId = pfn(order.Price, order.Amount, order.Extra + "(距离: " + diff + (order.Type == ORDER_TYPE_BUY ? (" 买一: " + ticker.Buy) : (" 卖一: " + ticker.Sell))+")");
if (realId) {
order.Id = realId;
}
} else if (order.Id > 0 && diff > (priceDiff + 1)) {
var ok = true;
do {
ok = true;
exchange.CancelOrder(order.Id, "不必要的" + (order.Type == ORDER_TYPE_BUY ? "买单" : "卖单"), "委托价:", order.Price, "量:", order.Amount, ", 距离:", diff, order.Type == ORDER_TYPE_BUY ? ("买一: " + ticker.Buy) : ("卖一: " + ticker.Sell));
Sleep(200);
orders = _C(exchange.GetOrders);
for (var i = 0; i < orders.length; i++) {
if (orders[i].Id == order.Id) {
ok = false;
}
}
} while (!ok);
order.Id = 0;
}
}
};
}
function balanceAccount(orgAccount, initAccount) {
cancelPending();
var nowAccount = _C(exchange.GetAccount);
var slidePrice = 0.2;
var ok = true;
while (true) {
var diff = _N(nowAccount.Stocks - initAccount.Stocks);
if (Math.abs(diff) < MinStock) {
break;
}
var depth = _C(exchange.GetDepth);
var books = diff > 0 ? depth.Bids : depth.Asks;
var n = 0;
var price = 0;
for (var i = 0; i < books.length; i++) {
n += books[i].Amount;
if (n >= Math.abs(diff)) {
price = books[i].Price;
break;
}
}
var pfn = diff > 0 ? exchange.Sell : exchange.Buy;
var amount = Math.abs(diff);
var price = diff > 0 ? (price - slidePrice) : (price + slidePrice);
Log("开始平衡", (diff > 0 ? "卖出" : "买入"), amount, "个币");
if (diff > 0) {
amount = Math.min(nowAccount.Stocks, amount);
} else {
amount = Math.min(nowAccount.Balance / price, amount);
}
if (amount < MinStock) {
Log("资金不足, 无法平衡到初始状态");
ok = false;
break;
}
pfn(price, amount);
Sleep(1000);
cancelPending();
nowAccount = _C(exchange.GetAccount);
}
if (ok) {
LogProfit(_N(nowAccount.Balance - orgAccount.Balance));
Log("平衡完成", nowAccount);
}
}
var STATE_WAIT_OPEN = 0;
var STATE_WAIT_COVER = 1;
var STATE_WAIT_CLOSE = 2;
var ProfitCount = 0;
var BuyFirst = true;
var IsSupportGetOrder = true;
var LastBusy = 0;
function setBusy() {
LastBusy = new Date();
}
function isTimeout() {
if (MaxIdle <= 0) {
return false;
}
var now = new Date();
if (((now.getTime() - LastBusy.getTime()) / 1000) >= MaxIdle) {
LastBusy = now;
return true;
}
return false;
}
function onexit() {
if (CancelAllWS) {
Log("正在退出, 尝试取消所有挂单");
cancelPending();
}
Log("策略成功停止");
Log(_C(exchange.GetAccount));
}
function fishing(orgAccount, fishCount) {
setBusy();
var account = _C(exchange.GetAccount);
Log(account);
var InitAccount = account;
var ticker = _C(exchange.GetTicker);
var amount = _N(AmountOnce);
var amountB = [amount];
var amountS = [amount];
if (typeof(AmountType) !== 'undefined' && AmountType == 1) {
for (var idx = 0; idx < AllNum; idx++) {
amountB[idx] = BAmountOnce;
amountS[idx] = SAmountOnce;
}
} else {
for (var idx = 1; idx < AllNum; idx++) {
switch (AmountCoefficient[0]) {
case '+':
amountB[idx] = amountB[idx - 1] + parseFloat(AmountCoefficient.substring(1));
break;
case '-':
amountB[idx] = amountB[idx - 1] - parseFloat(AmountCoefficient.substring(1));
break;
case '*':
amountB[idx] = amountB[idx - 1] * parseFloat(AmountCoefficient.substring(1));
break;
case '/':
amountB[idx] = amountB[idx - 1] / parseFloat(AmountCoefficient.substring(1));
break;
}
amountB[idx] = _N(amountB[idx], AmountDot);
amountS[idx] = amountB[idx];
}
}
if (FirstPriceAuto) {
FirstPrice = BuyFirst ? _N(ticker.Buy - PriceGrid, Precision) : _N(ticker.Sell + PriceGrid, Precision);
}
// Initialize fish table
var fishTable = {};
var uuidTable = {};
var needStocks = 0;
var needMoney = 0;
var actualNeedMoney = 0;
var actualNeedStocks = 0;
var notEnough = false;
var canNum = 0;
for (var idx = 0; idx < AllNum; idx++) {
var price = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision);
needStocks += amountS[idx];
needMoney += price * amountB[idx];
if (BuyFirst) {
if (_N(needMoney) <= _N(account.Balance)) {
actualNeedMondy = needMoney;
actualNeedStocks = needStocks;
canNum++;
} else {
notEnough = true;
}
} else {
if (_N(needStocks) <= _N(account.Stocks)) {
actualNeedMondy = needMoney;
actualNeedStocks = needStocks;
canNum++;
} else {
notEnough = true;
}
}
fishTable[idx] = STATE_WAIT_OPEN;
uuidTable[idx] = -1;
}
if (!EnableAccountCheck && (canNum < AllNum)) {
Log("警告, 当前资金只可做", canNum, "个网格, 全网共需", (BuyFirst ? needMoney : needStocks), "请保持资金充足");
canNum = AllNum;
}
if (BuyFirst) {
if (EnableProtectDiff && (FirstPrice - ticker.Sell) > ProtectDiff) {
throw "首次买入价比市场卖1价高" + _N(FirstPrice - ticker.Sell, Precision) + ' 元';
} else if (EnableAccountCheck && account.Balance < _N(needMoney)) {
if (fishCount == 1) {
throw "资金不足, 需要" + _N(needMoney) + "元";
} else {
Log("资金不足, 需要", _N(needMoney), "元, 程序只做", canNum, "个网格 #ff0000");
}
} else {
Log('预计动用资金: ', _N(needMoney), "元");
}
} else {
if (EnableProtectDiff && (ticker.Buy - FirstPrice) > ProtectDiff) {
throw "首次卖出价比市场买1价高 " + _N(ticker.Buy - FirstPrice, Precision) + ' 元';
} else if (EnableAccountCheck && account.Stocks < _N(needStocks)) {
if (fishCount == 1) {
throw "币数不足, 需要 " + _N(needStocks) + " 个币";
} else {
Log("资金不足, 需要", _N(needStocks), "个币, 程序只做", canNum, "个网格 #ff0000");
}
} else {
Log('预计动用币数: ', _N(needStocks), "个, 约", _N(needMoney), "元");
}
}
var trader = new Trader();
var OpenFunc = BuyFirst ? exchange.Buy : exchange.Sell;
var CoverFunc = BuyFirst ? exchange.Sell : exchange.Buy;
if (EnableDynamic) {
OpenFunc = BuyFirst ? trader.Buy : trader.Sell;
CoverFunc = BuyFirst ? trader.Sell : trader.Buy;
}
var ts = new Date();
var preMsg = "";
var profitMax = 0;
while (true) {
var now = new Date();
var table = null;
if (now.getTime() - ts.getTime() > 5000) {
if (typeof(GetCommand) == 'function' && GetCommand() == "收网") {
Log("开始执行命令进行收网操作");
balanceAccount(orgAccount, InitAccount);
return false;
}
ts = now;
var nowAccount = _C(exchange.GetAccount);
var ticker = _C(exchange.GetTicker);
if (EnableDynamic) {
trader.Poll(ticker, DynamicMax);
}
var amount_diff = (nowAccount.Stocks + nowAccount.FrozenStocks) - (InitAccount.Stocks + InitAccount.FrozenStocks);
var money_diff = (nowAccount.Balance + nowAccount.FrozenBalance) - (InitAccount.Balance + InitAccount.FrozenBalance);
var floatProfit = _N(money_diff + (amount_diff * ticker.Last));
var floatProfitAll = _N((nowAccount.Balance + nowAccount.FrozenBalance - orgAccount.Balance - orgAccount.FrozenBalance) + ((nowAccount.Stocks + nowAccount.FrozenStocks - orgAccount.Stocks - orgAccount.FrozenStocks) * ticker.Last));
var isHold = Math.abs(amount_diff) >= MinStock;
if (isHold) {
setBusy();
}
profitMax = Math.max(floatProfit, profitMax);
if (EnableAccountCheck && EnableStopLoss) {
if ((profitMax - floatProfit) >= StopLoss) {
Log("当前浮动盈亏", floatProfit, "利润最高点: ", profitMax, "开始止损");
balanceAccount(orgAccount, InitAccount);
if (StopLossMode == 0) {
throw "止损退出";
} else {
return true;
}
}
}
if (EnableAccountCheck && EnableStopWin) {
if (floatProfit > StopWin) {
Log("当前浮动盈亏", floatProfit, "开始止盈");
balanceAccount(orgAccount, InitAccount);
if (StopWinMode == 0) {
throw "止盈退出";
} else {
return true;
}
}
}
var distance = 0;
if (EnableAccountCheck && AutoMove) {
if (BuyFirst) {
distance = ticker.Last - FirstPrice;
} else {
distance = FirstPrice - ticker.Last;
}
var refish = false;
if (!isHold && isTimeout()) {
Log("空仓过久, 开始移动网格");
refish = true;
}
if (distance > MaxDistance) {
Log("价格超出网格区间过多, 开始移动网格, 当前距离: ", _N(distance, Precision), "当前价格:", ticker.Last);
refish = true;
}
if (refish) {
balanceAccount(orgAccount, InitAccount);
return true;
}
}
var holdDirection, holdAmount = "--",
holdPrice = "--";
if (isHold) {
if (RestoreProfit && ProfitAsOrg) {
if (BuyFirst) {
money_diff += LastProfit;
} else {
money_diff -= LastProfit;
}
}
holdAmount = amount_diff;
holdPrice = (-money_diff) / amount_diff;
if (!BuyFirst) {
holdAmount = -amount_diff;
holdPrice = (money_diff) / -amount_diff;
}
holdAmount = _N(holdAmount, 4);
holdPrice = _N(holdPrice, Precision);
holdDirection = BuyFirst ? "多" : "空";
} else {
holdDirection = "--";
}
table = {
type: 'table',
title: '运行状态',
cols: ['动用资金', '持有仓位', '持仓大小', '持仓均价', '总浮动盈亏', '当前网格盈亏', '撒网次数', '网格偏移', '真实委托', '最新币价'],
rows: [
[_N(actualNeedMondy, 4), holdDirection, holdAmount, holdPrice, _N(floatProfitAll, 4) + ' ( ' + _N(floatProfitAll * 100 / actualNeedMondy, 4) + ' % )', floatProfit, fishCount, (AutoMove && distance > 0) ? ((BuyFirst ? "向上" : "向下") + "偏离: " + _N(distance) + " 元") : "--", trader.RealLen(), ticker.Last]
]
};
}
var orders = _C(trader.GetOrders);
if (table) {
if (!EnableDynamic) {
table.rows[0][8] = orders.length;
}
LogStatus('`' + JSON.stringify(table) + '`');
}
for (var idx = 0; idx < canNum; idx++) {
var openPrice = _N((BuyFirst ? FirstPrice - (idx * PriceGrid) : FirstPrice + (idx * PriceGrid)), Precision);
var coverPrice = _N((BuyFirst ? openPrice + PriceDiff : openPrice - PriceDiff), Precision);
var state = fishTable[idx];
var fishId = uuidTable[idx];
if (hasOrder(orders, fishId)) {
continue;
}
if (fishId != -1 && IsSupportGetOrder) {
var order = trader.GetOrder(fishId);
if (!order) {
Log("获取订单信息失败, ID: ", fishId);
continue;
}
if (order.Status == ORDER_STATE_PENDING) {
//Log("订单状态为未完成, ID: ", fishId);
continue;
}
}
if (state == STATE_WAIT_COVER) {
var coverId = CoverFunc(coverPrice, (BuyFirst ? amountS[idx] : amountB[idx]), (BuyFirst ? '完成买单:' : '完成卖单:'), openPrice, '量:', (BuyFirst ? amountB[idx] : amountS[idx]));
if (coverId) {
fishTable[idx] = STATE_WAIT_CLOSE;
uuidTable[idx] = coverId;
}
} else if (state == STATE_WAIT_OPEN || state == STATE_WAIT_CLOSE) {
var openId = OpenFunc(openPrice, BuyFirst ? amountB[idx] : amountS[idx]);
if (openId) {
fishTable[idx] = STATE_WAIT_COVER;
uuidTable[idx] = openId;
if (state == STATE_WAIT_CLOSE) {
ProfitCount++;
var account = _C(exchange.GetAccount);
var ticker = _C(exchange.GetTicker);
var initNet = _N(((InitAccount.Stocks + InitAccount.FrozenStocks) * ticker.Buy) + InitAccount.Balance + InitAccount.FrozenBalance, 8);
var nowNet = _N(((account.Stocks + account.FrozenStocks) * ticker.Buy) + account.Balance + account.FrozenBalance, 8);
var actualProfit = _N(((nowNet - initNet)) * 100 / initNet, 8);
if (AmountType == 0) {
var profit = _N((ProfitCount * amount * PriceDiff) + LastProfit, 8);
Log((BuyFirst ? '完成卖单:' : '完成买单:'), coverPrice, '量:', (BuyFirst ? amountS[idx] : amountB[idx]), '平仓收益', profit);
} else {
Log((BuyFirst ? '完成卖单:' : '完成买单:'), coverPrice, '量:', (BuyFirst ? amountS[idx] : amountB[idx]));
}
}
}
}
}
Sleep(CheckInterval);
}
return true;
}
function main() {
if (ResetData) {
LogProfitReset();
LogReset();
}
exchange.SetPrecision(Precision, XPrecision)
if (typeof(AmountType) === 'undefined') {
AmountType = 0;
}
if (typeof(AmountDot) === 'undefined') {
AmountDot = 3;
}
if (typeof(EnableDynamic) === 'undefined') {
EnableDynamic = false;
}
if (typeof(AmountCoefficient) === 'undefined') {
AmountCoefficient = "*1";
}
if (typeof(EnableAccountCheck) === 'undefined') {
EnableAccountCheck = true;
}
BuyFirst = (OpType == 0);
IsSupportGetOrder = exchange.GetName().indexOf('itstamp') == -1;
if (!IsSupportGetOrder) {
Log(exchange.GetName(), "不支持GetOrder, 可能影响策略稳定性.");
}
SetErrorFilter("502:|503:|S_U_001|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|refused|EOF|When");
exchange.SetRate(1);
Log('已经禁用汇率转换, 当前货币为', exchange.GetBaseCurrency());
if (!RestoreProfit) {
LastProfit = 0;
}
var orgAccount = _C(exchange.GetAccount);
var fishCount = 1;
while (true) {
if (!fishing(orgAccount, fishCount)) {
break;
}
fishCount++;
Log("第", fishCount, "次重新撒网...");
FirstPriceAuto = true;
Sleep(1000);
}
}
Detail
https://www.fmz.com/strategy/629
Last Modified
2020-02-04 16:33:00