Tenho uma abordagem um pouco diferente. Como cada robô grava um registro de suas negociações, basta criar um script. Quando o executo em um gráfico de símbolos, ele solicita o número do robô (mágico) e, a partir do arquivo com esse símbolo e esse número no nome, ele carrega as negociações no gráfico - todas ou apenas longas/curtas (pode ser útil separá-las). Essa abordagem permite trabalhar com compensação, o que é de fundamental importância para mim. E mesmo com robôs que negociam em modo de teste - sem fazer negociações, mas apenas simulando, calculando e registrando os resultados em um arquivo.
//+------------------------------------------------------------------+ //|| Estrutura de transações. Usada para criar um arquivo de histórico de transações. //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // Deal Ticket long order; // Ordem com base na qual a transação foi aberta long pos_id; // Identificador de posição long time_msc; // Tempo em milissegundos datetime time; // Hora. double volume; // Volume double price; // Preço double profit; // Lucro double commission; // Comissão sobre a transação double swap; // Swap acumulado no fechamento double fee; // Pagamento pela transação, cobrado imediatamente após a transação ser feita double sl; // Nível de stop loss double tp; // Nível de Take Profit ENUM_DEAL_TYPE type; // Tipo ENUM_DEAL_ENTRY entry; // Método para alterar a posição ENUM_DEAL_REASON reason; // Motivo ou fonte para a realização da transação long magic; // ID de especialista int digits; // Caractere de dígitos ushort symbol[16]; // Símbolo ushort comment[64]; // Comentário sobre a transação ushort external_id[256]; // Identificador da transação no sistema de negociação externo (na Bolsa) //--- Configuração das propriedades da string bool SetSymbol(const string deal_symbol) { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length()); } bool SetComment(const string deal_comment) { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length()); } bool SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); } //--- Propriedades da string de retorno string Symbol(void) { return(::ShortArrayToString(symbol)); } string Comment(void) { return(::ShortArrayToString(comment)); } string ExternalID(void) { return(::ShortArrayToString(external_id)); } }; //+------------------------------------------------------------------+ //| Salva as transações do histórico em uma matriz //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { //--- estrutura da transação SDeal deal={}; //--- solicitar o histórico de negócios no intervalo desde o início até o momento atual if(!HistorySelect(0, TimeCurrent())) { Print("HistorySelect() failed. Error ", GetLastError()); return 0; } //--- número total de negócios na lista int total=HistoryDealsTotal(); //--- processar cada transação for(int i=0; i<total; i++) { //--- obter um tíquete do próximo negócio (o negócio é automaticamente selecionado para obter suas propriedades) ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; //--- salvar apenas o balanço patrimonial e as transações comerciais ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE); if(deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) continue; //--- armazenar as propriedades da transação na estrutura deal.ticket=ticket; deal.type=deal_type; deal.order=HistoryDealGetInteger(ticket, DEAL_ORDER); deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY); deal.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket, DEAL_REASON); deal.time=(datetime)HistoryDealGetInteger(ticket, DEAL_TIME); deal.time_msc=HistoryDealGetInteger(ticket, DEAL_TIME_MSC); deal.pos_id=HistoryDealGetInteger(ticket, DEAL_POSITION_ID); deal.volume=HistoryDealGetDouble(ticket, DEAL_VOLUME); deal.price=HistoryDealGetDouble(ticket, DEAL_PRICE); deal.profit=HistoryDealGetDouble(ticket, DEAL_PROFIT); deal.commission=HistoryDealGetDouble(ticket, DEAL_COMMISSION); deal.swap=HistoryDealGetDouble(ticket, DEAL_SWAP); deal.fee=HistoryDealGetDouble(ticket, DEAL_FEE); deal.sl=HistoryDealGetDouble(ticket, DEAL_SL); deal.tp=HistoryDealGetDouble(ticket, DEAL_TP); deal.magic=HistoryDealGetInteger(ticket, DEAL_MAGIC); deal.SetSymbol(HistoryDealGetString(ticket, DEAL_SYMBOL)); deal.SetComment(HistoryDealGetString(ticket, DEAL_COMMENT)); deal.SetExternalID(HistoryDealGetString(ticket, DEAL_EXTERNAL_ID)); deal.digits=(int)SymbolInfoInteger(deal.Symbol(), SYMBOL_DIGITS); //--- incrementar a matriz e int size=(int)array.Size(); ResetLastError(); if(ArrayResize(array, size+1, total)!=size+1) { Print("ArrayResize() failed. Error ", GetLastError()); continue; } //--- armazenar a transação na matriz array[size]=deal; //--- se ativado, envia a descrição da transação salva para o diário if(logs) DealPrint(deal, i); } //--- retorna o número de negócios armazenados na matriz return (int)array.Size(); }
Não entendo por que a lógica da arquitetura é ignorada.
- Se a matriz for preenchida elemento por elemento, deve haver uma função para preencher um elemento.
- Se um elemento é uma estrutura, ele deve ser preenchido por si mesmo.
- Se você alocar um espaço de reserva para a matriz de uma só vez, por que precisa executar o "redimensionamento" a cada preenchimento?
Por que não escrever, por exemplo, assim?
//+------------------------------------------------------------------+ //|| Estrutura de transações. Usada para criar um arquivo de histórico de transações. //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // Deal Ticket long order; // Ordem com base na qual a transação foi aberta long pos_id; // Identificador de posição long time_msc; // Tempo em milissegundos datetime time; // Hora. double volume; // Volume double price; // Preço double profit; // Lucro double commission; // Comissão sobre a transação double swap; // Swap acumulado no fechamento double fee; // Pagamento pela transação, cobrado imediatamente após a transação ser feita double sl; // Nível de stop loss double tp; // Nível de Take Profit ENUM_DEAL_TYPE type; // Tipo ENUM_DEAL_ENTRY entry; // Método para alterar a posição ENUM_DEAL_REASON reason; // Motivo ou fonte para a realização da transação long magic; // ID de especialista int digits; // Caractere de dígitos ushort symbol[16]; // Símbolo ushort comment[64]; // Comentário sobre a transação ushort external_id[256]; // Identificador da transação no sistema de negociação externo (na Bolsa) //--- Configuração das propriedades da string bool SetSymbol(const string deal_symbol) { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length()); } bool SetComment(const string deal_comment) { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length()); } bool SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); } //--- Propriedades da string de retorno string Symbol(void) { return(::ShortArrayToString(symbol)); } string Comment(void) { return(::ShortArrayToString(comment)); } string ExternalID(void) { return(::ShortArrayToString(external_id)); } bool Set( const ulong _ticket ) { this.ticket=_ticket; this.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(_ticket, DEAL_TYPE); this.order=HistoryDealGetInteger(_ticket, DEAL_ORDER); this.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(_ticket, DEAL_ENTRY); this.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(_ticket, DEAL_REASON); this.time=(datetime)HistoryDealGetInteger(_ticket, DEAL_TIME); this.time_msc=HistoryDealGetInteger(_ticket, DEAL_TIME_MSC); this.pos_id=HistoryDealGetInteger(_ticket, DEAL_POSITION_ID); this.volume=HistoryDealGetDouble(_ticket, DEAL_VOLUME); this.price=HistoryDealGetDouble(_ticket, DEAL_PRICE); this.profit=HistoryDealGetDouble(_ticket, DEAL_PROFIT); this.commission=HistoryDealGetDouble(_ticket, DEAL_COMMISSION); this.swap=HistoryDealGetDouble(_ticket, DEAL_SWAP); this.fee=HistoryDealGetDouble(_ticket, DEAL_FEE); this.sl=HistoryDealGetDouble(_ticket, DEAL_SL); this.tp=HistoryDealGetDouble(_ticket, DEAL_TP); this.magic=HistoryDealGetInteger(_ticket, DEAL_MAGIC); this.SetSymbol(HistoryDealGetString(_ticket, DEAL_SYMBOL)); this.SetComment(HistoryDealGetString(_ticket, DEAL_COMMENT)); this.SetExternalID(HistoryDealGetString(_ticket, DEAL_EXTERNAL_ID)); this.digits=(int)SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS); return((bool)this.time); } }; bool SetDeal( SDeal &deal, const ulong ticket ) { //--- salvar apenas o balanço patrimonial e as transações comerciais const ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE); return((deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) && deal.Set(ticket)); } //+------------------------------------------------------------------+ //| Salva as transações do histórico em uma matriz //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { int Amount = 0; //--- solicitar o histórico de negócios no intervalo desde o início até o momento atual if(HistorySelect(0, INT_MAX)) { //--- número total de negócios na lista const int total=ArrayResize(array, HistoryDealsTotal()); //--- processar cada transação for(int i=0; i<total; i++) if (SetDeal(array[Amount], HistoryDealGetTicket(i))) Amount++; } //--- retorna o número de negócios armazenados na matriz return(ArrayResize(array, Amount)); }
Eu mesmo estou longe de escrever código de forma exemplar. Mas vamos ser mais sensatos de alguma forma.
Lá vamos nós de novo.
//+------------------------------------------------------------------+ //| Retorna uma descrição da transação| //+------------------------------------------------------------------+ string DealDescription(SDeal &deal, const int index) { string indexs=StringFormat("% 5d", index); if(deal.type!=DEAL_TYPE_BALANCE) return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f", indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type), deal.pos_id, deal.Symbol(), deal.magic, deal.digits, deal.price, TimeToString(deal.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.digits, deal.sl, deal.digits, deal.tp)); else return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s", indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type), deal.profit, AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.time))); }
Por que a estrutura não imprime a si mesma?
Se um elemento for uma estrutura, ele deverá ser preenchido automaticamente.
Mostre as estruturas padrão de autopublicação e autopreenchimento, por favor.
Esses códigos são escritos para simplificar a compreensão.
Não há nenhum objetivo de envolver tudo em uma monstruosidade ilegível de uma linha, mesmo que funcione.
Os artigos são escritos para transmitir a essência da forma mais clara possível, não o virtuosismo da codificação. Há tópicos especiais para virtuosismo, e você participa deles. E eles são úteis.
Os artigos têm uma finalidade diferente.
Se estivermos falando de estruturas MQ, elas não estão disponíveis na MQL5. Os autores não podiam saber o que e quem precisaria delas. Portanto, há apenas a base e a possibilidade de herança a partir delas para dotar as estruturas padrão com a funcionalidade exigida pelo usuário.
Esses códigos foram escritos para simplificar a compreensão.
Não há nenhum propósito em envolver tudo em uma monstruosidade ilegível empacotada em uma única linha, mesmo que funcione.
Não sugiro tal coisa.
Os artigos são escritos para transmitir a essência da forma mais clara possível, não o virtuosismo da escrita do código. Há tópicos especiais para virtuosismo e você participa deles. E eles são úteis.
Os artigos têm uma finalidade diferente.
O virtuosismo é o autor do livro-texto. Sou a favor da lógica.
Se um objeto pode fazer algo por si mesmo, sem ajuda, ele deve fazê-lo, e não por ele. Deve haver uma hierarquia mínima na arquitetura da escrita do código. Em seu código, você propõe apenas a substituição de várias matrizes separadas de propriedades de transação por uma única matriz de propriedades de transação usando uma estrutura. Bem, você não usa quase nada da estrutura (leitura/gravação e equiparação). O mesmo estilo processual de trabalhar com propriedades de transação.
Se estivermos falando de estruturas MQ, elas não estão disponíveis na MQL5. Os autores não podiam saber o que e quem precisaria delas. Portanto, há apenas a base e a possibilidade de herança a partir delas para dotar as estruturas padrão com a funcionalidade exigida pelo usuário.
Eu não ofereço tal coisa.
A virtuosidade é o autor do Tutorial. Eu defendo a lógica.
Se um objeto puder fazer algo por si mesmo, sem ajuda, ele deve fazer isso, e não para isso. Deve haver uma hierarquia mínima na arquitetura da escrita do código. Em seu código, você propõe apenas a substituição de várias matrizes separadas de propriedades de transação por uma única matriz de propriedades de transação usando uma estrutura. Bem, você não usa quase nada da estrutura (leitura/gravação e equiparação). O mesmo estilo processual de trabalhar com propriedades de transação.
Eu uso os recursos que você sugere nos artigos sobre a biblioteca. Isso não é necessário aqui.
//+------------------------------------------------------------------+ //|| Abre o arquivo para gravação e retorna o identificador //+------------------------------------------------------------------+ bool FileOpenToWrite(int &handle) { ResetLastError(); handle=FileOpen(PATH, FILE_WRITE|FILE_BIN|FILE_COMMON); if(handle==INVALID_HANDLE) { PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError()); return false; } //--- com sucesso return true; } //+------------------------------------------------------------------+ //|| Abre o arquivo para leitura e retorna o identificador //+------------------------------------------------------------------+ bool FileOpenToRead(int &handle) { ResetLastError(); handle=FileOpen(PATH, FILE_READ|FILE_BIN|FILE_COMMON); if(handle==INVALID_HANDLE) { PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError()); return false; } //--- com sucesso return true; } //+------------------------------------------------------------------+ //| Salva os dados da transação da matriz || em um arquivo //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size) { //--- se uma matriz vazia for passada - informe isso e retorne false if(array.Size()==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- abrir o arquivo para gravação, obter seu identificador int handle=INVALID_HANDLE; if(!FileOpenToWrite(handle)) return false; //--- mover o ponteiro do arquivo para o final do arquivo bool res=true; ResetLastError(); res&=FileSeek(handle, 0, SEEK_END); if(!res) PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError()); //--- escreva os dados da matriz no final do arquivo file_size=0; res&=(FileWriteArray(handle, array)==array.Size()); if(!res) PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError()); else file_size=FileSize(handle); //--- fechar o arquivo FileClose(handle); return res; } //+------------------------------------------------------------------+ //| Carrega os dados da transação do arquivo para a matriz //+------------------------------------------------------------------+ bool FileReadDealsToArray(SDeal &array[], ulong &file_size) { //--- abrir o arquivo para leitura, obter seu identificador int handle=INVALID_HANDLE; if(!FileOpenToRead(handle)) return false; //--- mover o ponteiro do arquivo para o final do arquivo bool res=true; ResetLastError(); //--- ler dados do arquivo em uma matriz file_size=0; // res=(FileReadArray(handle, array)>0); res=(ArrayResize(array, FileReadArray(handle, array))>0); if(!res) PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError()); else file_size=FileSize(handle); //--- fechar o arquivo FileClose(handle); return res; }
Há um erro frequente em destacar e corrigir isso.
Mas, ainda assim, por que não escrever todo esse código de forma diferente?
//+------------------------------------------------------------------+ //| Salva os dados da transação da matriz || em um arquivo //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], long &file_size) { return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0); } //+------------------------------------------------------------------+ //| Carrega os dados da transação do arquivo para a matriz //+------------------------------------------------------------------+ bool FileReadDealsToArray2(SDeal &array[], long &file_size) { return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0); }
Mas, ainda assim, por que não escrever todo esse código de forma diferente?
O artigo é destinado a um nível de treinamento bastante baixo. Seu objetivo é abranger uma grande parte dos leitores. Aqueles que são experientes e experientes não precisam desses artigos. Eles têm seus próprios bigodes.)
O artigo é destinado a um nível de treinamento bastante baixo. A fim de atingir uma ampla seção do público leitor.
Fórum sobre negociação, sistemas de negociação automatizados e teste de estratégias de negociação
MetaTrader 5 Strategy Tester: erros, bugs, sugestões para melhorar seu trabalho
fxsaber, 2019.09.06 15:45
Um bom recurso que você está ignorando
MqlTick tiks[]; if (FileLoad("deribit1.out.bin", ticks)) { // ....
- 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
Novo artigo Avaliação visual e ajuste da negociação no MetaTrader 5 foi publicado:
Imagine a seguinte situação: em uma conta específica, há uma negociação relativamente ativa sendo realizada há bastante tempo, com diferentes instrumentos, por vários EAs e até mesmo manualmente. E agora, passado algum tempo, queremos ver os resultados desse trabalho. Claro que é possível conferir os relatórios padrão de negociação no terminal pressionando Alt+E. Também dá para carregar os ícones das negociações no gráfico e visualizar as aberturas e fechamentos das posições. Mas e se quisermos ver de forma dinâmica como foi realizada a negociação, onde e como as posições foram abertas e fechadas? Ver separadamente por cada símbolo, ou todos de uma vez, os momentos de abertura e fechamento das posições, em quais níveis os stop orders foram colocados e se seu tamanho foi justificado. E se depois nos perguntarmos "o que teria acontecido se..." (e aqui cabem várias opções: outros stops, com algoritmos e critérios diferentes, uso de trailing nas posições, ou mover os stops para o ponto de equilíbrio etc.); e depois ainda testar todos esses "e se" com resultados visíveis e claros. Como a negociação poderia ter mudado se...
Para resolver esse tipo de questão, na verdade, já temos tudo o que precisamos. Basta carregar o histórico da conta em um arquivo (todas as negociações realizadas) e, em seguida, executar um EA no testador de estratégias que leia essas negociações do arquivo e abra/feche posições no testador do terminal cliente. Com um EA assim, podemos adicionar código para alterar as condições de saída das posições e comparar como a negociação teria mudado, e o que teria acontecido se...
E o que isso nos oferece? Mais uma ferramenta para buscar melhores resultados, para fazer ajustes na negociação que já estava sendo feita na conta; o teste visual permite ver em tempo real se as posições foram abertas corretamente em determinado instrumento, se foram fechadas no momento certo, e assim por diante. E o mais importante: podemos simplesmente adicionar um novo algoritmo no código do EA, testar, obter um resultado e aplicar as correções nos EAs que estão operando nessa conta.
Autor: Artyom Trishkin