Tengo un enfoque ligeramente diferente. Como cada robot escribe un registro de sus operaciones, basta con hacer un script. Cuando lo ejecuto en un gráfico de símbolos, pide el número del robot (mágico) y desde el archivo con este símbolo y este número en el nombre carga las operaciones en el gráfico - todas o sólo largas/sólo cortas (puede ser útil separarlas). Este enfoque permite trabajar con la compensación, que es fundamentalmente importante para mí. E incluso con los robots de comercio en el modo de prueba - sin hacer operaciones, pero sólo simular, calcular y registrar los resultados en un archivo.
//+------------------------------------------------------------------+ //|| Estructura de transacciones. Se utiliza para crear un archivo de historial de transacciones. //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // Billete de oferta long order; // Orden de apertura de la operación long pos_id; // Identificador de posición long time_msc; // Tiempo en milisegundos datetime time; // Hora. double volume; // Volumen double price; // Precio double profit; // Beneficio double commission; // Comisión sobre la transacción double swap; // Permuta acumulada al cierre double fee; // Pago de la transacción, cargado inmediatamente después de realizarla double sl; // Nivel de Stop Loss double tp; // Nivel de Take Profit ENUM_DEAL_TYPE type; // Tipo ENUM_DEAL_ENTRY entry; // Método de cambio de posición ENUM_DEAL_REASON reason; // Motivo o fuente para realizar la transacción long magic; // ID de experto int digits; // Carácter de los dígitos ushort symbol[16]; // Símbolo ushort comment[64]; // Comentario de la transacción ushort external_id[256]; // Identificador de la operación en el sistema de negociación externo (en la Bolsa) //--- Establecer propiedades de cadena 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()); } //--- Devolver propiedades de cadena string Symbol(void) { return(::ShortArrayToString(symbol)); } string Comment(void) { return(::ShortArrayToString(comment)); } string ExternalID(void) { return(::ShortArrayToString(external_id)); } }; //+------------------------------------------------------------------+ //| Guarda las transacciones del historial en un array ||| //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { //--- estructura de la transacción SDeal deal={}; //--- solicitar el historial de operaciones en el intervalo desde el inicio hasta el momento actual if(!HistorySelect(0, TimeCurrent())) { Print("HistorySelect() failed. Error ", GetLastError()); return 0; } //--- número total de operaciones en la lista int total=HistoryDealsTotal(); //--- procesar cada transacción for(int i=0; i<total; i++) { //--- obtener un ticket de la siguiente operación (la operación se selecciona automáticamente para obtener sus propiedades) ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; //--- guardar sólo el balance y las transacciones comerciales 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; //--- almacenar las propiedades de la transacción en la estructura 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); //--- incrementa el array y int size=(int)array.Size(); ResetLastError(); if(ArrayResize(array, size+1, total)!=size+1) { Print("ArrayResize() failed. Error ", GetLastError()); continue; } //--- almacenar la transacción en la matriz array[size]=deal; //--- si está activada, muestra la descripción de la transacción guardada en el diario if(logs) DealPrint(deal, i); } //--- devuelve el número de tratos almacenados en el array return (int)array.Size(); }
No entiendo por qué se ignora la lógica de la arquitectura.
- Si el array se rellena elemento a elemento, debería haber una función para rellenar un elemento.
- Si un elemento es una estructura, debería llenarse por sí mismo.
- Si asignas un espacio de reserva para el array de una vez, ¿por qué necesitas realizar un "redimensionamiento" en cada llenado?
¿Por qué no escribir, por ejemplo, así?
//+------------------------------------------------------------------+ //|| Estructura de transacciones. Se utiliza para crear un archivo de historial de transacciones. //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // Billete de oferta long order; // Orden de apertura de la operación long pos_id; // Identificador de posición long time_msc; // Tiempo en milisegundos datetime time; // Hora. double volume; // Volumen double price; // Precio double profit; // Beneficio double commission; // Comisión sobre la transacción double swap; // Permuta acumulada al cierre double fee; // Pago de la transacción, cargado inmediatamente después de realizarla double sl; // Nivel de Stop Loss double tp; // Nivel de Take Profit ENUM_DEAL_TYPE type; // Tipo ENUM_DEAL_ENTRY entry; // Método de cambio de posición ENUM_DEAL_REASON reason; // Motivo o fuente para realizar la transacción long magic; // ID de experto int digits; // Carácter de los dígitos ushort symbol[16]; // Símbolo ushort comment[64]; // Comentario de la transacción ushort external_id[256]; // Identificador de la operación en el sistema de negociación externo (en la Bolsa) //--- Establecer propiedades de cadena 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()); } //--- Devolver propiedades de cadena 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 ) { //--- guardar sólo el balance y las transacciones comerciales 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)); } //+------------------------------------------------------------------+ //| Guarda las transacciones del historial en un array ||| //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { int Amount = 0; //--- solicitar el historial de operaciones en el intervalo desde el inicio hasta el momento actual if(HistorySelect(0, INT_MAX)) { //--- número total de operaciones en la lista const int total=ArrayResize(array, HistoryDealsTotal()); //--- procesar cada transacción for(int i=0; i<total; i++) if (SetDeal(array[Amount], HistoryDealGetTicket(i))) Amount++; } //--- devuelve el número de tratos almacenados en el array return(ArrayResize(array, Amount)); }
Yo mismo estoy lejos de escribir código de forma ejemplar. Pero seamos más sensatos de alguna manera.
Ya estamos otra vez.
//+------------------------------------------------------------------+ //| Devuelve una descripción de la transacción| //+------------------------------------------------------------------+ 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 qué no se autoimprime la estructura?
Si un elemento es una estructura, debería autorellenarse.
Muestre las estructuras de autorrellenado y autorrellenado estándar, por favor.
Estos códigos están escritos para simplificar la comprensión.
No se trata de envolverlo todo en una monstruosidad ilegible de una sola línea, aunque funcione.
Los artículos se escriben para transmitir la esencia de la forma más clara posible, no el virtuosismo de la escritura de código. Hay hilos especiales para el virtuosismo, y tú participas en ellos. Y son útiles.
Los artículos tienen un propósito diferente.
Si estamos hablando de estructuras MQ, no están disponibles en MQL5. Los autores no podían saber qué y quién las necesitaría. Por lo tanto, sólo existe la base y la posibilidad de heredar de ellas para dotar a las estructuras estándar de la funcionalidad requerida por el usuario.
Estos códigos están escritos para simplificar la comprensión.
No hay ningún propósito de envolverlo todo en una monstruosidad ilegible empaquetada en una línea, aunque funcione.
No sugiero tal cosa.
Los artículos se escriben para transmitir la esencia de la forma más clara posible, no el virtuosismo de la escritura de código. Hay hilos especiales para el virtuosismo y tú participas en ellos. Y son útiles.
Los artículos tienen otra finalidad.
El virtuosismo es el autor del libro de texto. Estoy a favor de la lógica.
Si un objeto puede hacer algo por sí mismo sin ayuda, debería hacerlo, no por él. Debería haber una jerarquía mínima en la arquitectura de la escritura de código. En tu código, sólo propones sustituir varias matrices separadas de propiedades de transacción por una única matriz de propiedades de transacción utilizando una estructura. Pues bien, no utilizas casi nada de la estructura (lectura/escritura y equiparación). El mismo estilo procedimental de trabajar con propiedades de transacción.
Si hablamos de estructuras MQ, no están disponibles en MQL5. Los autores no podían saber qué y quién las necesitaría. Por tanto, sólo existe la base y la posibilidad de heredar de ellas para dotar a las estructuras estándar de la funcionalidad requerida por el usuario.
Yo no ofrezco tal cosa.
La virtuosidad es la autora del Tutorial. Yo abogo por la lógica.
Si un objeto puede hacer algo por sí mismo sin ayuda, debe hacerlo él, no por él. Debería haber una jerarquía mínima en la arquitectura de la escritura de código. En tu código, sólo propones sustituir varias matrices separadas de propiedades de transacción por una única matriz de propiedades de transacción utilizando una estructura. Pues bien, no utilizas casi nada de la estructura (lectura/escritura y equiparación). El mismo estilo procedimental de trabajar con propiedades de transacción.
Uso las características que sugieres en los artículos sobre la librería. Aquí no es necesario.
//+------------------------------------------------------------------+ //|| Abre el archivo para escribir, devuelve el handle ||| //+------------------------------------------------------------------+ 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; } //--- con éxito return true; } //+------------------------------------------------------------------+ //|| Abre el archivo para lectura, devuelve el handle || //+------------------------------------------------------------------+ 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; } //--- con éxito return true; } //+------------------------------------------------------------------+ //| Guarda los datos de la transacción del array || a un fichero //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size) { //--- si se pasa un array vacío - infórmalo y devuelve false if(array.Size()==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- abrir el fichero para escribir, obtener su handle int handle=INVALID_HANDLE; if(!FileOpenToWrite(handle)) return false; //--- mover el puntero del fichero al final del fichero bool res=true; ResetLastError(); res&=FileSeek(handle, 0, SEEK_END); if(!res) PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError()); //--- escribe los datos del array al final del fichero file_size=0; res&=(FileWriteArray(handle, array)==array.Size()); if(!res) PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError()); else file_size=FileSize(handle); //--- cerrar el archivo FileClose(handle); return res; } //+------------------------------------------------------------------+ //| Carga los datos de la transacción desde el fichero a la matriz ||| //+------------------------------------------------------------------+ bool FileReadDealsToArray(SDeal &array[], ulong &file_size) { //--- abrir el archivo para lectura, obtener su handle int handle=INVALID_HANDLE; if(!FileOpenToRead(handle)) return false; //--- mover el puntero del fichero al final del fichero bool res=true; ResetLastError(); //--- leer datos del fichero en un array 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); //--- cerrar el archivo FileClose(handle); return res; }
Hay un error frecuente a la hora de resaltarlo y arreglarlo.
Pero aún así, ¿por qué no escribir todo este código de otra manera?
//+------------------------------------------------------------------+ //| Guarda los datos de la transacción del array || a un fichero //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], long &file_size) { return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0); } //+------------------------------------------------------------------+ //| Carga los datos de la transacción desde el fichero a la matriz ||| //+------------------------------------------------------------------+ bool FileReadDealsToArray2(SDeal &array[], long &file_size) { return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0); }
El artículo está destinado a un nivel de formación bastante bajo. Con el fin de llegar a un amplio sector del público lector.
Foro sobre comercio, sistemas automatizados de comercio y comprobación de estrategias comerciales
Probador de estrategias de MetaTrader 5: errores, fallos, sugerencias para mejorar su trabajo
fxsaber, 2019.09.06 15:45
Una buena característica que estás ignorando
MqlTick tiks[]; if (FileLoad("deribit1.out.bin", ticks)) { // ....

- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Artículo publicado Evaluación visual y ajuste comercial en MetaTrader 5:
Imaginemos una situación: en cualquier cuenta durante bastante tiempo se da un comercio más o menos activo en diferentes instrumentos por parte de diferentes asesores expertos e incluso manualmente. Y ahora, pasado un tiempo, queremos ver los resultados del trabajo. Naturalmente, podemos consultar los informes estándar sobre transacciones en el terminal pulsando la combinación de teclas Alt+E. O podemos cargar los iconos de transacciones en el gráfico y ver las entradas y salidas de las posiciones. Pero, ¿y si queremos ver en dinámica cómo se ha negociado, dónde y cómo se han abierto y cerrado las posiciones? O por ejemplo, visualizar por separado para cada símbolo, o para todos a la vez, la apertura y el cierre de posiciones, a qué niveles se han colocado las órdenes stop y si su tamaño está justificado. ¿Y si nos preguntamos "qué pasaría si..." (y aquí hay muchas opciones: otros stops, utilizar diferentes algoritmos y criterios, utilizar posiciones finales, o mover los stops hasta el punto de equilibrio, etc.); y luego probamos todos nuestros "si" con un resultado claramente visible? ¿Cómo habría cambiado el comercio si?...
Pues resulta que ya está todo preparado para abordar estas cuestiones. Todo lo que deberemos hacer es cargar la historia de la cuenta en un archivo, es decir, todas las transacciones ejecutadas, y luego ejecutar el asesor experto en el simulador de estrategias, que leerá las transacciones del archivo y abrirá/cerrará posiciones en el simulador de estrategias del terminal cliente. Con un asesor experto de este tipo, podemos añadirle código para cambiar las condiciones de salida de las posiciones y comparar cómo ha cambiado el comercio con ello, y qué habría pasado si...
¿Qué ganamos nosotros? Otra herramienta para encontrar los mejores resultados y hacer ajustes en las transacciones ya realizadas en la cuenta durante algún tiempo. Las pruebas visuales nos permitirán ver en dinámica si las posiciones se han abierto correctamente en tal o cual instrumento, y si se han cerrado en el momento adecuado, etc. Y lo principal es que podremos simplemente añadir un nuevo algoritmo al código del asesor experto, probarlo, obtener resultados y hacer ajustes a los asesor expertos que trabajan en esta cuenta.
Autor: Artyom Trishkin