Notificaciones por SMS sobre el estado del Asesor Experto

Дмитрий | 11 mayo, 2016

Introducción

Mi trabajo interfiere a menudo con mi capacidad de hacer un seguimiento continuo del terminal. A veces son sólo 20 o 30 minutos y a veces puede ser un día entero. Así que decidí desarrollar un sistema que me permita recibir notificaciones por SMS acerca de cualquier situación crítica, que sea un apagón o que mi ordenador no arranca después de haber estado en el modo de suspensión durante el fin de semana. Estoy seguro de que este sistema tal y como se presenta en este artículo será de gran ayuda para muchos traders o servirá como base para los que consideran que no es suficiente tal y como está y quieren crear su propia "obra maestra".

En este artículo voy a presentar el desarrollo de una solución operativa para este propósito. Quiero mencionar que no tengo conocimientos en la programación Java, así que he pedido ayuda a un colega que es desarrollador Java para depurar y corregir los fallos de mi código.


Índice

  1. Características de Google Calendar
  2. Algunos comentarios acerca de la instalación de Google Data APIs
  3. El diagrama de bloques del sistema
  4. El código Java de la aplicación
  5. El código del archivo .bat
  6. El código del Asesor Experto
  7. Deficiencias
  8. Conclusión


Características de Google Calendar

Para el desarrollo del sistema de notificaciones por SMS he optado por Google Calendar ya que permite crear eventos en su calendario gratuito además de permitir usar los SMS para notificar cualquiera de estos eventos. He creado una cuenta en Google especialmente para este propósito.

En pocas palabras, el principio de funcionamiento es el siguiente: si no se elimina el mensaje actual sobre las 9:59 a.m. (ver la captura de pantalla anterior), es decir, si no se conecta el terminal con el servidor de Google para sustituir este evento por el siguiente, se enviará un SMS a su teléfono con la notificación correspondiente.


Algunos comentarios acerca de la instalación de Google Data APIs

Google proporciona la documentación Google Data APIs para que se puedan utilizar los servicios de Google en el desarrollo de los programas.
Toda la documentación que pueda necesitar un desarrollador Java para trabajar con Google Calendar está disponible en este enlace Google Calendar APIs and Tools.

Para comenzar con la programación tenemos que instalar Google Data Java Client Library. Podemos encontrar un enlace a la documentación relacionada con la librería para Eclipse en la misma página. Esto es lo que he utilizado y lo que recomiendo: Using Eclipse with Google Data APIs.


El diagrama de bloques del sistema

Creo que lo primero es comentar el diagrama de bloques:

  • el evento StopTXT sirve para poder parar de forma manual y remota el envío de los SMS, por si acaso;
  • el evento ReanimationTXT sirve para informar que por alguna razón se conectó el terminal después de habernos enviado el SMS correspondiente con un aviso;


El código Java de la aplicación

Todos los códigos fuente y la distribución que he utilizado están adjuntos al final del artículo.

package Calendar;
/* INSTRUCTION: This is a command line application. 
    So please execute this template with the following arguments:

                arg[0] = username
                arg[1] = password
*/

import com.google.gdata.client.GoogleService;
import com.google.gdata.client.Query;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.PlainTextConstruct;
import com.google.gdata.data.calendar.CalendarEntry;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.calendar.CalendarEventFeed;
import com.google.gdata.data.calendar.CalendarFeed;
import com.google.gdata.data.extensions.Reminder;
import com.google.gdata.data.extensions.When;
import com.google.gdata.data.extensions.Reminder.Method;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

public class Calendar {

    public static void main(String[] args) {

        try {
            //--- EXTERNAL VARIABLES ----------------------------
            String alertTxt = args[2];
            String reanimationTxt = args[3];
            String stopSMS = args[4];
            int timePeriod = Integer.parseInt(args[5]); // interval in minutes at which the terminal 
                                                        // will get connected
            int mode = Integer.parseInt(args[6]);
            int startHour = Integer.parseInt(args[7]);
            int startMin = Integer.parseInt(args[8]);
            //---------------------------------------------------
            //--- INTERNAL VARIABLES --------------------------
            URL feedUrl = new URL("http://www.google.com/calendar/feeds/default/private/full");
            //link for working with events
            URL calendarUrl = new URL("http://www.google.com/calendar/feeds/default/allcalendars/full");
            //link for working with calendars   
            //---------------------------------------------------
            System.out.println(">>----------  Start   ----------<<");

            // Create a new Calendar service, connect to Google
            CalendarService myService = new CalendarService("My Application");
            myService.setUserCredentials(args[0], args[1]);    // LOGIN AND PASSWORD AS ARGUMENTS

            // DECLARE VARIABLES FOR EVENT SEARCH
            Query myQuery = new Query(feedUrl);
            CalendarEventFeed myResultsFeed;
            CalendarEventEntry firstMatchEntry = null;
            String myEntryTitle;
            URL deleteUrl;
            When timeVar;
            SimpleDateFormat formater = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
            Date newDate = new Date();
            CalendarEventEntry myEntry;
            When eventTimes = new When();
            DateTime startTime = null;
            DateTime endTime = null;
            int reminderMinutes;
            Method methodType = Method.SMS;
            Reminder reminder = new Reminder();
            CalendarEventEntry insertedEntry;

            // PREPARE SEARCH QUERY
            myQuery.setFullTextQuery(stopSMS);            // stopSMS TEXT EVENT
            myResultsFeed = myService.query(myQuery, CalendarEventFeed.class);
            if (myResultsFeed.getEntries().size() > 0) {
                System.out.println(">> STOP event found. \n>> Finish");
                return;
            }

            // PREPARE SEARCH QUERY 
            myQuery.setFullTextQuery(reanimationTxt);    // reanimationTxt TEXT EVENT
            myResultsFeed = myService.query(myQuery, CalendarEventFeed.class);
            if (myResultsFeed.getEntries().size() > 0) {    // IF FOUND
                System.out.println(">> REANIMATION event found");
                // GET TITLE OF THE FIRST MATCH ENTRY
                firstMatchEntry = (CalendarEventEntry) myResultsFeed.getEntries().get(0);
                myEntryTitle = firstMatchEntry.getTitle().getPlainText();

                // IF THE reanimationTxt EVENT FOUND, DELETE IT
                deleteUrl = new URL(firstMatchEntry.getEditLink().getHref());
                myService.getRequestFactory().setHeader("If-Match", "*");
                myService.delete(deleteUrl);
                System.out.println(">> ...deleting REANIMATION event");
            }

            // PREPARE SEARCH QUERY
            myQuery.setFullTextQuery(alertTxt);            // alertTxt TEXT EVENT
            myResultsFeed = myService.query(myQuery, CalendarEventFeed.class);
            if (myResultsFeed.getEntries().size() > 0) {    // IF FOUND
                System.out.println(">>  >> ALERT event found");
                // GET TITLE OF THE FIRST MATCH ENTRY
                firstMatchEntry = (CalendarEventEntry) myResultsFeed.getEntries().get(0);
                myEntryTitle = firstMatchEntry.getTitle().getPlainText();

                timeVar = firstMatchEntry.getTimes().get(0);
                System.out.println(">>  >> event start&stop time         : " 
                        + timeVar.getStartTime() + "\n>>        >>                               : " 
                        + timeVar.getEndTime());
                System.out.println(">>  >> event start     (milliseconds): " 
                        + timeVar.getStartTime().getValue());
                System.out.println(">>  >> new event start (milliseconds): " 
                        + (newDate.getTime() - (1000 * 60 * timePeriod)));

                if (timeVar.getStartTime().getValue() > newDate.getTime() - (1000 * 60 * 0)) {    
                    // IF THE START TIME OF THE OLD EVENT IS LESS THAN timePeriod MINUTES AGO
                    // DELETE IT
                    deleteUrl = new URL(firstMatchEntry.getEditLink().getHref());
                    myService.getRequestFactory().setHeader("If-Match", "*");
                    myService.delete(deleteUrl);
                    System.out.println(">>              >> event start > new event start");
                    System.out.println(">>              >> ...deleting event");
                    System.out.println(">>              >>    #M###      ##M    #M#  ");
                    System.out.println(">>              >>   ### ### ### "); System.out.println(">> >> ### ### ### ### "); System.out.println(">> >> ### ### ### ### "); System.out.println(">> >> M## ### #M "); System.out.println(">> >> ### ### #### ### "); System.out.println(">> >> ### ### ### ### "); System.out.println(">> >> ### ### #M# ### "); System.out.println(">> >> ### ### ### ### "); System.out.println(">> >> ### ### ### "); System.out.println(">> >> #### ### ### "); } else { // THE TIME IS MORE THAN timePeriod MINUTES AGO, WHICH MEANS THAT THE sms HAS ALREADY BEEN SENT // DELETE IT deleteUrl = new URL(firstMatchEntry.getEditLink().getHref()); myService.getRequestFactory().setHeader("If-Match", "*"); myService.delete(deleteUrl); System.out.println(">> >> SMS has gone already"); System.out.println(">> >> event start < new event start"); System.out.println(">> >> ...deleting event"); System.out.println(">> >> .M#### M##M ###M . "); System.out.println(">> >> ### #### #### ### "); System.out.println(">> >> ### ### #### #### #M# ### "); System.out.println(">> >> ### ###M#  ### "); System.out.println(">> >> #M####   # "); System.out.println(">> >> # ### # # @## #M#### "); System.out.println(">> >>  ### # # ###  "); System.out.println(">> >> #M# ### ### # # ### M## ### "); System.out.println(">> >> ### ### ### ### ### ### ### "); System.out.println(">> >> # ### ### ### # "); System.out.println(">> >> #### ### # ### #### "); // CREATE NEW EVENT myEntry = new CalendarEventEntry(); myEntry.setTitle(new PlainTextConstruct(reanimationTxt)); // SET ITS TITLE newDate.setTime(newDate.getTime() + 1000 * 60 * 1); // CURRENT TIME + 1 MINUTE startTime = DateTime.parseDateTime(formater.format(newDate)); // INSERTS THE LINE USING THE PATTERN "2009-06-30T20:55:00" System.out.println(">> >> creating REANIMATION event"); System.out.println(">> >> event starttime: " + formater.format(newDate)); newDate.setTime(newDate.getTime() + 1000 * 60 * 0); // AGAIN ADD THE CURRENT TIME  // + 1 MINUTE endTime = DateTime.parseDateTime(formater.format(newDate)); System.out.println(">> >> event stoptime : " + formater.format(newDate)); eventTimes.setStartTime(startTime); eventTimes.setEndTime(endTime); myEntry.addTime(eventTimes); reminderMinutes = 0; // REMIND 1 MINUTE BEFORE reminder.setMinutes(reminderMinutes); reminder.setMethod(methodType); myEntry.getReminder().add(reminder); // CREATE A NEW Calendar service, CONNECT TO Google CalendarService tempService = new CalendarService("My Application"); tempService.setUserCredentials(args[0], args[1]); // LOGIN AND PASSWORDS AS ARGUMENTS // PUT EVENT IN QUEUE tempService.insert(feedUrl, myEntry); } } // CREATE A NEW Calendar service, CONNECT TO Google CalendarService basicService = new CalendarService("basic Application"); basicService.setUserCredentials(args[0], args[1]); // LOGIN AND PASSWORD AS ARGUMENTS // CREATE NEW EVENT CalendarEventEntry basicEntry = new CalendarEventEntry(); basicEntry.setTitle(new PlainTextConstruct(alertTxt)); // SET ITS TITLE if (mode == 0) { // STANDARD DAILY SITUATION newDate.setTime(newDate.getTime() + 1000 * 60 * timePeriod); // CURRENT TIME + timePeriod MINUTES startTime = DateTime.parseDateTime(formater.format(newDate)); // INSERTS THE LINE USING THE PATTERN "2009-06-30T20:55:00" newDate.setTime(newDate.getTime() + 1000 * 60 * 0); // AGAIN ADD THE CURRENT TIME // + timePeriod MINUTES endTime = DateTime.parseDateTime(formater.format(newDate)); } if (mode == 1) { // SCHEDULE EVENT FOR THE NEXT MORNING OR MONDAY MORNING Date curDate = new Date(); GregorianCalendar gDate = new GregorianCalendar(); gDate.setTime(curDate); // SET THE REQUIRED HOUR AND MINUTE gDate.set(GregorianCalendar.HOUR_OF_DAY, startHour); gDate.set(GregorianCalendar.MINUTE, startMin); gDate.set(GregorianCalendar.SECOND, 0); if (gDate.get(GregorianCalendar.DAY_OF_WEEK) < 6) { // MO - THU gDate.set(GregorianCalendar.DAY_OF_MONTH, gDate.get(GregorianCalendar.DAY_OF_MONTH) + 1); } else { // MO gDate.set(GregorianCalendar.DAY_OF_MONTH, gDate.get(GregorianCalendar.DAY_OF_MONTH) + 3); } // System.out.println("gDate "+ gDate.getTime()); startTime = DateTime.parseDateTime(formater.format(gDate.getTime())); gDate.set(GregorianCalendar.MINUTE, gDate.get(GregorianCalendar.MINUTE) + timePeriod); endTime = DateTime.parseDateTime(formater.format(gDate.getTime())); System.out.println(">> nextday event should be created"); } //System.out.println("creating ALERT event!"); System.out.println(">> creating ALERT event"); System.out.println(">> event starttime: " + startTime.toString()); System.out.println(">> event stoptime : " + endTime.toString()); eventTimes.setStartTime(startTime); eventTimes.setEndTime(endTime); basicEntry.addTime(eventTimes); reminderMinutes = 0; // REMIND 1 MINUTE BEFORE reminder.setMinutes(reminderMinutes); reminder.setMethod(methodType); basicEntry.getReminder().add(reminder); // PUT EVENT IN QUEUE basicService.insert(feedUrl, basicEntry); System.out.println(">>---------- Finish ----------<<"); } catch (AuthenticationException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (ServiceException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }


El código del archivo .bat

Puesto que la creación de los archivos .bat en MQL4 es imposible en la actualidad, he utilizado 2 archivos .bat con distintos modos.

@rem +-----------------------------------------------------------------------------------------+
@rem |         The application sends SMS                                                       |
@rem |      ------------------------------------------------------                             |
@rem | Rules:                                                                                  |
@rem |                                                                                         |
@rem | USER   -   sender's account                                                             |
@rem | PASS   -   sender's password                                                            |
@rem | ALER   -   Alert, text of the alert message                                             |
@rem |              (only use Latin characters)                                                |
@rem | REAN   -   Reanimated, text of the message notifying about the reestablished connection |
@rem |                  (only use Latin characters)                                            |
@rem | STOP   -   STOP, title of the event that forbids execution                              |
@rem |                  of the program actions                                                 |
@rem |                  (only use Latin characters)                                            |
@rem | PERI   -   Period, period-timer for sending Alert message                               |
@rem | MODE   -   Mode                                                                         |
@rem |                  (0 - standard work,                                                    |
@rem |                   1 - scheduling the message to be sent tomorrow morning or             |
@rem |                        on Monday morning)                                               |
@rem | HOUR   -   Hour "of tomorrow morning or Monday morning" at                              |
@rem |                   which the message is required to be sent.                             |
@rem | MINU   -   Minutes, minutes "of tomorrow morning..."                                    |
@rem +-----------------------------------------------------------------------------------------+

@rem ECHO off

set USER=forex.myaccount
set PASS=mypassword
set ALER=Terminal_Alert
set REAN=Trading_Resolved
set STOP=STOP
set PERI=5
set MODE=0
set HOUR=8
set MINU=20

set ARGS= %USER% %PASS% %ALER% %REAN% %STOP% %PERI% %MODE% %HOUR% %MINU%
     
    java -jar "d:\Documents\forex\Deltabank Trader 4\TerminalWatch\terminalWatcher.jar" %ARGS% 

@rem ECHO %ARGS%


El código del Asesor Experto

Se proporciona a continuación el código de las funciones a las que hay que llamar en el bloque de las "acciones planificadas" de acuerdo con los criterios de su sistema de trading.
Al finalizar la sesión de trading llamo a la función que programa los SMS para la siguiente mañana.

Los accesos están marcados con la etiqueta .lnk para que podamos establecer el modo de llamada "minimizado a un icono" en la ventana de la consola para evitar sustos cuando aparezca.

//+------------------------------------------------------------------+
//| Sends a status SMS                                               |
//+------------------------------------------------------------------+
int sendSMS()
{
      string destination = StringConcatenate(TerminalPath(),"\TerminalWatch\launch.lnk");
      ShellExecuteA(WindowHandle(Symbol(),0),"open", destination, NULL, NULL,1);
}
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//| Sends a status SMS the next morning                              |
//+------------------------------------------------------------------+
int sendSMSnextDate()
{
      string destination = StringConcatenate(TerminalPath(),"\TerminalWatch\launch_next_date.lnk");
      ShellExecuteA(WindowHandle(Symbol(),0),"open", destination, NULL, NULL,1);
}
//+------------------------------------------------------------------+


Deficiencias

En la práctica, es muy difícil sincronizar la operación con el reloj del terminal, el servidor de cotizaciones y el servidor de Google. Así que he utilizado la siguiente técnica en el código del Asesor Experto: se ajusta el SMS cada 5 minutos con el temporizador de 8 minutos. Esto funciona muy bien en el 95 % de los casos pero todavía tengo que lidiar con el 5 % restante; es la única pega.


Conclusión

Si está interesado y no le da pereza crear un cuenta en Google, puede comprobar fácilmente de que es capaz mi sencillo sistema.
Un vez más, debo recordar que muchos detalles de la implementación Java en este artículo no son del todo claros para mí. He recibido mucha ayuda por parte de algunos colegas desarrolladores (¡muchas gracias a Dima!), así que no voy a poder responder a todas sus preguntas al detalle.
¡Gracias por su tiempo!


Cambios motivados por los comentarios recibidos acerca del artículo

La versión actual contiene una modificación muy útil gracias a komposter en relación a la lógica de funcionamiento:

  • no se elimina el mensaje de ReanimationTXT si no se ha enviado;

El archivo TerminalWatch_03.rar está adjunto al artículo.