如何将Metatrader 4的警报或电子邮件指标信号转换为专家顾问以开放交易?

8

我一直在使用一个指标来进行交易。我没有开发过这个指标,所以我只能访问 .ex4 文件。如何从警报或电子邮件信号中提取获利、开仓和止损值以开放交易?请参见下面的电子邮件和警报信号样本。

enter image description here

enter image description here

3个回答

3
下面是一份使用kernel32.dll的本地MQL解决方案的可行脚本示例,用于将日志文件从./MQL4/Logs复制到./MQL4/Files。需要子类化LogSignalParser抽象基类并实现虚拟bool parse()方法。
编辑:@TenOutOfTen希望提供如何解析日志文件中以下行格式的实际示例:
0 02:20:00.874 SuperIndicator USDCAD,M5: Alert: USDCAD, M5: Super Indicator SELL @ 1.29136, TP 1.28836, SL 1.29286
步骤1:将LogParser.mqh保存在有意义的位置。
//LogParser.mqh

#property strict
#include <stdlib.mqh>
#include <Arrays\ArrayString.mqh>
#include <Arrays\ArrayObj.mqh>
#import "kernel32.dll"
   bool CopyFileW(string lpExistingFileName,
                  string lpNewFileName,
                  bool   bFailIfExists);
#import
//+------------------------------------------------------------------+
//|                                                              
//+------------------------------------------------------------------+
class Signal : public CObject
{
public:
   string            symbol;
   datetime          signal_time;
   int               order_type;
   double            price_entry;
   double            price_sl;
   double            price_tp;
   virtual int Compare(const CObject *node,const int mode=0) const override
   {
      const Signal *other=node;
      if(this.signal_time>other.signal_time)
         return 1;
      if(this.signal_time<other.signal_time)
         return -1;
      return 0;
   }
   string to_string()
   {
      return StringFormat("%s - %s(%s) @ %.5f, SL=%.5f, TP=%.5f",
         signal_time,
         symbol,
         order_type==OP_BUYLIMIT ? "BUY" : "SELL",
         price_entry,
         price_sl,
         price_tp
      );
   }
};
//+------------------------------------------------------------------+
//|Vector-like collection                                                          
//+------------------------------------------------------------------+
class SignalList : public CArrayObj
{
   public: Signal *operator[](int i){return this.At(i);}
};
//+------------------------------------------------------------------+
//|Abstract abse class: the parse method must be implemented in subclass                                                             
//+------------------------------------------------------------------+
class LogSignalParser : public CObject
{
protected:
   CArrayString      m_rows;
   SignalList        m_signals;
   string            m_log_file_name;
   string            m_ind_name;
public:
                     LogSignalParser(string indicator_name);

                     // parse method must be overridden!
   virtual bool      parse() = 0;
   int               Total();
   Signal           *operator[](int i);
protected:
   bool              _copy_log();
   int               _open_log();
   bool              _parse_rows();
};
//+------------------------------------------------------------------+
LogSignalParser::LogSignalParser(string indicator_name)
{
   m_log_file_name="copy_log.log";
   m_ind_name=indicator_name;
}
//+------------------------------------------------------------------+
bool LogSignalParser::_copy_log(void)
{
   MqlDateTime t;
   TimeLocal(t);
   string data_path = TerminalInfoString(TERMINAL_DATA_PATH)+"\\MQL4";
   string logs_path = data_path + "\\Logs\\";
   string dest_file = data_path + "\\Files\\" + m_log_file_name;
   string log_file=logs_path+StringFormat("%d%02d%02d.log",
                                          t.year,t.mon,t.day);
   return CopyFileW(log_file, dest_file, false);
}
//+------------------------------------------------------------------+
bool LogSignalParser::_parse_rows()
{
   if(!this._copy_log())
      return false;
   int h= this._open_log();
   if(h == INVALID_HANDLE)
      return false;
   m_rows.Clear();
   while(!FileIsEnding(h)){
      string row=FileReadString(h);
      if(StringFind(row,"Alert:") >= 0 && StringFind(row,m_ind_name) >= 0)
         m_rows.Add(row);
   }
   m_rows.Sort();
   FileClose(h);
   return true;
}
//+------------------------------------------------------------------+
int LogSignalParser::_open_log(void)
{
   return FileOpen(m_log_file_name,
      FILE_TXT|FILE_READ|FILE_SHARE_READ|FILE_SHARE_WRITE);
}
//+------------------------------------------------------------------+
int LogSignalParser::Total(void)
{
   return m_signals.Total();
}
//+------------------------------------------------------------------+
Signal* LogSignalParser::operator[](int i)
{
   return m_signals.At(i);
}

步骤2:对LogSignalParser类进行子类化并重写parse方法。
//SuperIndicatorParser.mqh
#property strict
#include "LogParser.mqh"
//+------------------------------------------------------------------+
class SuperIndicatorParser : public LogSignalParser
{ 
public:
   SuperIndicatorParser():LogSignalParser("SuperIndicator"){}
   virtual bool parse() override;
};
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
bool SuperIndicatorParser::parse() override
{
   if(!this._parse_rows())
      return false;
   m_signals.Clear();
   MqlDateTime local;
   TimeLocal(local);
   for(int i=m_rows.Total()-1; i>=0; i--)
   {
      string row=m_rows[i];
      MqlDateTime log_time;
      TimeToStruct(StringToTime(StringSubstr(row, 2, 12)), log_time);
      log_time.year = local.year;
      log_time.mon = local.mon;
      log_time.day = local.day;
      datetime time = StructToTime(log_time); 
      row = StringSubstr(row, StringFind(row, m_ind_name) + StringLen(m_ind_name) + 1);
      StringReplace(row, ",", " ");
      string parts[];
      StringSplit(row, ' ', parts);
      int len = ArraySize(parts);
      string debug = "";
      for(int k=0;k<len;k++)
         debug += "|" + parts[k];
      if(len != 17)
         continue;
      Signal *s      = new Signal();
      s.signal_time  = time;
      s.symbol       = parts[0];
      s.order_type   = parts[8] == "BUY" ? OP_BUYLIMIT : OP_SELLLIMIT;
      s.price_entry  = double(parts[10]);
      s.price_tp     = double(parts[13]);
      s.price_sl     = double(parts[16]);
      m_signals.Add(s);
   }
   m_signals.Sort();
   return true;
}

步骤3:在MQL程序中使用(示例脚本)

#property strict
#include "SuperIndicatorParser.mqh"
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
   SuperIndicatorParser parser;
   if(parser.parse()){
      for(int i=parser.Total()-1; i>=0; i--){
         Signal *s = parser[i];
         int ticket = OrderSend(
            s.symbol, s.order_type, 0.1, 
            s.price_entry, 0, s.price_sl, s.price_tp 
         ); 
         if(ticket < 0){
            Print(_LastError);
         }
      }
   }     
}

3
哇,这看起来令人难以置信。我还没有写 MQL 编程很长时间,所以有很多东西我正在努力理解。虽然我没有问原始问题,但如果您能展示如何使用此日志来开放交易,我将不胜感激。注:该日志为一个卖出信号,包括货币对、时间框架、卖出价格、止损价和获利目标价。 - TenOutOfTen
1
@TenOutOfTen 我更新了上面的帖子,给出了一个实际的例子,说明如何在日志示例中使用这些类。 - nicholishen
再次感谢您的示例。如果您可以帮我看一下这个问题,我将非常感激 https://stackoverflow.com/questions/53421751/how-to-draw-a-trend-line-that-joins-the-highest-point-of-every-candle-after-firs - TenOutOfTen
@nicholishen 我对编写MQL代码还很陌生。我在https://dev59.com/1VMI5IYBdhLWcg3wj8KF上发了一个问题,但是还没有找到解决方案。如果你能帮帮我就太感激了。最近有人还为我的问题提供了悬赏。 - NotEveryDay
1
我真的很感激我所问的一个MQL5问题的帮助。我已经为这个问题创建了一个赏金,并提供了更多的信息。请看 https://dev59.com/T1IG5IYBdhLWcg3w3Fo_ - SuperHueman

2
不需要从电子邮件中提取数据,因为指示器也通过“Alert”函数发送数据。警报记录在“.\MQL4\Logs”目录下的“*.log”文本文件中。您可以编写一些使用“win32”读取日志的MQL,并在MQL中制作自己的解析器。
另一种选择是编写一个看门狗脚本来扫描和解析日志文件,并将结果写入csv,EA可以访问它。这种方法的好处是与MQL解决方案相比开发容易,并且由于它适用于所有符号,因此避免了多个EA尝试同时读取日志并写入csv的潜在竞争条件。
以下是Python编写的示例。
import csv
import re
import time
from datetime import datetime
from pathlib import Path

MQL_DATA_PATH = Path(
    'C:/Users/user/Desktop/MT-TEST/Vanilla-MT4-v0_0_2/MT4/MQL4'
)
OUTPUT_FILENAME = 'signals.csv'

signal_pattern = re.compile(r'''# regex - verbose mode
    (?P<time>\d\d:\d\d:\d\d).*? # time stamp
    (?P<symbol>[A-Z]{6}\w*),.*? # symbol with ECN suffix
    (?P<type>BUY|SELL).*?       # BUY or SELL command
    (?P<price>\d+\.\d+).*?      # execution price
    (?P<tp>\d+\.\d+).*?         # takeprofit 
    (?P<sl>\d+\.\d+)            # stoploss
''', re.VERBOSE)


def log_to_csv():
    date = datetime.now()
    log_file = MQL_DATA_PATH / 'Logs' / f'{date.strftime("%Y%m%d")}.log'
    with open(log_file) as f:
        log_entries = f.read()
    signals = [s.groupdict() for s in signal_pattern.finditer(log_entries)]
    for signal in signals:
        # correct time to MQL datetime
        signal['time'] = f"{date.strftime('%Y.%m.%d')} {signal['time']}"
    csv_file = MQL_DATA_PATH / 'Files' / OUTPUT_FILENAME
    with open(csv_file, 'w') as f:
        writer = csv.DictWriter(f,
            fieldnames=('time', 'symbol', 'type', 'price', 'tp', 'sl',),
            lineterminator='\n',
        )
        writer.writerows(signals)


def main():
    print(f'Watching MQL log and saving signals to {OUTPUT_FILENAME}')
    print('Press Ctrl+C to exit')
    while True:
        try:
            log_to_csv()
            print(datetime.now().strftime('%Y.%m.%d %H:%M:%S'), end='\r')
            time.sleep(5)
        except KeyboardInterrupt:
            exit()


if __name__ == '__main__':
    main()

我对此非常感兴趣,尽管我不知道如何使用Python编程。我刚刚在Mac上安装了Anaconda。我该如何使用它?这是我的指标在.log文件中的显示方式:0 02:20:00.874 SuperIndicator USDCAD,M5: Alert: USDCAD, M5: Super Indicator SELL @ 1.29136, TP 1.28836, SL 1.29286。我非常感谢您的帮助。 - TenOutOfTen
@TenOutOfTen 你需要将脚本保存为 *.py 扩展名,然后从命令行运行它。 C:\Path\To\Script>python log_parser.py。我不确定它是否适用于 Mac,因为你可能正在使用虚拟机... - nicholishen
@TenOutOfTen,我刚刚发布了一个本地的MQL解决方案,你也可以尝试一下。 - nicholishen
你能否帮我看一下这个问题:https://dev59.com/j7voa4cB1Zd3GeqP9LhZ - TenOutOfTen

0

MT4无法读取您的电子邮件。您需要使用其他工具或更通用的语言来读取您的电子邮件,如Java.Mail.API或Python,或者其他一些工具。 阅读电子邮件,确保格式正确并且发件人是您预期的人,然后将消息放入一个对MT4可用的文件中 - 要么是自己的文件夹(C:\Users\UserName\AppData\Roaming\MetaQuotes\Terminal\12345678E7E35342DB4776F5AE09D64B\MQL4\Files),要么是公共文件夹(C:\Users\User1\AppData\Roaming\MetaQuotes\Terminal\Common\Files)。然后使用FileSearchNext()函数在MT4应用程序中读取文件,并在MQL4文档中使用example进行链接操作。在读取文件后,您需要使用String functions解析它,并创建一个OrderSend()请求(可能需要检查输入以及您的逻辑是否允许您的机器人发送交易,例如您没有达到允许的最大开放交易数、交易时间、其他逻辑)。


感谢您的解释。我对两种方法很感兴趣,一种是从指标警报信号中进行交易,另一种是从电子邮件信号中进行交易。从事情的样子来看,我认为从指标信号警报中进行交易要比从电子邮件中进行交易简单得多。如果您可以确认这是可能的,我宁愿编辑我的问题,专门针对指标信号警报。此外,如果可能的话,我也会感激您提供有关警报弹出窗口的解决方案。 - iGetIt
我认为一旦警报发布,就不可能以简单的方式将警报数据返回到MT4。正如我所提到的,您可以从Terminal\Common\Files或Terminal\TERMINAL_ID\MQL4\Files读取文件,而日志位于Terminal\TERMINAL_ID\MQL4\Logs中。因此,您无法读取日志以查看警报(如果发送了警报,则也会在日志中打印)。如果您有代码,您可以通过在警报后发送“ChartEventCustom()”来轻松解决问题,并且您将在该事件中提及符号、tf和警报消息(或至少方向)。但是,如果没有源代码,您无法编辑指标。 - Daniel Kniaz
我建议您检查指标是否具有缓冲区。在这种情况下,可以从指标获取缓冲数据,并在缓冲区更改后发送交易。但是即使您拥有源代码,也不能保证缓冲区会对您有所帮助。 - Daniel Kniaz
指标没有缓冲区。我想更艰难的路径更为明显 : )。我不太懂Java,但如果您能提供任何链接(最好是代码),以便尽快阅读电子邮件,我将不胜感激。 - iGetIt
针对javamail,可以参考以下链接:https://stackoverflow.com/questions/21459021/receiving-mail-using-javamail-api?rq=1。如果您没有特别偏好的话,我建议使用Python,因为我认为它更简单、更短小。对于小型脚本来说,Python似乎是完美的选择。 - Daniel Kniaz
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接