
在 MQL5 中创建做市商算法
什么是流动性?
金融市场的流动性是指市场上以订单和头寸形式存在的资金的 "饱和度"。这可以使交易者能够快速大量出售股票(或货币)。市场的流动性越高,就越容易卖出或买入大量资产,而不会因滑点造成重大损失。
滑点是大玩家的主要恐惧:庞大的资金会注意到,处理大量头寸并不那么容易,往往只是因为订单 "滑点",交易才会亏损平仓。当交易以一个价格开仓,而以另一个不同于预期的价格执行时,就会出现订单滑点。当交易者只有几百美元时,通常不会出现流动性问题(除非是三流加密货币完全缺乏流动性的市场深度)。但是,当我们面对的是数亿美元的资金时,就很难同时开仓和平仓了。这与市场流动性直接相关。
市场的流动性得益于做市商。它们的主要任务是维持流动性。这些市场参与者会尽一切努力使您的交易尽可能顺畅,这样就不会出现急剧的报价差距,买方和卖方总是能得到适合自己的价格。
在一个没有做市商的市场中,我们经常会看到一个方向的剧烈价格波动、巨大的资产波动和报价缺口。
做市商是如何运作的,为什么他不是“傀儡大师”?
许多交易员相信,做市商是某种傀儡大师 — 一个操纵者,他将价格移动到他想要的地方,打破止损水平,诱骗大众下达止损订单等。
事实上,做市商根本不需要让"大众"亏损。由于价差、佣金和库存费,市场"大众"自己也会亏损。
至于市场向正确方向的转变,这也不是做市商的任务。根据与交易所签订的协议,做市商有义务向买方提供买入报价,向卖方提供卖出报价,并在必要时填补"市场深度"空白。
如果没有做市商,市场将完全不同:我们将不断看到价格缺口、报价缺口、双向挤压以及双向价格大幅跳水。今天,在那些做市商无利可图的市场中,仍然可以找到所有这些,例如在许多美国细价股中。
加密货币市场中的新型 AMM 技术
但是,如果我们用智能合约取代参与者呢?换句话说,如果我们建立的不是做市商,而是一个自动调整供求关系和总体报价的系统,情况会怎样呢?
去中心化交易所(decentralized exchanges,DEX)大致就是这样出现的。他们是第一个使用 AMM(automated market making,自动做市商)机制的公司。AMM 算法通过一个特殊的流动性池,利用参与者的资源进行参与者之间的交易。交易所的价格和交易量始终由算法控制。这使得所有卖家和所有买家都能聚集在一起,而参与者却不会因此蒙受损失。但实际上,所有的 DEX 都有巨大的价格滑点。如果交易量很大,您肯定会在代币交易所损失很大一部分。
此外,这种创新并没有消除市场操纵,在 DEX 上有很多这样的情况。即使是 DEX 上的代币创造者,也可以轻松地抽走他们的代币,将整个代币流动池套现。
做市商如何打击价格操纵?
虽然这不是做市商的责任,但当价格刚刚开始被欺诈性参与者推高时,他们经常会在萌芽状态下扼杀拉高出货计划的企图。在这些初始阶段,做市商向试图推高"市场"价格的参与者抛出大量限价订单。这样一来,需求就会消失,因此抽水计划的新手往往会对做市商咬牙切齿。但如果抽水计划周密,大量市场订单涌入,会有力地推动价格,迫使做市商暂时离开市场。
做市商何时离场?
大多数做市商在与交易所签订的协议中规定,在节假日、异常活动期和重要新闻发布期间,做市商必须关闭算法并离开市场。这是因为做市商希望保住资本。
通过扩大的价差,我们可以看到做市商的立即离场。您是否看到过,即使是在 ECN 上,在发布重要的全球新闻时,价差也会扩大?通常的价差缩小是通过做市商的努力实现的。因此,如果没有它们,我们就会面临非常糟糕的交易条件,包括价差过大、价格大幅滑落、突然下跌和价格飙升 - 所有这些都是市场的狂野乐趣。
什么是做市商的库存风险?
很多人认为做市商根本不承担任何风险。然而,情况并非如此。做市商的主要风险是库存风险。这种风险在于头寸可能朝一个方向急剧变动,而无法关闭头寸并从价差中获利。例如,当疯狂的人群抛售某种资产时,做市商被迫买断全部供应。结果,价格变成负数,导致做市商蒙受损失。
公司会试图通过使用特殊的价差居中公式和确定最佳买卖价格来避免这种风险。但这并非总能实现。即使价格不是最优的,做市商的工作也是为市场提供流动性,即使暂时亏损,他们也必须完成这项工作。
分析全球最大做市商 Kenneth Griffin 的公司的记录
在分析世界上最大的做市商 - 由 Kenneth Griffin 创立的 Citadel Securities 的活动时,我们可以清楚地看到它在金融市场中扮演着多么重要的角色。
该公司的报告显示,其影响令人印象深刻:在美国股市,每 10 笔交易中就有 7 笔依赖于该做市商提供的流动性。这一行动表明,Citadel Securities 在维护该市场的稳定性和流动性方面发挥了重要作用。
为了评估 Griffin 公司的影响规模,可以说每天约有 9 亿手美股通过它的算法。如此巨大的交易量反映了该公司在美国交易所的高度活跃性和影响力。
顺便说一句,Kenneth Griffin 从定向交易到做市商的演变过程非常有趣。Griffin 的公司正在积极拓展全球市场,积极探索亚洲交易所并在那里提供流动性。
准备做市商 EA
这样,我们已经弄清了理论。是时候开始创建做市商 EA 了!当然,我们的算法会非常简单。我们不会根据特殊公式建立价差交易,
相反,我们将采用最简单的算法,使两个限价订单(限价卖出订单和限价买入订单)始终处于打开状态。
MQL5 中最简单的做市实现
让我们来分析一下算法的代码。代码头部,本节设置策略的基本参数,如手数、盈利水平、EA 幻数、选定交易货币对等:
//+------------------------------------------------------------------+ //| MarketMaker.mq5 | //| Copyright 2023, Evgeniy Koshtenko | //| | //+------------------------------------------------------------------+ #property copyright "Copyright 2023, Evgeniy Koshtenko" #property link "https://www.mql5.com/en/users/koshtenko" #property version "1.00" #include <Trade\Trade.mqh> // Include the CTrade trading class //--- input parameters input double Lots = 0.1; // lot input double Profit = 0.1; // profit input double BProfit = 11; // buy profit input double SProfit = 11; // sell profit input int StopLoss = 0; // stop loss input int TakeProfit = 0; // take profit input int Count = 5; // number of orders input int Delta = 55; // delta input int Magic = 123; // magic number input bool BuyLimit = 1; // Buy Limit input bool SellLimit = 1; // Sell Limit input string Symbol1 = "EURUSD"; input string Symbol2 = "GBPUSD"; input string Symbol3 = "USDCHF"; input string Symbol4 = "USDJPY"; input string Symbol5 = "USDCAD"; input string Symbol6 = "AUDUSD"; input string Symbol7 = "NZDUSD"; input string Symbol8 = "EURGBP"; input string Symbol9 = "CADCHF"; input int MaxOrders = 20; // Max number of orders CTrade trade; datetime t=0; int delta=0;
它包括基本设置,如订单之间的价差值、平仓利润(总利润、买入利润和卖出利润)、EA 幻数、交易库导入,以及选择交易货币对和限制订单数量。
初始化和去初始化功能一般都是标准的。EA 启动时调用 OnInit() 函数,结束时调用 OnDeinit() 函数。OnInit() 设置 EA 幻数和交易函数计时器:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- // Set a timer with a resolution of 10000 milliseconds (10 seconds) EventSetMillisecondTimer(100000); trade.SetExpertMagicNumber(Magic); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) {// Disable timer EventKillTimer(); Comment(""); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+
以下是计数未结订单和未结头寸的函数。CountOrders 和 CountTrades 计算特定交易品种的未结订单和仓位,同时考虑 EA 幻数。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CountOrders(string symbol, ENUM_ORDER_TYPE orderType) { int count = 0; for(int i = OrdersTotal()-1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(!OrderSelect(ticket)) { continue; } if(OrderGetInteger(ORDER_TYPE) != orderType) { continue; } if(PositionGetString(POSITION_SYMBOL) != symbol || PositionGetInteger(POSITION_MAGIC) != Magic) { continue; } count++; } return count; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CountTrades(string symbol, ENUM_POSITION_TYPE type) { int count = 0; for(int i=PositionsTotal()-1; i>=0; i--) { ulong ticket=PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) { continue; } if(PositionGetString(POSITION_SYMBOL)==symbol && PositionGetInteger(POSITION_TYPE)==type) { count++; } } return count; }
以下是删除订单、计算利润和关闭订单的函数。DelOrder 使用幻数删除特定交易品种的所有订单。AllProfit 可计算特定交易品种的总利润或买入/卖出交易利润,同时考虑到幻数。
//+------------------------------------------------------------------+ //| Position Profit | //+------------------------------------------------------------------+ double AllProfit(string symbol, int positionType = -1) { double profit = 0; for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) { continue; } if(PositionGetString(POSITION_SYMBOL) != symbol || PositionGetInteger(POSITION_MAGIC) != Magic) { continue; } if(positionType != -1 && PositionGetInteger(POSITION_TYPE) != positionType) { continue; } profit += PositionGetDouble(POSITION_PROFIT); } return profit; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CloseAll(string symbol, int positionType = -1) { for(int i = PositionsTotal()-1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(!PositionSelectByTicket(ticket)) { continue; } if(PositionGetString(POSITION_SYMBOL) != symbol || PositionGetInteger(POSITION_MAGIC) != Magic) { continue; } if(positionType != -1 && PositionGetInteger(POSITION_TYPE) != positionType) { continue; } trade.PositionClose(ticket); } }
最后,两个主要函数是交易函数和分时函数。Trade 负责根据指定参数下达限价买入和卖出订单。OnTimer 调用 Trade 函数来交易选定的交易品种,并显示该交易品种的盈利信息。
//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void Trade(string symb) { double sl = 0, tp = 0; double pr=0; double Bid=SymbolInfoDouble(symb,SYMBOL_BID); if(AllProfit(symb)>Profit && Profit>0) CloseAll(symb); if(AllProfit(symb)>Profit && Profit>0) CloseAll(symb); if(AllProfit(symb,0)>BProfit && BProfit>0) CloseAll(symb,0); for(int i=1; i<=Count; i++) { if(BuyLimit) { if (StopLoss > 0) sl = NormalizeDouble(Bid - (StopLoss) * Point(), _Digits); if (TakeProfit > 0) tp = NormalizeDouble(Bid + (TakeProfit) * Point(), _Digits); pr=NormalizeDouble(Bid-(Delta+Step)*_Point*i,_Digits); trade.BuyLimit(Lots,pr,symb,sl, tp,0,0,""); } if(SellLimit) { if (StopLoss > 0) sl = NormalizeDouble(Bid + (_Point * StopLoss) * Point(), _Digits); if (TakeProfit > 0) tp = NormalizeDouble(Bid - (_Point * TakeProfit) * Point(), _Digits); pr=NormalizeDouble(Bid+(Delta+Step)*_Point*i,_Digits); trade.SellLimit(Lots,pr,symb,sl, tp,0,0,""); } } } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTimer() { DelOrder(); Trade(Symbol1); Trade(Symbol2); Trade(Symbol3); Comment("\n All Profit: ",AllProfit(Symbol1), "\n Buy Profit: ",AllProfit(Symbol1,0), "\n Sell Profit: ",AllProfit(Symbol1,1)); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+--------+
这就是这个简单 EA 的全部代码。
测试结果
这样,让我们在测试器中使用默认设置启动 EA。以下是 2023 年 2 月 1 日至 2024 年 2 月 18 日期间 EURUSD、GBPUSD、EURGBP、USDJPY 和 EURJPY 的 EA 结果:
与利润相比,回撤幅度非常大。净值的回撤一般大于年利润。该 EA 的行为与普通网格 EA 没有太大区别。以下是测试统计数据:
显然,这种 EA 并不能以任何方式抵消其风险。就像任何没有止损位的算法一样,它是一颗定时炸弹。尽管没有出现亏损,但谁也不能保证市场上的货币不会每天崩盘 10-15%。就我个人而言,过去四年的经验告诉我,市场上绝对存在一切可能,即使是最不可思议的情况也可能成真,因此,一个全能的 EA 必须做好一切准备。该 EA 不符合我的评估标准,因此我决定予以公布。
结论
就是这样,我们创建了一个最简单的做市商算法示例。当然,这个例子只是举例说明,非常简单。显然,长期以来,市场上没有一个做市商是这样工作的。如今,它们的算法与时俱进,使用机器学习和神经网络,基于订单簿的流数据进行深度学习,并考虑到许多变量和价格特征。没有人再在价格上下订单了 - 这充满了库存风险。未来,利用机器学习创建做市商,由其自行决定订单之间的最佳价差值,或许是个合理的尝试。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/13897


