O protótipo do robô de negócio
Introdução
O ciclo de vida de qualquer sistema de negociação é reduzido às posições. Isto vai além de qualquer dúvida. Mas quando se trata da realização algorítmica, se tem tantas opiniões quanto programadores. Todo mundo será capaz de resolver o mesmo problema de sua própria maneira, mas com o mesmo resultado final.
Com os anos de prática em programação diversas abordagens para construção de lógica e estrutura dos especialistas foram tentadas. No momento pode se argumentar que foi estabelecido um modelo de padrão claro que é usado em todos os códigos.
Essa abordagem não é 100% universal, mas pode mudar o seu método de planejar a lógica de especialistas. E o caso não é quais capacidades de trabalho com pedidos você quer usar o especialista. O ponto aqui - é o princípio da criação de um modelo de negócio.
1. Princípios de planejamento de sistemas de negociação e tipos de fontes de evento
A abordagem básica para o planejamento do algorítimo, usado pela maioria, é traçar uma posição desde sua abertura até o fechamento. Esta é a abordagem linear. E se você quiser fazer mudança ao código - elas geralmente levam a grandes complicações, uma vez que grande número de condições emergem e o código acumula novos ramos de análise.
A melhor solução para modelar um robô de negociação é "servir condições". E o princípio fundamental - analisar não como essa condição de especialidade e suas posições e ordens surgiram - mas o que devemos fazer com elas agora. Este princípio básico está mudando fundamentalmente a administração de negócio e simplifica o desenvolvimento de código.
Considere isto em mais detalhes.
1.1. O princípio de "Servir Condições"
Como já mencionado, o especialista não precisa saber como o estado atual foi alcançado. Ele deve saber o que fazer com ele, de acordo com o seu ambiente (valores de parâmetro, propriedades dos pedidos arquivadas, etc.).
Este princípio é relacionado diretamente ao fato de que o especialista existe de ciclo a ciclo (particularmente - turno a turno), e não deve ser preocupar com o que aconteceu com os pedidos no turno anterior. Portanto, você deve usar uma abordagem orientada a eventos para administrar pedidos. Ou seja, no turno atual, o especialista salva seu estado, que é o ponto de partida para decisão sobre o próximo turno.
Por exemplo, você deve remover todos os pedidos pendentes do especialista e só então continuar a analisar indicador e situar novos pedidos. A maioria do código exemplifica que nós temos visto o uso do ciclo "while (true) {try to remove}" ou o ciclo ligeiramente mais leve "while (k < 1000) {try to remove; k++;}". Nós pularemos a variante, aonde a nomeação de uma vez do comando de remoção sem erro de análise.
Este método é linear, ele "suspende" o especialista por um período de tempo indeterminado.
Portanto, será mais correto não laçar um especialista, mas armazenar o pedido para remover pedidos, para que a cada novo turno, este pedido seja checado enquanto tenta deletar pedidos pendentes. Neste caso, um especialista, enquanto está lendo os parâmetros de estado, sabe que neste momento ele deve deletar pedidos. E ele tentará removê-los. Se um erro de negócio acontecer, um especialista vai simplesmente bloquear análises futuras e trabalhar antes do próximo ciclo.
1.2. O Segundo Princípio Primordial de Planejamento - é a abstração máxima possível da considerada direção de posição (Compra/Venda), moeda e tabela. Todas as funções especialistas devem ser implementadas de tal modo que, a direção ou símbolo são analisados em casos raros, quando realmente não podem ser evitados (por exemplo, quando você considera o crescimento favorável de preço para a posição de abertura, embora existam diferentes opções de evitar especificidades). Sempre tente evitar tal planejamento de "baixo nível". Isto reduzirá o código e o processo de escrever funções pelo menos duas vezes. E os fará independentes do negócio.
A implementação deste princípio é para repor a análise explícita de tipo de pedidos, parâmetro de símbolos e parâmetros calculados dependentes com as funções de macro. No artigo a seguir, nós cobrimos esta implementação em detalhes.
1.3. Terceiro princípio - segmentação do algoritmo em lexemas lógicos (módulos independentes)
Na prática, podemos dizer que a melhor abordagem é a separação de operações especialistas em funções individuais. Eu acho que você vai concordar que é difícil escrever todo o algoritmo da escrita do especialista em uma função, e isto complica análises e edições subsequentes. Então nós não devemos fazer isto no MQL5, o qual agora provém controle praticamente total sobre o seu ambiente.
Portanto, as lexemas lógicas (por exemplo, abertura, acompanhamento, fechamento de pedidos) devem ser implementadas separadamente entre si, com análise total dos parâmetros e eventos ambientais. Através dessa abordagem, o especialista se torna flexível no planejamento. Você pode facilmente adicionar novos módulos independentes nele, sem tocar os já existentes, ou incapacitar módulos existentes sem alterar o código principal.
Fontes de eventos para o sistema especialista são:
1. Indicadores. Um exemplo - é a análise dos valores indicadores lineares, suas interseções, combinações, etc. Indicadores também podem ser: tempo atual, a data obtida da Internet, etc. Na maioria dos casos, os eventos indicadores são usados para sinalizar a abertura e fechamento de pedidos. Menos para seus ajustes (usualmente Trailing Stop Loss ou pedido pendente para o indicador).
Por exemplo, a implementação prática de indicador pode ser chamada um especialista, que analisa interseção da rápida e devagar MA com a abertura futura da posição na direção da interseção.
2. Pedidos existentes, posições e seus estados. Por exemplo, o tamanho atual do prejuízo ou lucro, a presença/ausência de posições ou pedidos pendentes, o lucro da posição de fechamento, etc. Implementação prática destes eventos é mais ampla e mais diversa, conforme existem mais opções de seus relacionamentos do que para os eventos indicadores.
O evento mais simples de um especialista, baseado apenas no evento de negócio, é o reabastecimento para posições médias existentes e a saída dela no lucro desejado. Ou seja, a presença de prejuízo em posição disponível será um evento para colocar um novo pedido comum.
Ou, por exemplo, Trailling Stop Loss. Esta função checa um evento, quando o preço se modifica em lucro para um número específico de pontos a partir do Stop Loss anterior. Como resultado, especialista atrai três Stop Loss atrás do preço.
3. Eventos externos. Embora tal evento não ocorra usualmente em sistemas puramente técnicos, mas geral deve ser considerado para tomar uma decisão. Isto inclui ajustar os pedidos, posições, processamento de erros de troca, processamento de eventos de tabela (mover/criar/deletar objetos, pressionar botões, etc.). Em geral, estes são eventos que não são verificáveis no histórico e ocorrem apenas quando especialistas trabalham.
Um exemplo impressionante de como tais especialistas são sistemas de troca de informação com controle gráfico de negócios.
2. A classe base CExpertAdvisor - construtor especialista
Qual será o trabalho de um especialista em negócios? Um esquema geral das interações do programa MQL é mostrado no diagrama abaixo.
Figura 1. Esquema geral das interações dos elementos do programa MQL
Como você pode ver nestes esquemas, primeiro vem a entrada para o trabalho em ciclo (isto pode ser um turno ou um sinal de cronômetro). Neste ponto, no primeiro bloco este turno pode ser filtrado sem processamento. Isto é feito naqueles casos aonde o especialista não é necessário para trabalhar em todo ponto, mas somente na nova barra, ou se o especialista simplesmente não é autorizado a trabalhar.
Então o programa vai para o segundo bloco - módulos de trabalho com pedidos e posições, e somente então o bloco de processamento de eventos é nomeado dos módulos. Cada modo pode inquirir apenas o seu evento de interesse.
Esta sequência pode ser chamada de esquema com lógica direta, uma vez que ela primeiro determina O QUE o especialista fará (qual módulo de processamento é usado) e só então ela implementa COMO e PORQUE, ela fará (obtendo sinais de eventos).
A lógica direta é consistente com a nossa percepção de mundo e lógica universal. Afinal, um homem pensa primeiro conceitos concretos, depois ele os resume, os classifica e identifica suas interrelações.
Especialistas em planejamento não são exceções a esse respeito. Primeiro, é declarado o que um especialista deve fazer (abrir e fechar posições, acionar a parada de segurança), e só depois é especificado em quais eventos e como isto deve ser feito. Mas em nenhum caso vice e versa: receber sinais e pensar aonde e como os processá-los. Esta é a lógica reversa, e é melhor não usá-la, uma vez que como resultado você conseguirá um código pesado com grande número de ramos de condição.
Aqui está um exemplo de lógica reversa e direta. Tome a abertura/fechamento pelo sinal RSI.
- Na lógica reversa, o especialista começa obtendo o valor indicador, e depois ele checha a direção do sinal e o que você deve fazer com a posição: abrir a Compra, e fechar a Venda, ou vice e versa - abrir a Venda e fechar a Compra. Isto é, o ponto de entrada é obter e analisar o sinal.
- Na lógica direta, tudo é oposto. Especialistas têm dois módulos de abertura e fechamento de posições, e eles simplesmente checam as condições para executar estes módulos. Ou seja, após dar entrada ao modulo de entrada, o especialista recebe o valor indicador e checa se este é um sinal para iniciar. Então, após dar entrada aos módulos de encerramento, o especialista checa se este é um sinal para fechar a posição. Isto é, não há ponto de entrada - existem módulos operando independentemente do sistema de análise de estado (o primeiro princípio do planejamento).
Agora, se você quer complicar o especialista, será muito mais fácil utilizando a segunda variante ao invés da primeira. Isto será suficiente para criar um novo módulo de processamento de evento.
E, na primeira variante, você terá de revisar a estrutura do processamento de sinal ou colá-la como uma função a parte.
Para melhor entender essa abordagem, aqui estão os diferentes esquemas de trabalho no contexto de quatro diferentes especialistas.
Figura 2. Exemplos de implementação de especialista
a) Especialista, baseado apenas em sinais de algum indicador. Pode abrir e fechar posições quando sinal está mudando. Exemplo - um especialista MA.
b) Especialista com controle gráfico de negócios.
c) Especialista baseado em indicadores, mas com adição de Trailling Stop Loss e tempo operante. Exemplo - escalpelando nas notícias com a posição de abertura na tendência pelo indicador MA.
d) Especialista sem indicadores, com tendenciamento de posições. Ele verifica parâmetros de posição apenas quando estiver abrindo uma nova barra. Exemplo - especialista de tendenciamento.
3. Implementação da classe especialista
Crie uma classe usando todas as regras e requerimentos mencionados acima, a qual será base para todos os futuros especialistas.
A funcionalidade mínima que deverá ter na classe CExpertAdvisor são as seguintes:
1. Inicialização
- Registrar indicadores;
- Estabelecer os valores inicias dos parâmetros;
- Ajustar para o simbolo e prazo requeridos.
2. Funções de Obtenção de Sinais
- Tempo de trabalho permitido (intervalos trocados);
- Determinar o sinal para abrir/fechar posições ou pedidos;
- Determinar o filtro (tendencia, tempo, etc.);
- Cronômetro Iniciar/Parar.
3. Funções de serviço
- Calcular o preço de abertura, os níveis SL e TP, o volume do pedido;
- Enviar requisições de negócio (abrir, fechar, modificar).
4. Módulos de negócio
- Processar os sinais e filtros;
- Controlar as posições e pedidos;
- Trabalhar nas funções especialistas: OnTrade(), OnTimer(), OnTester(), OnChartEvent().
5. Desinicialização
- Mensagens de produção, relatórios;
- Limpar mapa, descarregar indicadores.
Todas as funções da classe são divididas em três grupos. O esquema geral de funções agrupadas e suas descrições são apresentadas abaixo.
Figura 3. Esquema de agrupamento de funções de um especialista
1. Funções de macro
Este pequeno grupo de funções é a base para trabalhar com tipos de pedidos, parâmetros de símbolos e valores de preço para estabelecer pedidos. Estas funções de macro fornecem o segundo principio do planejamento - abstração. Eles trabalham no contexto do símbolo, o qual é usado pelo especialista.
Funções de macro de conversão de tipos funcionam com a direção do mercado - compra ou venda. Portanto, para não criar suas próprias constantes, melhor usar as já existentes ORDER_TYPE_BUY e ORDER_TYPE_SELL. Aqui estão alguns exemplos de uso de macro e os resultados de seu trabalho.
//--- Type conversion macro long BaseType(long dir); // returns the base type of order for specified direction long ReversType(long dir); // returns the reverse type of order for specified direction long StopType(long dir); // returns the stop-order type for specified direction long LimitType(long dir); // returns the limit-order type for specified direction //--- Normalization macro double BasePrice(long dir); // returns Bid/Ask price for specified direction double ReversPrice(long dir); // returns Bid/Ask price for reverse direction long dir,newdir; dir=ORDER_TYPE_BUY; newdir=ReversType(dir); // newdir=ORDER_TYPE_SELL newdir=StopType(dir); // newdir=ORDER_TYPE_BUY_STOP newdir=LimitType(dir); // newdir=ORDER_TYPE_BUY_LIMIT newdir=BaseType(newdir); // newdir=ORDER_TYPE_BUY double price; price=BasePrice(dir); // price=Ask price=ReversPrice(dir); // price=Bid
Quando desenvolvendo especialistas, as macro permitem a você não especificar a direção processada e ajuda a criar um código mais compacto.
2. Funções de serviço
Estas funções são designadas para trabalhar com pedidos e posições. Assim como a função de macro, elas também são de baixo nível. Para conveniência, elas podem ser divididas em duas categorias: funções de informação e funções executivas. Elas todas realizam apenas um tipo de ação, sem analisar nenhum evento. Elas realizam pedidos de administradores mais velhos de especialistas.
Exemplos de funções de informação: achar o preço máximo de abertura de pedidos pendentes atuais; achar como a posição foi fechada - com lucro ou prejuízo; obter o número e lista de bilhetes de pedidos de especialistas, etc.
Exemplos de funções executivas: fechamento de pedidos específicos, modificar o Stop Loss na posição específica, etc.
Este grupo é o maior. Este é o tipo de funcionalidade, na qual toda a rotina de trabalho de um especialista é baseada. O grande número de exemplos destas funções podem ser achadas no fórum em https://www.mql5.com/ru/forum/107476. Mas em adição a isto a biblioteca padrão do MQL5 já contém classes que tomam para si mesmas parte do trabalho em situar pedidos e posições, particularmente - a classe CTrade.
Mas qualquer tarefa sua necessitará criar novas implementações ou modificar ligeiramente as existentes.
3. Módulos de processamento de evento
O grupo destas funções é uma superestrutura de alto nível acima dos primeiros dois grupos. Como mencionado acima - estes são blocos prontos para o uso para qual o seu especialista é construído. De maneira geral, eles estão incluídos na função de processamento de evento do programa MQL: OnStart(), OnTick(), OnTimer(), OnTrade(), OnChartEvent(). Este grupo não é numeroso, e os componentes destes módulos podem ser ajustados de tarefa para tarefa. Mas essencialmente nada muda.
Nos módulos tudo deve ser abstrato (o segundo princípio do planejamento) afim do mesmo módulo poder ser invocado tanto para comprar quanto para vender. Isto é alcançado, é claro, com a ajuda da macro.
Então, proceda com implementação
1. Inicialização, desinicialização
class CExpertAdvisor { protected: bool m_bInit; // flag of correct initialization ulong m_magic; // magic number of expert string m_smb; // symbol, on which expert works ENUM_TIMEFRAMES m_tf; // working timeframe CSymbolInfo m_smbinf; // symbol parameters int m_timer; // time for timer public: double m_pnt; // consider 5/3 digit quotes for stops CTrade m_trade; // object to execute trade orders string m_inf; // comment string for information about expert's work
Este é o grupo de parâmetros mínimos requeridos para as funções especialistas funcionarem.
Os parâmetros m_smb e m_tf são especialmente colocados nas propriedades do especialista, para facilmente dizer a ele em qual moeda e qual período trabalhar. Por exemplo, se você associar m_smb = "USDJPY", o especialista vai trabalhar neste símbolo, independentemente de qual símbolo ele foi executado. Se você estabelecer tf = PERIOD_H1, então todos os sinais e análises de indicadores tomarão lugar na planilha H1.
Posteriormente existem as classes de métodos. Os três primeiros métodos são a inicialização e a desinicialização de um especialista.
public: //--- Initialization void CExpertAdvisor(); // constructor void ~CExpertAdvisor(); // destructor virtual bool Init(long magic,string smb,ENUM_TIMEFRAMES tf); // initialization
O construtor e o destruidor na classe base não fazem nada.
O método Init() faz a inicialização de parâmetros especialistas pelo simbolo, prazo e número mágico.
//------------------------------------------------------------------ CExpertAdvisor void CExpertAdvisor::CExpertAdvisor() { m_bInit=false; } //------------------------------------------------------------------ ~CExpertAdvisor void CExpertAdvisor::~CExpertAdvisor() { } //------------------------------------------------------------------ Init bool CExpertAdvisor::Init(long magic,string smb,ENUM_TIMEFRAMES tf) { m_magic=magic; m_smb=smb; m_tf=tf; // set initializing parameters m_smbinf.Name(m_smb); // initialize symbol m_pnt=m_smbinf.Point(); // calculate multiplier for 5/3 digit quote if(m_smbinf.Digits()==5 || m_smbinf.Digits()==3) m_pnt*=10; m_trade.SetExpertMagicNumber(m_magic); // set magic number for expert m_bInit=true; return(true); // trade allowed }
2. Funções de obtenção de sinais
Estas funções analisam mercado e indicadores.
bool CheckNewBar(); // check for new bar bool CheckTime(datetime start,datetime end); // check allowed trade time virtual long CheckSignal(bool bEntry); // check signal virtual bool CheckFilter(long dir); // check filter for direction
As duas primeiras funções tem implementação bem específica e pode ser usada em futuras filhas desta classe.
//------------------------------------------------------------------ CheckNewBar bool CExpertAdvisor::CheckNewBar() // function of checking new bar { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) // copy bar { Print("CopyRates of ",m_smb," failed, no history"); return(false); } if(rt[1].tick_volume>1) return(false); // check volume return(true); } //--------------------------------------------------------------- CheckTime bool CExpertAdvisor::CheckTime(datetime start,datetime end) { datetime dt=TimeCurrent(); // current time if(start<end) if(dt>=start && dt<end) return(true); // check if we are in the range if(start>=end) if(dt>=start|| dt<end) return(true); return(false); }
O segundo sempre depende destes indicadores que você esta usando. É simplesmente impossível ver estas funções para todas as classes.
O assunto principal - é importante entender que as funções de sinal CheckSignal() e CheckFilter() podem analisar absolutamente qualquer indicador e suas combinações! Ou seja, módulos de negócio, nos quais estes sinais serão subsequentemente incluídos, são independentes das fontes.
Isto lhe permite usar o especialista uma vez escrito, como modelo para especialistas que funcionem em um princípio similar. Apenas mude os indicadores analisados ou adicione novas condições de filtragem.
3. Funções de serviço
Como já mencionado, este grupo de função é o mais numeroso. Para nossas tarefas práticas descritas no artigo, será suficiente implementar quatro de tais funções.
double CountLotByRisk(int dist,double risk,double lot); // calculate lot by size of risk ulong DealOpen(long dir,double lot,int SL,int TP); // execute deal with specified parameter ulong GetDealByOrder(ulong order); // get deal ticket by order ticket double CountProfitByDeal(ulong ticket); // calculate profit by deal ticket
//------------------------------------------------------------------ CountLotByRisk double CExpertAdvisor::CountLotByRisk(int dist,double risk,double lot) // calculate lot by size of risk { if(dist==0 || risk==0) return(lot); m_smbinf.Refresh(); return(NormalLot(AccountInfoDouble(ACCOUNT_BALANCE)*risk/(dist*10*m_smbinf.TickValue()))); } //------------------------------------------------------------------ DealOpen ulong CExpertAdvisor::DealOpen(long dir,double lot,int SL,int TP) { double op,sl,tp,apr,StopLvl; // determine price parameters m_smbinf.RefreshRates(); m_smbinf.Refresh(); StopLvl = m_smbinf.StopsLevel()*m_smbinf.Point(); // remember stop level apr = ReversPrice(dir); op = BasePrice(dir); // open price sl = NormalSL(dir, op, apr, SL, StopLvl); // stop loss tp = NormalTP(dir, op, apr, TP, StopLvl); // take profit // open position m_trade.PositionOpen(m_smb,(ENUM_ORDER_TYPE)dir,lot,op,sl,tp); ulong order = m_trade.ResultOrder(); if(order<=0) return(0); // order ticket return(GetDealByOrder(order)); // return deal ticket } //------------------------------------------------------------------ GetDealByOrder ulong CExpertAdvisor::GetDealByOrder(ulong order) // get deal ticket by order ticket { PositionSelect(m_smb); HistorySelectByPosition(PositionGetInteger(POSITION_IDENTIFIER)); uint total=HistoryDealsTotal(); for(uint i=0; i<total; i++) { ulong deal=HistoryDealGetTicket(i); if(order==HistoryDealGetInteger(deal,DEAL_ORDER)) return(deal); // remember deal ticket } return(0); } //------------------------------------------------------------------ CountProfit double CExpertAdvisor::CountProfitByDeal(ulong ticket) // position profit by deal ticket { CDealInfo deal; deal.Ticket(ticket); // deal ticket HistorySelect(deal.Time(),TimeCurrent()); // select all deals after this uint total = HistoryDealsTotal(); long pos_id = deal.PositionId(); // get position id double prof = 0; for(uint i=0; i<total; i++) // find all deals with this id { ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(ticket,DEAL_POSITION_ID)!=pos_id) continue; prof += HistoryDealGetDouble(ticket,DEAL_PROFIT); // summarize profit } return(prof); // return profit }
4. Módulos de negócio
Finalmente, este grupo de funções vincula todo o processo de negociação, processando os sinais e eventos, usando as funções de macro e as funções de serviço. Lexemas lógicos de operações de negócio são poucos, eles dependem dos seus objetivos específicos. Entretanto, nós podemos diferir os conceitos comuns, que existem em quase todos os especialistas.
virtual bool Main(); // main module controlling trade process virtual void OpenPosition(long dir); // module of opening position virtual void CheckPosition(long dir); // check position and open additional ones virtual void ClosePosition(long dir); // close position virtual void BEPosition(long dir,int BE); // moving Stop Loss to break-even virtual void TrailingPosition(long dir,int TS); // trailing position of Stop Loss virtual void OpenPending(long dir); // module of opening pending orders virtual void CheckPending(long dir); // work with current orders and open additional ones virtual void TrailingPending(long dir); // move pending orders virtual void DeletePending(long dir); // delete pending orders
Nós consideraremos implementações específicas destas funções nos exemplos abaixo.
Adicionar novas funções não será difícil, uma vez que nós escolhamos a abordagem correta e compomos a estrutura especialista. Se você utilizar exatamente este esquema, projetos necessitarão esforços e tempo mínimos, o código será legível depois mesmo de um ano.
E claro, seus especialistas não são limitados a eles. Na classe CExpertAdvisor, nós declaramos apenas os métodos mais necessários. Você pode adicionar novos manipuladores na children classes, modificar as já existentes, expandir os seus próprios módulos, deste modo criando uma biblioteca única. Tendo tal biblioteca, o desenvolvimento de especialistas "carcereiros" leva de meia hora a dois dias.
4. Exemplos do Uso da Classe CExpertAdvisor
4.1. Exemplo de trabalho baseado nos sinais indicadoresComo primeiro exemplo, vamos começar com uma tarefa simples - considere o Consultor Especialista de Média Móvel (exemplo básico do MetaTrader 5) usando a classe CExpertAdvisor. Vamos complicar um pouco.
Algoritmo:
a) Condição para abertura de posição
- Se o preço cruzar a MA de baixo pra cima, então abra a posição para Compra.
- Se o preço cruzar a MA de cima pra baixo, então abra a posição para Venda.
- Estabeleça SL (Stop Loss - Parar Perdas), TP (Take Profit - Obter Lucros).
- O lote da posição é calculado pelo parâmetro de Risco - quantos vão perder pelo depósito quando o Stop Loss for ativado.
b) Condição para fechamento de posição
- Se o preço cruzar a MA de baixo pra cima, então feche a posição para Venda.
- Se o preço cruzar a MA de cima pra baixo, então feche a posição para Compra.
c) Limitação
- Limitar o trabalho de um especialista diariamente por tempo pelo HourStart até o HourEnd.
- Especialistas realizam operações de negociação apenas na nova barra.
d) Suporte de posição
- Use um simples acompanhamento de parada a uma distância do TS.
Para nosso especialista, nós necessitaremos de sete funções da classe CExpertAdvisor:
- Função de sinal - CheckSignal()
- Filtros de ponto - CheckNewBar()
- Filtro de tempo - CheckTime()
- Funções de serviço de abertura de posição - DealOpen()
- Três módulos operantes - OPenPosition(), ClosePosition(), TraillingPostion()
A função CheckSignal() e os módulos devem ser definidos na classe filha para resolver especificamente suas funções. Nós também precisamos adicionar o indicador de inicialização.
//+------------------------------------------------------------------+ //| Moving Averages.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" input double Risk = 0.1; // Risk input int SL = 100; // Stop Loss distance input int TP = 100; // Take Profit distance input int TS = 30; // Trailing Stop distance input int pMA = 12; // Moving Average period input int HourStart = 7; // Hour of trade start input int HourEnd = 20; // Hour of trade end //--- class CMyEA : public CExpertAdvisor { protected: double m_risk; // size of risk int m_sl; // Stop Loss int m_tp; // Take Profit int m_ts; // Trailing Stop int m_pMA; // MA period int m_hourStart; // Hour of trade start int m_hourEnd; // Hour of trade end int m_hma; // MA indicator public: void CMyEA(); void ~CMyEA(); virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // initialization virtual bool Main(); // main function virtual void OpenPosition(long dir); // open position on signal virtual void ClosePosition(long dir); // close position on signal virtual long CheckSignal(bool bEntry); // check signal }; //------------------------------------------------------------------ CMyEA void CMyEA::CMyEA() { } //----------------------------------------------------------------- ~CMyEA void CMyEA::~CMyEA() { IndicatorRelease(m_hma); // delete MA indicator } //------------------------------------------------------------------ Init bool CMyEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class m_risk=Risk; m_tp=TP; m_sl=SL; m_ts=TS; m_pMA=pMA; // copy parameters m_hourStart=HourStart; m_hourEnd=HourEnd; m_hma=iMA(m_smb,m_tf,m_pMA,0,MODE_SMA,PRICE_CLOSE); // create MA indicator if(m_hma==INVALID_HANDLE) return(false); // if there is an error, then exit m_bInit=true; return(true); // trade allowed } //------------------------------------------------------------------ Main bool CMyEA::Main() // main function { if(!CExpertAdvisor::Main()) return(false); // call function of parent class if(Bars(m_smb,m_tf)<=m_pMA) return(false); // if there are insufficient number of bars if(!CheckNewBar()) return(true); // check new bar // check each direction long dir; dir=ORDER_TYPE_BUY; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); dir=ORDER_TYPE_SELL; OpenPosition(dir); ClosePosition(dir); TrailingPosition(dir,m_ts); return(true); } //------------------------------------------------------------------ OpenPos void CMyEA::OpenPosition(long dir) { if(PositionSelect(m_smb)) return; // if there is an order, then exit if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) return; if(dir!=CheckSignal(true)) return; // if there is no signal for current direction double lot=CountLotByRisk(m_sl,m_risk,0); if(lot<=0) return; // if lot is not defined then exit DealOpen(dir,lot,m_sl,m_tp); // open position } //------------------------------------------------------------------ ClosePos void CMyEA::ClosePosition(long dir) { if(!PositionSelect(m_smb)) return; // if there is no position, then exit if(!CheckTime(StringToTime(IntegerToString(m_hourStart)+":00"), StringToTime(IntegerToString(m_hourEnd)+":00"))) { m_trade.PositionClose(m_smb); return; } // if it's not time for trade, then close orders if(dir!=PositionGetInteger(POSITION_TYPE)) return; // if position of unchecked direction if(dir!=CheckSignal(false)) return; // if the close signal didn't match the current position m_trade.PositionClose(m_smb,1); // close position } //------------------------------------------------------------------ CheckSignal long CMyEA::CheckSignal(bool bEntry) { MqlRates rt[2]; if(CopyRates(m_smb,m_tf,0,2,rt)!=2) { Print("CopyRates ",m_smb," history is not loaded"); return(WRONG_VALUE); } double ma[1]; if(CopyBuffer(m_hma,0,0,1,ma)!=1) { Print("CopyBuffer MA - no data"); return(WRONG_VALUE); } if(rt[0].open<ma[0] && rt[0].close>ma[0]) return(bEntry ? ORDER_TYPE_BUY:ORDER_TYPE_SELL); // condition for buy if(rt[0].open>ma[0] && rt[0].close<ma[0]) return(bEntry ? ORDER_TYPE_SELL:ORDER_TYPE_BUY); // condition for sell return(WRONG_VALUE); // if there is no signal } CMyEA ea; // class instance //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // initialize expert return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.Main(); // process incoming tick }
Vamos analisar a estrutura da função Main(). Convencionalmente é dividida em duas partes.
Na primeira parte, a função principal é nomeada. Essa função processa os parâmetros possíveis que afetam globalmente o trabalho de um especialista. Isto inclui checar a permissão para negociar para um especialista e a validação de dados históricos.
Na segunda parte, os eventos de mercado são diretamente processados.
O filtro CheckNewBar() é testado - verificando uma nova barra. E módulos para duas direções de negócio são nomeados um após o outro.
Nos módulos tudo é organizado bastante abstratamente (o segundo princípio de planejamento). Não existe endereço direto para as propriedades do símbolo. E três módulos OpenPosition(), ClosePosition() e TraillingPosition() - Conte apenas com os parâmetros que venham do exterior. Isto lhe permite nomear estes módulos para verificação de pedidos tanto para Compra quanto para Venda.
4.2. Exemplo de utilização do CExpertAdvisor - Especialista sem indicadores, analisando o estado da posição e resultado
Para demonstrar, vamos tomar o sistema que negocia apenas na posição reversa com aumento de lote após prejuízo (este tipo de especialista é usualmente chamado de "Martingale").
a) Coloque o pedido inicial
- Quando o especialista inicia, ele abre a primeira posição para compra com lote inicial.
b) Abra as situações subsequentes
- Se a posição anterior foi fechada com lucro, então abra posição na mesma direção com lote inicial.
- se a posição anterior foi fechada com prejuízo, abra posição na direção oposta com um lote maior (usando fator).
Para nosso especialista, nós iremos necessitar de três funções da classe CExpertAdvisor:
- Abrir posição - DealOpen()
- Obtenha o valor do lucro da posição fechada pelo bilhete do acordo - CountProfitByDeal()
- Módulos operantes - OpenPosition(), CheckPosition()
Uma vez que o especialista não analisa nenhum indicador, mas apenas resultados dos acordos, nós usaremos os eventos OnTrade() para a produtividade ótima. Isto é, o especialista, que uma vez colocou o primeiro pedido inicial para Compra, colocará todos os pedidos somente depois do fechamento desta posição. Então nós colocaremos o pedido inicial no OnTick() e faremos todo o trabalho subsequente no OnTrade().
A função Init(), como usual, simplesmente inicializa os parâmetros da classe com parâmetros externos do especialista.
O módulo OpenPosition() abre a posição inicial e é bloqueado pelo sinalizador m_first.
O modulo CheckPosition() controla as futuras reversões de posição.
Estes módulos são nomeados a partir das respectivas funções do especialista: OnTick() e OnTrade().
//+------------------------------------------------------------------+ //| eMarti.mq5 | //| Copyright Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #include "ExpertAdvisor.mqh" #include input double Lots = 0.1; // Lot input double LotKoef = 2; // lot multiplier for loss input int Dist = 60; // distance to Stop Loss and Take Profit //--- class CMartiEA : public CExpertAdvisor { protected: double m_lots; // Lot double m_lotkoef; // lot multiplier for loss int m_dist; // distance to Stop Loss and Take Profit CDealInfo m_deal; // last deal bool m_first; // flag of opening the first position public: void CMartiEA() { } void ~CMartiEA() { } virtual bool Init(string smb,ENUM_TIMEFRAMES tf); // initialization virtual void OpenPosition(); virtual void CheckPosition(); }; //------------------------------------------------------------------ Init bool CMartiEA::Init(string smb,ENUM_TIMEFRAMES tf) { if(!CExpertAdvisor::Init(0,smb,tf)) return(false); // initialize parent class m_lots=Lots; m_lotkoef=LotKoef; m_dist=Dist; // copy parameters m_deal.Ticket(0); m_first=true; m_bInit=true; return(true); // trade allowed } //------------------------------------------------------------------ OnTrade void CMartiEA::OpenPosition() { if(!CExpertAdvisor::Main()) return; // call parent function if(!m_first) return; // if already opened initial position ulong deal=DealOpen(ORDER_TYPE_BUY,m_lots,m_dist,m_dist); // open initial position if(deal>0) { m_deal.Ticket(deal); m_first=false; } // if position exists } //------------------------------------------------------------------ OnTrade void CMartiEA::CheckPosition() { if(!CExpertAdvisor::Main()) return; // call parent function if(m_first) return; // if not yet placed initial position if(PositionSelect(m_smb)) return; // if position exists // check profit of previous position double lot=m_lots; // initial lot long dir=m_deal.Type(); // previous direction if(CountProfitByDeal(m_deal.Ticket())<0) // if there was loss { lot=NormalLot(m_lotkoef*m_deal.Volume()); // increase lot dir=ReversType(m_deal.Type()); // reverse position } ulong deal=DealOpen(dir,lot,m_dist,m_dist); // open position if(deal>0) m_deal.Ticket(deal); // remember ticket } CMartiEA ea; // class instance //------------------------------------------------------------------ OnInit int OnInit() { ea.Init(Symbol(),Period()); // initialize expert return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { } //------------------------------------------------------------------ OnTick void OnTick() { ea.OpenPosition(); // process tick - open first order } //------------------------------------------------------------------ OnTrade void OnTrade() { ea.CheckPosition(); // process trade event }
5. Trabalhando com eventos
Neste artigo você encontrou exemplos de processamento de dois eventos - NewTick e Trade que foram representados pelas funções OnTick() e OnTrade(), respectivamente. Na maioria dos casos, estes dois eventos são usados constantemente.
Para especialistas, existem quatro funções para processamento de eventos:
-
OnChartEvent processa um grande grupo de eventos; quando trabalhando com objetos gráficos; teclado, mouse e eventos personalizados. Por exemplo, a função é usada para criar especialistas interativos ou especialistas, construídos no princípio de administração gráfica de pedidos. Ou somente para criar controles ativos de parâmetros do programa MQL (usando botões e campos de edição). Em geral, essa função é usada para processar eventos externos de um especialista.
- OnTimer é chamado quando o evento de cronometro do sistema é processado. É usado em casos quando o programa MQL requer analisar seu ambiente regularmente, para calcular valores de indicadores, quando é necessário para continuamente referir a fonte externas de sinais, etc. A grosso modo, a função OnTimer() - é uma alternativa, ou mesmo a melhor substituição para:
- while(true) { /* perform analysis */; Sleep(1000); }.
Ou seja, o especialista não precisa trabalhar num ciclo sem fim no seu início, mas o suficiente para mover a nomeação das suas funções do OnTick() para OnTimer(). - OnBookEvent processa um evento que é gerado quando o Departamento de Mercado altera o seu estado. Este evento pode ser atribuído ao externo e realizar seu processamento de acordo com a tarefa.
- OnTester é chamado após testar o especialista em uma certa gama de data, antes que a função OnDeinit() para possíveis blindagens de gerações de testes, quando usando a otimização genética pelo parâmetro máximo personalizado.
Não esqueça que qualquer evento e suas combinações são sempre aconselháveis para uso na solução de suas tarefas específicas.
Epílogo
Como você pode ver, escrever um especialista, tendo o esquema correto, não leva muito tempo. Devido a novas possibilidades de processamento de eventos no MQL5, nós temos estruturas mais flexíveis para a administração de processos de negociação. Mas todas essas coisas se tornam uma ferramenta realmente poderosa apenas se você preparou propriamente o seu algoritmo de negociação.
O artigo descreve três princípios básicos de suas criações - Totalidade de eventos, abstração, modulação. Você fará sua negociação mais fácil, se basear seus especialistas nestes "três pilares".
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/132
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso