Die Strategie ist eine Strategie, die lange Zeit für die digitale Währung 796 Futures Exchange angewandt wurde, wobei Futures-Kontrakte Münz-Basis sind, d.h. das Sicherheitsgeld wird von Münzen abgezogen (z.B. BTC-Kontrakte abzüglich BTC), wobei die Einheitsmenge nach dem Vertrag eine kleine Zahl sein kann, ähnlich wie bei Münz-Basis-Kontrakten. Die Strategie ist eine gute Strategie für das Lernen von Strategie-Design, Logikverarbeitung und Strategie-Learning.
Strategie-Code-Anmerkung
var FirstTradeType = [ORDER_TYPE_BUY, ORDER_TYPE_SELL][OpType]; // 首次开仓方向,根据参数OpType开仓方向确定,全局变量FirstTradeType的值为ORDER_TYPE_BUY或者ORDER_TYPE_SELL
var OrgAccount = null; // 全局变量,记录账户资产
var Counter = {s : 0, f: 0}; // 声明一个变量Counter,值初始化为一个对象(类似的结构python叫字典),s代表胜的次数,f代表负的次数
var LastProfit = 0; // 最近盈亏
var AllProfit = 0; // 总盈亏
var _Failed = 0; // 止损次数
// 取消挂单列表中某个ID的订单之外的所有订单,当不传orderId参数时,取消当前交易对所有挂单,参数e为交易所对象的引用,例如传入exchange作为参数,此刻e就是exchange的别名
function StripOrders(e, orderId) {
var order = null; // 初始化变量 order 为空
if (typeof(orderId) == 'undefined') { // 如果参数 orderId 传入时,没有写,则typeof(orderId) == 'undefined'成立,执行if语句的代码块,orderId 赋值为null
orderId = null;
}
while (true) { // 处理循环
var dropped = 0; // 处理标记次数
var orders = _C(e.GetOrders); // 调用GetOrders获取当前挂单(未完全成交的订单),赋值给orders
for (var i = 0; i < orders.length; i++) { // 遍历未成交订单列表 orders
if (orders[i].Id == orderId) { // 如果订单ID和参数上传入的订单ID orderId 相同则给函数内局部变量order赋值orders[i],orders[i]即遍历时当前的订单结构
order = orders[i];
} else { // 如果ID不相同,执行撤销操作
var extra = ""; // 根据部分成交情况,设置扩展信息extra
if (orders[i].DealAmount > 0) {
extra = "成交: " + orders[i].DealAmount;
} else {
extra = "未成交";
}
e.SetDirection(orders[i].Type == ORDER_TYPE_BUY ? "buy" : "sell");
e.CancelOrder(orders[i].Id, orders[i].Type == ORDER_TYPE_BUY ? "买单" : "卖单", extra); // 撤单操作,附带输出extra信息,在日志上会显示
dropped++; // dropped 计数累计
}
}
if (dropped == 0) { // 当遍历完成时,dropped 等于0,即遍历时没有一次撤销处理(没有需要撤销的订单了),即为撤销处理工作完成,跳出while循环
break;
}
Sleep(300); // 防止轮转频率过快,每次间隔一定时间
}
return order; // 返回要查找的订单order
}
var preMsg = ""; // 记录缓存信息的变量
function GetAccount(e, waitFrozen) { // 获取账户资产信息,参数e亦是exchange的引用,参数waitFrozen控制是否等待冻结
if (typeof(waitFrozen) == 'undefined') { // 如果调用时不传入waitFrozen参数,给参数waitFrozen赋值false,即默认不等待冻结
waitFrozen = false;
}
var account = null;
var alreadyAlert = false; // 标记是否已经提醒过的变量
while (true) { // 获取当前账户信息,检测冻结,如果不等待冻结,则会直接跳出while循环
account = _C(e.GetAccount);
if (!waitFrozen || account.FrozenStocks < MinStock) {
break;
}
if (!alreadyAlert) {
alreadyAlert = true; // 触发提醒一次,就重置alreadyAlert,避免重复不停的提醒
Log("发现账户有冻结的钱或币", account); // 输出提醒日志
}
Sleep(Interval);
}
msg = "成功: " + Counter.s + " 次, 失败: " + Counter.f + " 次, 当前账户 币: " + account.Stocks;
if (account.FrozenStocks > 0) {
msg += " 冻结的币: " + account.FrozenStocks;
}
if (msg != preMsg) { // 检测当前信息是否和上次信息不同,不同的话更新在状态栏上
preMsg = msg;
LogStatus(msg, "#ff0000");
}
return account; // 函数返回账户信息 account结构
}
function GetPosition(e, orderType) { // 获取持仓,或者获取指定方向的持仓
var positions = _C(e.GetPosition); // 获取持仓
if (typeof(orderType) == 'undefined') { // orderType 参数为指定要获取的持仓类型,如果没有传入orderType参数,直接返回所有持仓
return positions;
}
for (var i = 0; i < positions.length; i++) { // 遍历持仓列表
if (positions[i].Type == orderType) { // 如果当前遍历的持仓数据是需要找的方向(orderType)
return positions[i]; // 返回 orderType 类型的持仓
}
}
return null;
}
function GetTicker(e) { // 获取ticker 行情数据
while (true) {
var ticker = _C(e.GetTicker); // 获取tick行情
if (ticker.Buy > 0 && ticker.Sell > 0 && ticker.Sell > ticker.Buy) { // 检查行情数据可靠性
return ticker; // 返回 ticker数据
}
Sleep(100);
}
}
// mode = 0 : direct buy, 1 : buy as buy1
function Trade(e, tradeType, tradeAmount, mode, slidePrice, maxSpace, retryDelay) { // 交易函数
// e 交易所对象引用, tradeType 交易方向(买/卖), tradeAmount 交易数量, mode 交易模式, slidePrice 滑价, maxSpace 最大挂单距离, retryDelay 重试时间间隔
var initPosition = GetPosition(e, tradeType); // 获取指定方向的持仓数据,记作 initPosition
var nowPosition = initPosition; // 声明另一个变量nowPosition 用initPosition赋值
var orderId = null;
var prePrice = 0; // 上次循环时的下单价格
var dealAmount = 0; // 已经交易的数量
var diffMoney = 0;
var isFirst = true; // 循环首次执行标记
var tradeFunc = tradeType == ORDER_TYPE_BUY ? e.Buy : e.Sell; // 下单函数,根据参数 tradeType 而定是调用 e.Buy 还是 e.Sell
var isBuy = tradeType == ORDER_TYPE_BUY; // 是否是买入的标记
while (true) { // while循环
var account = _C(e.GetAccount); // 获取当前账户资产数据
var ticker = GetTicker(e); // 获取当前行情数据
var tradePrice = 0; // 根据 mode 参数制定交易价格
if (isBuy) {
tradePrice = _N((mode == 0 ? ticker.Sell : ticker.Buy) + slidePrice, 4);
} else {
tradePrice = _N((mode == 0 ? ticker.Buy : ticker.Sell) - slidePrice, 4);
}
if (orderId == null) {
if (isFirst) { // 根据 isFirst 标记变量判断,如果是第一次执行,什么都不做
isFirst = false; // isFirst 标记设置为false ,代表已经不是第一次执行
} else { // 非第一次执行,更新持仓数据
nowPosition = GetPosition(e, tradeType);
}
dealAmount = _N((nowPosition ? nowPosition.Amount : 0) - (initPosition ? initPosition.Amount : 0), 6); // 根据最初的持仓数据和当前的持仓数据,计算已经成交的数量
var doAmount = Math.min(tradeAmount - dealAmount, account.Stocks * MarginLevel, 4); // 根据已经成交的数量、账户可用资产,计算剩余需要交易的数量
if (doAmount < MinStock) { // 如果算出的交易数量小于最小交易数量,终止逻辑,跳出while循环
break;
}
prePrice = tradePrice; // 缓存当前循环时的交易价格
e.SetDirection(tradeType == ORDER_TYPE_BUY ? "buy" : "sell"); // 设置期货交易方向
orderId = tradeFunc(tradePrice, doAmount); // 下单交易,参数为算出的价格,本次下单数量
} else { // 当记录订单的变量 orderId 不为null时,则说明已经下过订单
if (mode == 0 || Math.abs(tradePrice - prePrice) > maxSpace) { // 如果是挂单模式,当前价格与上一次缓存的价格超出最大挂单区间
orderId = null; // 重置orderId 为空值,就会在下一轮循环重新下单
}
var order = StripOrders(exchange, orderId); // 调用StripOrders查找挂单列表中的ID为orderId的订单
if (order == null) { // 如果查找不到,也重置orderId 为空值,继续下一轮的下单操作
orderId = null;
}
}
Sleep(retryDelay); // 暂定一定时间,起到控制循环频率的效果
}
if (dealAmount <= 0) { // 在while循环结束后,如果已经交易的量dealAmount小于等于0,说明交易失败返回空值
return null;
}
return nowPosition; // 正常情况返回最新的持仓数据
}
function coverFutures(e, orderType) { // 平仓函数
var coverAmount = 0; // 声明一个变量coverAmount,初始赋值0,用来记录已经平仓的数量
while (true) {
var positions = _C(e.GetPosition); // 获取持仓
var ticker = GetTicker(e); // 获取当前行情
var found = 0; // 查找标记
for (var i = 0; i < positions.length; i++) { // 遍历持仓数组positions
if (positions[i].Type == orderType) { // 找到需要的持仓
if (coverAmount == 0) {
coverAmount = positions[i].Amount; // 初始时记录持仓数量,即要平仓的数量
}
if (positions[i].Type == ORDER_TYPE_BUY) { // 根据持仓类型,执行平仓操作
e.SetDirection("closebuy"); // 设置期货交易方向
e.Sell(ticker.Buy, positions[i].Amount); // 下单函数
} else {
e.SetDirection("closesell");
e.Buy(ticker.Sell, positions[i].Amount);
}
found++; // 标记累计
}
}
if (found == 0) { // 如果标记变量found为0,则没有仓位需要处理,跳出while循环
break;
}
Sleep(2000); // 间隔2秒
StripOrders(e); // 撤销当前所有挂单
}
return coverAmount; // 返回平仓的数量
}
function loop(pos) {
var tradeType = null; // 初始化交易方向
if (typeof(pos) == 'undefined' || !pos) { // 判断是否是首轮执行
tradeType = FirstTradeType;
pos = Trade(exchange, tradeType, OpAmount, OpMode, SlidePrice, MaxSpace, Interval); // 首笔交易
if (!pos) {
throw "出师不利, 开仓失败";
} else {
Log(tradeType == ORDER_TYPE_BUY ? "开多仓完成" : "开空仓完成", "均价:", pos.Price, "数量:", pos.Amount);
}
} else {
tradeType = pos.Type; // 根据持仓方向继续指定交易方向
}
var holdPrice = pos.Price; // 持仓价格
var holdAmount = pos.Amount; // 持仓数量
var openFunc = tradeType == ORDER_TYPE_BUY ? exchange.Buy : exchange.Sell; // 多头持仓,开仓为买,否则开仓为卖
var coverFunc = tradeType == ORDER_TYPE_BUY ? exchange.Sell : exchange.Buy; // 多头持仓,平仓为卖,否则平仓位买
var reversePrice = 0; // 反手价格
var coverPrice = 0; // 平仓价格
var canOpen = true; // 可开仓标记
if (tradeType == ORDER_TYPE_BUY) {
reversePrice = _N(holdPrice * (1 - StopLoss), 4); // 止损价格
coverPrice = _N(holdPrice * (1 + StopProfit), 4); // 止盈价格
} else {
reversePrice = _N(holdPrice * (1 + StopLoss), 4);
coverPrice = _N(holdPrice * (1 - StopProfit), 4);
}
var coverId = null;
var msg = "持仓价: " + holdPrice + " 止损价: " + reversePrice;
for (var i = 0; i < 10; i++) { // 控制最多下单10次
if (coverId) { // 订单ID为空,不触发break,继续循环,直到10次
break;
}
if (tradeType == ORDER_TYPE_BUY) { // 根据方向下单,挂平仓单,即止盈的订单
exchange.SetDirection("closebuy");
coverId = exchange.Sell(coverPrice, holdAmount, msg);
} else {
exchange.SetDirection("closesell");
coverId = exchange.Buy(coverPrice, holdAmount, msg);
}
Sleep(Interval);
}
if (!coverId) { // 10次下单还失败抛出错误,策略停止
StripOrders(exchange); // 撤销所有挂单
Log("下单失败", "@") // 增加推送提醒
throw "下单失败"; // 抛出错误,让机器人停止
}
while (true) { // 进入检测反手的循环
Sleep(Interval);
var ticker = GetTicker(exchange); // 获取最新的行情
if ((tradeType == ORDER_TYPE_BUY && ticker.Last < reversePrice) || (tradeType == ORDER_TYPE_SELL && ticker.Last > reversePrice)) { // 检测触发止损即反手
StripOrders(exchange); // 挂单全部撤单
var coverAmount = coverFutures(exchange, tradeType); // 持仓全平
if (_Failed >= MaxLoss) { // 如果超过最大止损次数(反手次数),跳出循环重新开始
Counter.f++; // 计一次失败
Log("超过最大失败次数", MaxLoss);
break; // 跳出循环
}
var reverseAmount = _N(coverAmount * ReverseRate, 4); // 根据平仓的量,进行交易量加倍
var account = GetAccount(exchange, true); // 更新账户信息,此时不能有资产冻结
// 检测账户资产是否足够,不足跳出循环,重新开始,如同_Failed >= MaxLoss
if (_N(account.Stocks * MarginLevel, 4) < reverseAmount) { // 检测资产是否足够
Log("没有币反手开仓, 需要开仓: ", reverseAmount, "个, 只有", account.Stocks, "个币");
Counter.f++;
break;
}
var reverseType = tradeType; // 记录反转操作类型,默认顺仓
if (ReverseMode == 0) { // 反手模式影响的调整,即如果参数设置了反仓,这里调整
reverseType = tradeType == ORDER_TYPE_BUY ? ORDER_TYPE_SELL : ORDER_TYPE_BUY; // 反仓就是指,刚才持仓是多,这次反手就做空,刚才是做空,这次就做多
}
var pos = Trade(exchange, reverseType, reverseAmount, OpMode, SlidePrice, MaxSpace, Interval); // 加倍的下单操作
if (pos) { // 检测交易逻辑执行之后的持仓
Log(reverseType == ORDER_TYPE_BUY ? "多仓" : "空仓", "加倍开仓完成");
}
return pos; // 返回持仓结构
} else { // 没有触发反手时执行的逻辑
var orders = _C(exchange.GetOrders); // 当止盈挂单成交,记录胜次数加1
if (orders.length == 0) {
Counter.s++;
var account = GetAccount(exchange, true); // 更新账户资产
LogProfit(account.Stocks, account); // 打印账户资产
break;
}
}
}
// 非while循环内正常return 的,返回null,例如止盈成功,超过失败次数,资产不足
return null;
}
function onexit() { // 机器人停止时,执行扫尾函数onexit
StripOrders(exchange); // 撤销所有挂单
Log("Exit");
}
function main() {
if (exchange.GetName().indexOf("Futures") == -1) { // 检测当前添加的第一个交易所对象是不是期货交易所
throw "只支持期货, 现货暂不支持";
}
// EnableLogLocal(SaveLocal);
if (exchange.GetRate() != 1) { // 不启用汇率转换
Log("已禁用汇率转换");
exchange.SetRate(1);
}
StopProfit /= 100; // 参数处理为小数,假设StopProfit为1表示要1%止盈,重新计算赋值,StopProfit的值为0.01即1%
StopLoss /= 100; // 止损(反手)同上
var eName = exchange.GetName();
if (eName == "Futures_CTP") { // 检测添加的第一个交易所对象是否为商品期货,如果是,抛出错误信息,让机器人停止
throw "暂只支持数字货币期货"
}
exchange.SetContractType(Symbol); // 设置数字货币合约代码,即要交易、操作的合约
exchange.SetMarginLevel(MarginLevel); // 设置杠杆
Interval *= 1000; // 轮询间隔参数由秒转换为毫秒
SetErrorFilter("502:|503:|unexpected|network|timeout|WSARecv|Connect|GetAddr|no such|reset|http|received|EOF"); // 设置屏蔽的错误类型
StripOrders(exchange); // 取消所有挂单
OrgAccount = GetAccount(exchange, true); // 获取当前账户信息
LogStatus("启动成功"); // 更新状态栏信息
var pos = null; // 初始化main函数内的局部变量pos为null,用来记录持仓数据结构
var positions = GetPosition(exchange); // 获取当前持仓,调用的是封装后的GetPosition不带orderType参数是要获取全部持仓,注意调用的并非是API接口exchange.GetPosition
if (positions.length == 1) { // 如果开始时有持仓,赋值给pos变量
pos = positions[0];
Log("发现一个仓位, 已经自动恢复进度");
} else if (positions.length > 1) { // 有多个持仓时,为策略不可运行状态,策略抛出错误让机器人停止
throw "发现持仓超过1个";
}
while (true) { // 策略主循环
pos = loop(pos); // 执行交易逻辑主要的函数loop,pos作为参数,并且返回新的持仓数据结构
if (!pos) { // 该条件触发,返回null的情况,例如止盈成功,超过失败次数,资产不足
_Failed = 0; // 重置止损次数为0,重来
} else {
_Failed++; // 累计止损次数
}
Sleep(Interval);
}
}
Nach dem Lesen des Strategiecodes kann man feststellen, dass die Strategielogik nicht kompliziert ist, und dass der Code nicht viel zählt, aber der Entwurf kann als eigenständig bezeichnet werden.
Die Hauptfunktion der strategischen Handelslogik istloop
Die Funktion ist inmain
Die Funktion wird in der Hauptschleife wiederholt aufgerufen, wennloop
Wenn die Funktion ausgeführt wird, wird zuerst der Bestellvorrat gehalten, dann wird der Bestellvorgang aufgehängt und auf den Bestellvorgang gewartet.
Die strategische Logik beschreibt dies einfach, aber es gibt noch einige andere Details, wie z. B. die Einstellung der maximalen Anzahl von Wiederholungen, die Erfassung der verfügbaren Konto-Assets, die maximale Anzahl von 10 Ausführungen, bei denen eine Bestellung fehlschlägt.
Einige Funktionen der Strategie sind so konzipiert, dass sie sich nach verschiedenen Parametern verhalten, wie zum Beispiel:StripOrders
Die FunktionGetAccount
Die FunktionGetPosition
Funktionen. Die Funktionen verhalten sich unterschiedlich, je nachdem, wie sie eingegeben werden. Das ist eine gute Wiederverwendung des Codes, vermeidet Code-Reduktion und macht das Konzept einfach zu verstehen.
Die ursprüngliche Strategie:https://www.fmz.com/strategy/3648
Es gibt ein gewisses Risiko, sich zu verdoppeln, vor allem bei Futures, Strategien sind nur zum Lernen, praktisch vorsichtig, willkommen in der Nachricht Diskussion.