Crear y Publicar Informes de Trading y Notificaciones SMS
Introducción
Este artículo describe cómo generar un informe de resultados de trading (usando un Asesor Experto, Indicador o Script) como archivo HTML y subirlo por FTP a un servidor WWW. También explicaremos cómo enviar notificaciones de eventos de trading como mensajes SMS a teléfonos móviles.
Para sentirse más cómodo con el material descrito en este artículo, se recomienda al lector que se familiarice con el lenguaje HTML.
Para implementar los informes necesitamos un servidor WWW (puede ser cualquier ordenador) que pueda acceder a datos por FTP. Para implementar la posibilidad de recibir notificaciones sobre eventos de trading por SMS, necesitaremos una puerta de enlace EMAIL-SMS (la mayoría de compañías telefónicas prestan este servicio).
1. Crear un informe y enviarlo por FTP
Creemos un programa MQL5 que generará un informe de trading y lo enviará por protocolo FTP. Primero lo crearemos como un Script. En el futuro, lo podremos usar como un bloque terminado que se podrá insertar en cualquier Asesor Experto o Indicador. Por ejemplo, en un Asesor Experto puede usar este bloque como el controlador de eventos Trade o Timer para ejecutar este bloque tras la solicitud de trading, o para establecer acciones para el evento ChartEvent. En un Indicador, puede incluir este bloque en el controlador de eventos Timer o ChartEvent.
Puede ver un ejemplo de informe creado por un programa en las Figuras 1, 2 y 3. O puede descargarse este informe yendo al enlace que se encuentra al final de este artículo.
Figura 1. Ejemplo de informe: Tabla de Transacciones y Posiciones.
Figura 2. Ejemplo de informe: Gráfico de Saldo.
Figura 3. Ejemplo de informe: Gráfico de Precio en el Instrumento Actual.
En la tabla de Transacciones y Posiciones (Deals and Positions) (Figura 1), todas las transacciones se dividen en posiciones por motivos de conveniencia. El lado izquierdo de la tabla muestra el volumen, hora y precio de introducción en el mercado (de apertura de posiciones y adiciones). El lado derecho de la tabla muestra los mismos parámetros para salir del mercado (cierre parcial o completo de posición). En in/out, la transacción se divide en dos partes: el cierre de una posición y la apertura de la siguiente.
Bajo la tabla de Transacciones y Posiciones se muestra el gráfico de Saldo (Balance chart) (eje horizontal - hora), y al final, el gráfico de Precio (Price chart) en el Instrumento Actual.
El programa crea los archivos "report.html", "picture1.gif" y "picture2.gif" (archivos html del informe, de las imágenes del gráfico de saldo y del de precio) en la carpeta MetaTarder5_istall_dir\MQL5\Files. Y la publicación FTP se activa en la configuración del terminal: envía estos tres archivos al servidor especificado. Además, necesitaremos dos archivos de imagen con flechas apuntando a la dirección de la posición abierta: Buy (Compra) o Sell (Venta) ("buy.gif" and "sell.gif"). Puede tomar estas imágenes (el enlace de descarga está al final del artículo) o dibujarlas usted mismo en cualquier editor de gráficos. Estos dos archivos se deberían colocar en la misma carpeta del servidor WWW con el archivo "report.html".
Como parámetros de entrada, el programa acepta el momento de comienzo y final del período para las que se generó el informe. En nuestro ejemplo, el final del informe es el momento actual, y el usuario selecciona la variante de período del informe: un período entero, el último día, la última semana, el último mes, o el último año.
Otros aspectos a tener en consideración sobre cómo se crea un informe: Se requiere un servidor de trading para todo el historial de transacciones disponible. Las transacciones obtenidas se procesan una tras otra. El array deal_status[] almacena información sobre si la transacción se ha procesado o no. Los índices de elemento de este array son los números de transacciones recibidos de la lista de transacciones del servidor de trading. Y los valores de los elementos se interpretan de la siguiente manera: 0 - la transacción aún no se ha procesado, 1 - la transacción se ha procesado parcialmente (in/out), 127 - la transacción se ha procesado (otros valores no se usarán y se reservarán para su futuro uso).
El array symb_list[] contiene la lista de nombres de instrumentos financieros con la que se llevó a cabo la operación de trading, y el array lots_list[] contiene los volúmenes de las posiciones abiertas para cada instrumento en el momento de procesamiento de una transacción. Los valores positivos de volumen se corresponden con posiciones largas, mientras que valores negativos se corresponden con posiciones cortas. Si el volumen es igual a cero, significa que esta herramienta no tiene posiciones abiertas. Si durante una transacción aparece un instrumento financiero que no está en la lista (en el array symb_list[]), se añade allí, y el número de instrumentos financieros (la variable symb_total) aumenta en 1.
En cada procesamiento de transacción, cada transacción subsiguiente se analiza con el mismo instrumento financiero hasta que la posición se cierra o queda en estado in/out. Solo se analizan las transacciones cuyo valor en el array deal_status[] es menor de 127. Tras el procesamiento de la transacción, el elemento correspondiente del array deal_status[] se asigna con un valor de 127, y si la transacción está en estado in/out de posición, el valor será 1. Si el momento en el que se abrió la posición coincide con el período del informe (definido por las variables StartTime y EndTime), esta posición se registrará en el informe (con todas sus entradas y salidas).
Además de la tabla de transacciones, se abre un nuevo gráfico para el instrumento financiero actual. Para este gráfico se facilitan todas las propiedades necesarias, y se hace una captura de pantalla usando la función ChartScreenShot() para obtener un archivo de imagen con el gráfico de precio para el instrumento actual. A continuación, en este gráfico se oculta el gráfico de precio y se dibuja el gráfico de cambios de saldo, y después se hace otra captura de pantalla.
Cuando se han creado dos archivos de imagen con gráficos y un archivo HTMl con informe, se comrpueva si se pueden enviar archivos por FTP. Si está permitido, los archivos "report.html", "picture1.gif" y "picture2.gif" se enviarán usando la función SendFTP(), según la configuración especificada en MetaTrader 5.
Ejecute el Language Editor (Editor de Lenguaje) de MetaQuotes y comience a crear un script. Defina las constantes: la frecuencia de actualización del gráfico (en segundos), la anchura y altura del gráfico de precio, y la anchura máxima del gráfico de saldo. El período del gráfico que mostrará la curva de cambios de saldo se elegirá dependiendo de la duración del período del informe y de la anchura máxima del gráfico. La anchura del gráfico se ajusta al tamaño que se necesita para el gráfico de saldo.
La altura del gráfico se calcula automáticamente como la mitad de la anchura. Asimismo, especificaremos la anchura del eje vertical como constante: es el número de píxeles por los que se reducen los gráficos en comparación a la anchura de la imagen a causa del eje vertical.
#define timeout 10 // chart refresh timeout #define Picture1_width 800 // max width of chart in report #define Picture2_width 800 // width of price chart in report #define Picture2_height 600 // height of price chart in report #define Axis_Width 59 // width of vertical axis (in pixels)
Especifique que los parámetros de entrada se solicitarán al usuario.
// request input parameters #property script_show_inputs
Cree una enumeración de períodos de informe.
// enumeration of report periods enum report_periods { All_periods, Last_day, Last_week, Last_month, Last_year };
Solicite un período del informe al usuario (la opción por defecto es el período entero).
// ask for report period input report_periods ReportPeriod=0;
Escriba el cuerpo de la función OnStart().
void OnStart() {
Determine el inicio y el final del período del informe.
datetime StartTime=0; // beginning of report period datetime EndTime=TimeCurrent(); // end of report period // calculating the beginning of report period switch(ReportPeriod) { case 1: StartTime=EndTime-86400; // day break; case 2: StartTime=EndTime-604800; // week break; case 3: StartTime=EndTime-2592000; // month break; case 4: StartTime=EndTime-31536000; // year break; } // if none of the options is executed, then StartTime=0 (entire period)
Declare las variables que se usarán en el programa. El propósito de las variables se describe en los comentarios.
int total_deals_number; // number of deals for history data int file_handle; // file handle int i,j; // loop counters int symb_total; // number of instruments, that were traded int symb_pointer; // pointer to current instrument char deal_status[]; // state of deal (processed/not processed) ulong ticket; // ticket of deal long hChart; // chart id double balance; // current balance value double balance_prev; // previous balance value double lot_current; // volume of current deal double lots_list[]; // list of open volumes by instruments double current_swap; // swap of current deal double current_profit; // profit of current deal double max_val,min_val; // maximal and minimal value string symb_list[]; // list of instruments, that were traded string in_table_volume; // volume of entering position string in_table_time; // time of entering position string in_table_price; // price of entering position string out_table_volume; // volume of exiting position string out_table_time; // time of exiting position string out_table_price; // price of exiting position string out_table_swap; // swap of exiting position string out_table_profit; // profit of exiting position bool symb_flag; // flag that instrument is in the list datetime time_prev; // previous value of time datetime time_curr; // current value of time datetime position_StartTime; // time of first enter to position datetime position_EndTime; // time of last exit from position ENUM_TIMEFRAMES Picture1_period; // period of balance chart
Abra un nuevo gráfico y configure sus propiedades: este es el gráfico de precio que se imprimirá al final del informe.
// open a new chart and set its properties hChart=ChartOpen(Symbol(),0); ChartSetInteger(hChart,CHART_MODE,CHART_BARS); // bars chart ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // autoscroll enabled ChartSetInteger(hChart,CHART_COLOR_BACKGROUND,White); // white background ChartSetInteger(hChart,CHART_COLOR_FOREGROUND,Black); // axes and labels are black ChartSetInteger(hChart,CHART_SHOW_OHLC,false); // OHLC are not shown ChartSetInteger(hChart,CHART_SHOW_BID_LINE,true); // show BID line ChartSetInteger(hChart,CHART_SHOW_ASK_LINE,false); // hide ASK line ChartSetInteger(hChart,CHART_SHOW_LAST_LINE,false); // hide LAST line ChartSetInteger(hChart,CHART_SHOW_GRID,true); // show grid ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,true); // show period separators ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // grid is light-gray ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,Black); // chart lines are black ChartSetInteger(hChart,CHART_COLOR_CHART_UP,Black); // up bars are black ChartSetInteger(hChart,CHART_COLOR_CHART_DOWN,Black); // down bars are black ChartSetInteger(hChart,CHART_COLOR_BID,Gray); // BID line is gray ChartSetInteger(hChart,CHART_COLOR_VOLUME,Green); // volumes and orders levels are green ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,Red); // SL and TP levels are red ChartSetString(hChart,CHART_COMMENT,ChartSymbol(hChart)); // comment contains instrument <end segm
Haga una captura de pantalla a un gráfico y guarde la imagen como "picture2.gif".
// save chart as image file ChartScreenShot(hChart,"picture2.gif",Picture2_width,Picture2_height);
Solicite el historial de transacciones para todo el tiempo de existencia de la cuenta.
// request deals history for entire period HistorySelect(0,TimeCurrent());
Abra el archivo "report.html", en el que escribiremos una página HTML con el informe (codificación ANSI).
// open report file file_handle=FileOpen("report.html",FILE_WRITE|FILE_ANSI);
Escriba la primera parte del documento HTML:
- inicio del documento html (<html>)
- título que se mostrará en la parte de arriba de su ventana de navegador (<head><title>Informe de Trading del Expert</title></head>)
- comienzo de la parte principal del documento html con color de fondo (<body bgcolor='#EFEFEF'>)
- alineamiento central (<center>)
- título de la tabla de transacciones y posiciones (<h2>Informe de Tradingt</h2>)
- comienzo de la tabla de transacciones y posiciones con alineamiento, anchura de borde, color de fondo, color de borde, espaciado de celda y margen de celda (<table align='center' border='1' bgcolor='#FFFFFF' bordercolor='#7F7FFF' cellspacing='0' cellpadding='0'>)
- encabezado de tabla
// write the beginning of HTML FileWrite(file_handle,"<html>"+ "<head>"+ "<title>Expert Trade Report</title>"+ "</head>"+ "<body bgcolor='#EFEFEF'>"+ "<center>"+ "<h2>Trade Report</h2>"+ "<table align='center' border='1' bgcolor='#FFFFFF' bordercolor='#7F7FFF' cellspacing='0' cellpadding='0'>"+ "<tr>"+ "<th rowspan=2>SYMBOL</th>"+ "<th rowspan=2>Direction</th>"+ "<th colspan=3>Open</th>"+ "<th colspan=3>Close</th>"+ "<th rowspan=2>Swap</th>"+ "<th rowspan=2>Profit</th>"+ "</tr>"+ "<tr>"+ "<th>Volume</th>"+ "<th>Time</th>"+ "<th>Price</th>"+ "<th>Volume</th>"+ "<th>Time</th>"+ "<th>Price</th>"+ "</tr>");
Obtenga número de transacciones en la lista.
// number of deals in history total_deals_number=HistoryDealsTotal();
Configure las dimensiones para los arrays symb_list[], lots_list[] y deal_status[].
// setting dimensions for the instruments list, the volumes list and the deals state arrays ArrayResize(symb_list,total_deals_number); ArrayResize(lots_list,total_deals_number); ArrayResize(deal_status,total_deals_number);
Inicialice los elementos del array deal_status[] con valor 0: todas las transacciones están sin procesar.
// setting all elements of array with value 0 - deals are not processed ArrayInitialize(deal_status,0);
Configure los valores iniciales de saldo y variable usados para almacenar el anterior valor de saldo.
balance=0; // initial balance balance_prev=0; // previous balance
Configure el valor inicial de variable usado para almacenar el número de instrumentos financieros en la lista.
// number of instruments in the list symb_total=0;
Cree un bucle que procesa secuencialmente cada transacción de la lista.
// processing all deals in history for(i=0;i<total_deals_number;i++) {
Seleccione la transacción actual y obtener su ticket.
//select deal, get ticket ticket=HistoryDealGetTicket(i);
Cambie el saldo por la cantidad de beneficio en la transacción actual.
// changing balance balance+=HistoryDealGetDouble(ticket,DEAL_PROFIT);
Obtenga la hora de la transacción - esta opción se usará de forma frecuente.
// reading the time of deal time_curr=HistoryDealGetInteger(ticket,DEAL_TIME);
Si se trata de la primera transacción de la lista, debemos ajustar los límites del período del informe y seleccionar el período para el gráfico de saldo dependiendo de la duración del período del informe y la anchura de la región en la que se representará el gráfico. Configurar los valores iniciales de saldo máximo y mínimo (estas variables se usarán para establecer un máximo y mínimo en el gráfico).
// if this is the first deal if(i==0) { // if the report period starts before the first deal, // then the report period will start from the first deal if(StartTime<time_curr) StartTime=time_curr; // if report period ends before the current time, // then the end of report period corresponds to the current time if(EndTime>TimeCurrent()) EndTime=TimeCurrent(); // initial values of maximal and minimal balances // are equal to the current balance max_val=balance; min_val=balance; // calculating the period of balance chart depending on the duration of // report period Picture1_period=PERIOD_M1; if(EndTime-StartTime>(Picture1_width-Axis_Width)) Picture1_period=PERIOD_M2; if(EndTime-StartTime>(Picture1_width-Axis_Width)*120) Picture1_period=PERIOD_M3; if(EndTime-StartTime>(Picture1_width-Axis_Width)*180) Picture1_period=PERIOD_M4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*240) Picture1_period=PERIOD_M5; if(EndTime-StartTime>(Picture1_width-Axis_Width)*300) Picture1_period=PERIOD_M6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*360) Picture1_period=PERIOD_M10; if(EndTime-StartTime>(Picture1_width-Axis_Width)*600) Picture1_period=PERIOD_M12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*720) Picture1_period=PERIOD_M15; if(EndTime-StartTime>(Picture1_width-Axis_Width)*900) Picture1_period=PERIOD_M20; if(EndTime-StartTime>(Picture1_width-Axis_Width)*1200) Picture1_period=PERIOD_M30; if(EndTime-StartTime>(Picture1_width-Axis_Width)*1800) Picture1_period=PERIOD_H1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*3600) Picture1_period=PERIOD_H2; if(EndTime-StartTime>(Picture1_width-Axis_Width)*7200) Picture1_period=PERIOD_H3; if(EndTime-StartTime>(Picture1_width-Axis_Width)*10800) Picture1_period=PERIOD_H4; if(EndTime-StartTime>(Picture1_width-Axis_Width)*14400) Picture1_period=PERIOD_H6; if(EndTime-StartTime>(Picture1_width-Axis_Width)*21600) Picture1_period=PERIOD_H8; if(EndTime-StartTime>(Picture1_width-Axis_Width)*28800) Picture1_period=PERIOD_H12; if(EndTime-StartTime>(Picture1_width-Axis_Width)*43200) Picture1_period=PERIOD_D1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*86400) Picture1_period=PERIOD_W1; if(EndTime-StartTime>(Picture1_width-Axis_Width)*604800) Picture1_period=PERIOD_MN1; // changing the period of opened chart ChartSetSymbolPeriod(hChart,Symbol(),Picture1_period); }
Si esta transacción no es la primera, cree el objeto "line" usando la línea en la que se representa el gráfico de cambio de saldo. La línea se representa solo si al menos uno de sus extremos está dentro del período del informe. Si ambos extremos se encuentran dentro del período del informe, la línea será "ancha". El color de la línea de saldo es verde. Si el saldo está más allá del margen de saldo mínimo y máximo, este margen se ajusta.
else // if this is not the first deal { // plotting the balance line, if the deal is in the report period, // and setting properties of the balance line if(time_curr>=StartTime && time_prev<=EndTime) { ObjectCreate(hChart,IntegerToString(i),OBJ_TREND,0,time_prev,balance_prev,time_curr,balance); ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_COLOR,Green); // if both ends of line are in the report period, // it will be "thick" if(time_prev>=StartTime && time_curr<=EndTime) ObjectSetInteger(hChart,IntegerToString(i),OBJPROP_WIDTH,2); } // if new value of balance exceeds the range // of minimal and maximal values, it must be adjusted if(balance<min_val) min_val=balance; if(balance>max_val) max_val=balance; }
Asigne el valor temporal anterior a la variable correspondiente.
// changing the previous time value
time_prev=time_curr;
Si la transacción no se ha procesado todavía, procésela.
// if the deal has not been processed yet if(deal_status[i]<127) {
Si esta transacción supone un cambio en el saldo y está dentro del período del informe, la cadena de caracteres correspondiente se añade al informe. La transacción se marca como procesada.
// If this deal is balance charge if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BALANCE) { // if it's in the report period - write the corresponding string to report. if(time_curr>=StartTime && time_curr<=EndTime) FileWrite(file_handle,"<tr><td colspan='9'>Balance:</td><td align='right'>",HistoryDealGetDouble(ticket,DEAL_PROFIT), "</td></tr>"); // mark deal as processed deal_status[i]=127; }
Si esta transacción es de Compra o Venta, compruebe si este instrumento está en la lista (el array symb_list[]). Si no, añádalo. La variable symb_pointer apunta a los elementos del array symb_list[], que contiene el nombre del instrumento de la transacción actual.
// if this deal is buy or sell if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY || HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // check if there is instrument of this deal in the list symb_flag=false; for(j=0;j<symb_total;j++) { if(symb_list[j]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { symb_flag=true; symb_pointer=j; } } // if there is no instrument of this deal in the list if(symb_flag==false) { symb_list[symb_total]=HistoryDealGetString(ticket,DEAL_SYMBOL); lots_list[symb_total]=0; symb_pointer=symb_total; symb_total++; }
Configure los valores iniciales de las variables position_StartTime y position_EndTime, que almacenan el tiempo de vida inicial y final de la posición.
// set the initial value for the beginning time of deal position_StartTime=time_curr; // set the initial value for the end time of deal position_EndTime=time_curr;
Las variables in_table_volume, in_table_time, in_table_price, out_table_volume, out_table_time, out_table_price, out_table_swap y out_table_profit almacenarán tablas que estarán dentro de celdas de una tabla más grande: volumen, hora y precio de apertura de mercado; volumen, hora, precio, cambio y beneficio de salida de mercado. La variable in_table_volume también almacenarán el nombre del instrumento financiero y el enlace a una imagen que se corresponde con la dirección de una posición abierta. Asignar todas estas variables con valores iniciales.
// creating the string in report - instrument, position direction, beginning of table for volumes to enter the market if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) StringConcatenate(in_table_volume,"<tr><td align='left'>", symb_list[symb_pointer],"</td><td align='center'><img src='buy.gif'></td><td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"); if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) StringConcatenate(in_table_volume,"<tr><td align='left'>", symb_list[symb_pointer],"</td><td align='center'><img src='sell.gif'></td><td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"); // creating the beginning of time table to enter the market in_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of price table to enter the market in_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of volume table to exit the market out_table_volume="<td><table border='1' width='100%' bgcolor=#FFFFFF bordercolor='#DFDFFF'>"; // creating the beginning of time table to exit the market out_table_time="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of price table to exit the market out_table_price="<td><table border='1' width='100%' bgcolor='#FFFFFF' bordercolor='#DFDFFF'>"; // creating the beginning of swap table to exit the market out_table_swap="<td><table border='1' width='100%' bgcolor=#FFFFFF bordercolor='#DFDFFF'>"; // creating the beginning of profit table to exit the market out_table_profit="<td><table border='1' width='100%' bgcolor=#FFFFFF bordercolor='#DFDFFF'>";
Procese todas las transacciones con la posición actual hasta que se cierre. Procesarlas todas ahora si no lo había hecho antes.
// process all deals for this position starting with the current(until position is closed) for(j=i;j<total_deals_number;j++) { // if the deal has not been processed yet - process it if(deal_status[j]<127) {
Seleccione una transacción y obtener su ticket.
// select deal, get ticket ticket=HistoryDealGetTicket(j);
Si la transacción está en el mismo instrumento que la posición abierta, procésela. Obtenga la hora de la transacción. Si el momento de la transacción está más allá del margen temporal de la posición, extienda este margen. Obtener el volumen de la transacción.
// if the instrument of deal matches the instrument of position, that is processed if(symb_list[symb_pointer]==HistoryDealGetString(ticket,DEAL_SYMBOL)) { // get the deal time time_curr=HistoryDealGetInteger(ticket,DEAL_TIME); // If the deal time goes beyond the range of position time // - extend position time if(time_curr<position_StartTime) position_StartTime=time_curr; if(time_curr>position_EndTime) position_EndTime=time_curr; // get the volume of deal lot_current=HistoryDealGetDouble(ticket,DEAL_VOLUME);
Las transacciones de Compra y Venta se procesan por separado. Comience con las transacciones de Compra.
// if this deal is buy if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_BUY) {
Si ya ha abierto una posición para venta, esta transacción de Compra supondrá una salida del mercado. Y si el volumen de la transacción es mayor que el volumen de la posición corta abierta, pasará a in/out. Asigne variables de cadena de caracteres con los valores requeridos. Asigne un valor de 127 al array deal_status[] si la transacción se ha procesado completamente, o un valor de 1 si está en in/out, y esta transacción debe analizarse para otra posición.
// if position is opened for sell - this will be exit from market if(NormalizeDouble(lots_list[symb_pointer],2)<0) { // if buy volume is greater than volume of opened short position - then this is in/out if(NormalizeDouble(lot_current+lots_list[symb_pointer],2)>0) { // creating table of volumes to exit the market - indicate only volume of opened short position StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // mark position as partially processed deal_status[j]=1; } else { // if buy volume is equal or less than volume of opened short position - then this is partial or full close // creating the volume table to exit the market StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // mark deal as processed deal_status[j]=127; } // creating the time table to exit the market StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table to exit the market StringConcatenate(out_table_price,out_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // get the swap of current deal current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // if swap is equal to zero - create empty string of the swap table to exit the market if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // else create the swap string in the swap table to exit the market else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // get the profit of current deal current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // if profit is negative (loss) - it is displayed as red in the profit table to exit the market if(NormalizeDouble(current_profit,2)<0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align=right><SPAN style='COLOR: #EF0000'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); // else - it is displayed as green else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); }
Si ya ha abierto una posición larga, la compra en esta transacción será la entrada al mercado (la primera, o una adición). Si el elemento del array deal_status[] que se corresponde con esta transacción tiene un valor de 1, significa que está en estado in/out. Asigne variables de cadena de caracteres con los valores requeridos y marque la transacción como procesada (asigne el elemento correspondiente del array deal_status[] con un valor de127).
else // if position is opened for buy - this will be the enter to the market { // if this deal has been already partially processed (in/out) if(deal_status[j]==1) { // create the volume table of entering the market (volume, formed after in/out, is put here) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // indemnity of volume change, which will be produced (the volume of this deal is already taken into account) lots_list[symb_pointer]-=lot_current; } // if this deal has not been processed yet, create the volume table to enter the market else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // creating the time table of entering the market StringConcatenate(in_table_time,in_table_time,"<tr><td align center>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table of entering the market StringConcatenate(in_table_price,in_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // mark deal as processed deal_status[j]=127; }
Cambie el volumen de la posición al volumen de la transacción actual. Si la posición está cerrada (el volumen es equivalente a 0), deje de procesar esta posición (salga del bucle con la variable j) y busque la siguiente posición no procesada (en el bucle con la variable i).
// change of position volume by the current instrument, taking into account the volume of current deal lots_list[symb_pointer]+=lot_current; // if the volume of opened position by the current instrument became equal to zero - position is closed if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; }
Las transacciones de venta se procesan de forma similar, y después salimos de bucle con la variable j.
// if this deal is sell if(HistoryDealGetInteger(ticket,DEAL_TYPE)==DEAL_TYPE_SELL) { // if position has been already opened for buy - this will be the exit from market if(NormalizeDouble(lots_list[symb_pointer],2)>0) { // if sell volume is greater than volume of opened long position - then this is in/out if(NormalizeDouble(lot_current-lots_list[symb_pointer],2)>0) { // creating table of volumes to exit the market - indicate only volume of opened long position StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lots_list[symb_pointer],2),"</td></tr>"); // mark position as partially processed deal_status[j]=1; } else { // if sell volume is equal or greater than volume of opened short position - then this is partial or full close // creating the volume table to exit the market StringConcatenate(out_table_volume,out_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // mark deal as processed deal_status[j]=127; } // creating the time table to exit the market StringConcatenate(out_table_time,out_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table to exit the market StringConcatenate(out_table_price,out_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // get the swap of current deal current_swap=HistoryDealGetDouble(ticket,DEAL_SWAP); // if swap is equal to zero - create empty string of the swap table to exit the market if(NormalizeDouble(current_swap,2)==0) StringConcatenate(out_table_swap,out_table_swap,"<tr></tr>"); // else create the swap string in the swap table to exit the market else StringConcatenate(out_table_swap,out_table_swap,"<tr><td align='right'>",DoubleToString(current_swap,2),"</td></tr>"); // get the profit of current deal current_profit=HistoryDealGetDouble(ticket,DEAL_PROFIT); // if profit is negative (loss) - it is displayed as red in the profit table to exit the market if(NormalizeDouble(current_profit,2)<0) StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'> <SPAN style='COLOR: #EF0000'>",DoubleToString(current_profit,2),"</SPAN></td></tr>"); // else - it is displayed as green else StringConcatenate(out_table_profit,out_table_profit,"<tr><td align='right'><SPAN style='COLOR: #00EF00'>", DoubleToString(current_profit,2),"</SPAN></td></tr>"); } else // if position is opened for sell - this will be the enter to the market { // if this deal has been already partially processed (in/out) if(deal_status[j]==1) { // create the volume table of entering the market (volume, formed after in/out, is put here) StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(-lots_list[symb_pointer],2),"</td></tr>"); // indemnity of volume change, which will be produced (the volume of this deal is already taken into account) lots_list[symb_pointer]+=lot_current; } // if this deal has not been processed yet, create the volume table to enter the market else StringConcatenate(in_table_volume,in_table_volume,"<tr><td align='right'>",DoubleToString(lot_current,2),"</td></tr>"); // creating the time table of entering the market StringConcatenate(in_table_time,in_table_time,"<tr><td align='center'>",TimeToString(time_curr,TIME_DATE|TIME_SECONDS),"</td></tr>"); // creating the price table of entering the market StringConcatenate(in_table_price,in_table_price,"<tr><td align='center'>",DoubleToString(HistoryDealGetDouble(ticket,DEAL_PRICE), (int)SymbolInfoInteger(symb_list[symb_pointer],SYMBOL_DIGITS)),"</td></tr>"); // mark deal as processed deal_status[j]=127; } // change of position volume by the current instrument, taking into account the volume of current deal lots_list[symb_pointer]-=lot_current; // if the volume of opened position by the current instrument became equal to zero - position is closed if(NormalizeDouble(lots_list[symb_pointer],2)==0 || deal_status[j]==1) break; } } } }
Si el momento en el que se abrió una posición se encuentra dentro del período del informe (al menos parcialmente), la entrada correspondiente es una salida al archivo "report.html".
// if the position period is in the the report period - the position is printed to report if(position_EndTime>=StartTime && position_StartTime<=EndTime) FileWrite(file_handle, in_table_volume,"</table></td>", in_table_time,"</table></td>", in_table_price,"</table></td>", out_table_volume,"</table></td>", out_table_time,"</table></td>", out_table_price,"</table></td>", out_table_swap,"</table></td>", out_table_profit,"</table></td></tr>");
Asigne la variable balance_prev con el valor de saldo. Salga del bucle con la variable i.
}
// changing balance
balance_prev=balance;
}
Escriba el final del archivo HTML (enlaces a imágenes, el final del alineamiento central, el final de la parte principal, el final del documento HTML). Cierre el archivo "report.html".
// create the end of html-file FileWrite(file_handle,"</table><br><br><h2>Balance Chart</h2><img src='picture1.gif'><br><br><br><h2>Price Chart</h2><img src='picture2.gif'></center></body></html>"); // close file FileClose(file_handle);
No espere a la actualización del gráfico más del tiempo especificado en la constante de frecuencia.
// get current time time_curr=TimeCurrent(); // waiting for chart update while(SeriesInfoInteger(Symbol(),Picture1_period,SERIES_BARS_COUNT)==0 && TimeCurrent()-time_curr<timeout) Sleep(1000);
Configure el máximo y mínimo en el gráfico.
// setting maximal and minimal values for the balance chart (10% indent from upper and lower boundaries) ChartSetDouble(hChart,CHART_FIXED_MAX,max_val+(max_val-min_val)/10); ChartSetDouble(hChart,CHART_FIXED_MIN,min_val-(max_val-min_val)/10);
Configure las propiedades de un gráfico de saldo.
// setting properties of the balance chart ChartSetInteger(hChart,CHART_MODE,CHART_LINE); // chart as line ChartSetInteger(hChart,CHART_FOREGROUND,false); // chart on foreground ChartSetInteger(hChart,CHART_SHOW_BID_LINE,false); // hide BID line ChartSetInteger(hChart,CHART_COLOR_VOLUME,White); // volumes and orders levels are white ChartSetInteger(hChart,CHART_COLOR_STOP_LEVEL,White); // SL and TP levels are white ChartSetInteger(hChart,CHART_SHOW_GRID,true); // show grid ChartSetInteger(hChart,CHART_COLOR_GRID,LightGray); // grid is light-gray ChartSetInteger(hChart,CHART_SHOW_PERIOD_SEP,false); // hide period separators ChartSetInteger(hChart,CHART_SHOW_VOLUMES,CHART_VOLUME_HIDE); // hide volumes ChartSetInteger(hChart,CHART_COLOR_CHART_LINE,White); // chart is white ChartSetInteger(hChart,CHART_SCALE,0); // minimal scale ChartSetInteger(hChart,CHART_SCALEFIX,true); // fixed scale on vertical axis ChartSetInteger(hChart,CHART_SHIFT,false); // no chart shift ChartSetInteger(hChart,CHART_AUTOSCROLL,true); // autoscroll enabled ChartSetString(hChart,CHART_COMMENT,"BALANCE"); // comment on chart
Redibuje el gráfico de saldo.
// redraw the balance chart ChartRedraw(hChart); Sleep(8000);
Haga una captura de pantalla del gráfico (guarde la imagen "picture1.gif"). La anchura del gráfico se ajusta a la anchura del período del informe (aunque, a causa de las fiestas, se dan a menudo errores, y el gráfico se hace más ancho que la curva de cambio de saldo), y la altura se calcula como la mitad de la anchura.
// screen shooting the balance chart ChartScreenShot(hChart,"picture1.gif",(int)(EndTime-StartTime)/PeriodSeconds(Picture1_period), (int)(EndTime-StartTime)/PeriodSeconds(Picture1_period)/2,ALIGN_RIGHT);
Elimine todos los objetos del gráfico y cerrarlo.
// delete all objects from the balance chart ObjectsDeleteAll(hChart); // close chart ChartClose(hChart);
Si se permite el envío de archivos por FTP, enviar tres archivos: "report.html", picture1.gif " y " picture2.gif ".
// if report publication is enabled - send via FTP // HTML-file and two images - price chart and balance chart if(TerminalInfoInteger(TERMINAL_FTP_ENABLED)) { SendFTP("report.html"); SendFTP("picture1.gif"); SendFTP("picture2.gif"); } }
Por ahora, la descripción del programa está completa. Para enviar archivos por FTP, deberá ajustar su configuración de MetaTrader 5: vaya al menú Tools (Herramientas), después Options (Opciones) y después abra la pestaña Publisher (Publicador) (Figura 4).
Figura 4. Opciones para publicar un informe por FTP.
En el cuadro de diálogo deberá asegurarse de activar la opción "Enable" ("Permitir"), especificar el número de cuenta, dirección FTP, ruta nombre de usuario y contraseña de acceso. La periodicidad de actualización no tiene importancia.
Ahora puede ejecutar el script. Tras la ejecución, aparece en pantalla el gráfico de saldo durante unos segundos, y después desaparece. En el Journal (Diario) puede encontrar posibles errores y ver si los archivos se enviaron por FTP. Si todo funciona bien, tres nuevos archivos aparecerán en el servidor, en la carpeta especificada. Si coloca allí dos archivos de imagen en forma de flecha, y si el servidor WWW está configurado y funciona correctamente, puede abrir un informe en su navegador web.
2. Enviar notificaciones como mensaje SMS a un teléfono móvil
Hay ocasiones en las que se encuentra lejos de su ordenador y otros aparatos electrónicos, y solo tiene su teléfono móvil a mano. Pero usted siempre querrá tener control sobre las operaciones de trading en su cuenta o monitorizar las cuotas para el instrumento financiero. En este caso, puede configurar el envío de notificaciones por mensaje SMS a un teléfono móvil. Muchas compañías telefónicas ofrecen un servicio de EMAIL-SMS que le permitirá recibir mensajes como cartas enviadas a una dirección de correo electrónico especificada.
Para ello, debe tener un buzón de correo electrónico (particularmente, debe conocer su servidor SMTP). Ajuste su configuración de MetaTrader 5: vaya al menú Tools (Herramientas), después Options (Opciones) y abra la pestaña Email (Correo Electrónico) (Figura 5).
Figura 5. Configurar el envío de notificaciones por correo electrónico
Active la opción "Enable" ("Permitir"), especifique una dirección de servidor SMTP, nombre de usuario y contraseña, dirección del remitente (su correo electrónico) y la del recipiente, es decir, la dirección de correo electrónico usada para enviar mensajes como SMS (compruebe el servicio con su compañía telefónica). Si todo está correcto, entonces al hacer click en el botón "Test" se enviará un mensaje de verificación (vea información adicional en el Diario).
La forma más fácil de recibir una notificación cuando el precio llega a un nivel determinado es crear una alerta. Para hacer esto, abra la pestaña Toolbox (Caja de Herramientas) correspondiente y seleccione Create (Crear) (Figura 6).
Figura 6. Crear una alerta
En esta ventana, active la opción "Enable" ("Permitir"), seleccione la acción "Mail", seleccione el instrumento financiero, introduzca el valor para la condición y escriba el texto del mensaje. En "Maximum iterations" ("Máximo de iteraciones"), introduzca el valor 1 si no quiere que el mensaje aparezca repetidamente. Tras completar todos los campos, haga click en OK.
Si enviamos un mensaje desde un programa MQL5, tendremos más posibilidades. Usaremos la función SendMail(). Tiene dos parámetros. Primero, el título; segundo, el cuerpo del mensaje.
Puede llamar a la función SendMail() después de la solicitud de trading (función OrderSend()), o en el controlador de eventos Trade. Así tendremos notificaciones de eventos de trading: entrada al mercado, establecimiento de órdenes, cierre de posiciones. O puede colocar la función SendMail() dentro de OnTimer(): recibiremos notificaciones periódicas sobre las cuotas actuales. Puede configurar el envío de notificaciones cuando aparecen determinadas señales de trading: cuando las líneas del indicador se cruzan, cuando el precio alcanza algunas líneas y niveles, etc.
Pongamos algunos ejemplos.
Si en un Asesor Experto o en un script sustituye
OrderSend(request,result};
con lo siguiente
string msg_subj,msg_text; if(OrderSend(request,result)) { switch(request.action) { case TRADE_ACTION_DEAL: switch(request.type) { case ORDER_TYPE_BUY: StringConcatenate(msg_text,"Buy ",result.volume," ",request.symbol," at price ",result.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL: StringConcatenate(msg_text,"Sell ",result.volume," ",request.symbol," at price ",result.price,", SL=",request.sl,", TP=",request.tp); break; } break; case TRADE_ACTION_PENDING: switch(request.type) { case ORDER_TYPE_BUY_LIMIT: StringConcatenate(msg_text,"Set BuyLimit ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL_LIMIT: StringConcatenate(msg_text,"Set SellLimit ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_BUY_STOP: StringConcatenate(msg_text,"Set BuyStop ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL_STOP: StringConcatenate(msg_text,"Set SellStop ",result.volume," ",request.symbol," at price ",request.price,", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_BUY_STOP_LIMIT: StringConcatenate(msg_text,"Set BuyStopLimit ",result.volume," ",request.symbol," at price ",request.price,", stoplimit=",request.stoplimit, ", SL=",request.sl,", TP=",request.tp); break; case ORDER_TYPE_SELL_STOP_LIMIT: StringConcatenate(msg_text,"Set SellStop ",result.volume," ",request.symbol," at price ",request.price,", stoplimit=",request.stoplimit, ", SL=",request.sl,", TP=",request.tp); break; } break; case TRADE_ACTION_SLTP: StringConcatenate(msg_text,"Modify SL&TP. SL=",request.sl,", TP=",request.tp); break; case TRADE_ACTION_MODIFY: StringConcatenate(msg_text,"Modify Order",result.price,", SL=",request.sl,", TP=",request.tp); break; case TRADE_ACTION_REMOVE: msg_text="Delete Order"; break; } } else msg_text="Error!"; StringConcatenate(msg_subj,AccountInfoInteger(ACCOUNT_LOGIN),"-",AccountInfoString(ACCOUNT_COMPANY)); SendMail(msg_subj,msg_text);
entonces, tras la solicitud de trading, la función OrderSend() enviará un mensaje usando la función SendMail(). Incluirá información sobre el número de cuenta de trading, el nombre de un broker y las acciones llevadas a cabo (compra, venta, realización de orden pendiente, modificación o eliminación de orden), tal y como se muestra a continuación:
59181-MetaQuotes Software Corp. Compra de 0.1 EURUSD a un precio d e1.23809, SL=1.2345, TP=1.2415
Y si en cualquier Asesor Experto o Indicador dentro del cuerpo de la función OnInit() comienza el temporizador usando la función EventSetTimer() (solo tiene un parámetro, el período del temporizador en segundos):
void OnInit() { EventSetTimer(3600); }
en la función OnDeinit() no se olvide de detenerlo usando EventKillTimer():
void OnDeinit(const int reason) { EventKillTimer(); }
y en OnTimer() para mandar mensajes usando la función SendMail():
void OnTimer() { SendMail(Symbol(),DoubleToString(SymbolInfoDouble(Symbol(),SYMBOL_BID),_Digits)); }
entonces recibirá mensajes sobre el precio del instrumento financiero actual en el período especificado.
Conclusión
Este artículo describe cómo usar el programa MQL5 para crear un archivo HTML y otro de imagen, y cómo subirlos a un servidor WWW por FTP. También describe cómo configurar el envío de notificaciones a su teléfono móvil como SMS.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/61
- 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