
СМС-извещения о состоянии Советника
Введение
Работа частенько отвлекает меня от того, чтобы быть рядом и наблюдать за Терминалом. Иногда это - 20-30 минут, иногда - сутки. Поэтому я решил организовать себе систему СМС-извещений о критических ситуациях, будь-то отключение света или если компьютер не смог "проснуться" после выходных. Уверен, что даже в том варианте готовности, в котором система представлена, она сможет помочь многим трейдерам, а те, кому её как таковой мало, смогут на базе этой идеи сформировать свой "шедевр".
В статье собираюсь продемонстрировать разработку рабочего решения поставленной задачи. Должен сделать оговорку: в Java разработке я не являюсь даже полупрофесионалом, настройку и вытягивание моего "вылетающего" кода мне помогал делать мой товарищ java-девелопер.
План
- Возможности Google Calendar
- Немного об установке Google Data API
- Блок-схема работы системы
- Код Java приложения
- Код .bat файла
- Код в Советнике
- Слабые стороны
- Заключение
Возможности Google Calendar
С целью создания СМС-извещений был выбран именно этот сервис, так как он позволяет создавать события в своем бесплатном Органайзере, а способом извещения о событии может служить СМС. Для целей системы я решил зарегистрировать отдельный, специальный, профиль на Google.
Если вкратце объяснить принцип работы, то выйдет так: если до 9:59 (по скриншоту) ничто не удалит данное событие, то есть если Терминал не выйдет на связь с Сервером Google и не заменит это событие на следующее, то на телефон отправится СМС с "тревогой".
Немного об установке Google Data API
Для программной работы со своими сервисами Google предоставляет документацию API данных Google.
Документация Java-разработчику по работе с Календарем находится по следующей ссылке Google Calendar APIs and Tools.
Для программирования вам потребуется установить Google Data Java Client Library. На этой же страничке есть ссылка на документацию о настройке библиотеки для Eclipse, сам использовал именно этот способ, поэтому и вам рекомендую Using Eclipse with Google Data APIs.
Блок-схема работы системы
Считаю нужным сперва немного прокомментировать блок-схему:
- событие с заголовком StopTXT нужно, чтобы удаленно вручную можно было прекратить поток СМС на всякий случай;
- событие с заголовком ReanimationTXT нужно для извещения о том, что по какой-то причине Терминал смог выйти на связь, а вам уже была отправлена СМС с "тревогой";
Код Java-приложения
Все исходники и дистрибутив, которыми я пользуюсь, есть в конце статьи.
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 { //--- ВНЕШНИЕ ПЕРЕМЕННЫЕ ---------------------------- String alertTxt = args[2]; String reanimationTxt = args[3]; String stopSMS = args[4]; int timePeriod = Integer.parseInt(args[5]); // период в минутах, с которым терминал // будет выходить на связь int mode = Integer.parseInt(args[6]); int startHour = Integer.parseInt(args[7]); int startMin = Integer.parseInt(args[8]); //--------------------------------------------------- //--- ВНУТРЕНИЕ ПЕРЕМЕННЫЕ -------------------------- URL feedUrl = new URL("http://www.google.com/calendar/feeds/default/private/full"); //ссылка для работы с событиями URL calendarUrl = new URL("http://www.google.com/calendar/feeds/default/allcalendars/full"); //ссылка для работы с календарями //--------------------------------------------------- System.out.println(">>---------- Start ----------<<"); // Создаем новый Calendar service, коннект к Google CalendarService myService = new CalendarService("My Application"); myService.setUserCredentials(args[0], args[1]); // ЛОГИН И ПАРОЛЬ В КАЧЕСТВЕ АРГУМЕНТОВ // ОБЪЯВЛЕНИЕ ПЕРЕМЕННЫХ ДЛЯ ПОИСКА СОБЫТИЯ 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; // ГОТОВИМ ПОИСКОВЫЙ ЗАПРОС myQuery.setFullTextQuery(stopSMS); // СОБЫТИЕ С ТЕКСТОМ stopSMS myResultsFeed = myService.query(myQuery, CalendarEventFeed.class); if (myResultsFeed.getEntries().size() > 0) { System.out.println(">> STOP event found. \n>> Finish"); return; } // ГОТОВИМ ПОИСКОВЫЙ ЗАПРОС myQuery.setFullTextQuery(reanimationTxt); // СОБЫТИЕ С ТЕКСТОМ reanimationTxt myResultsFeed = myService.query(myQuery, CalendarEventFeed.class); if (myResultsFeed.getEntries().size() > 0) { // ЕСЛИ УДАЛОСЬ НАЙТИ System.out.println(">> REANIMATION event found"); // У ПЕРВОГО ПОПАВШЕГОСЯ СОБЫТИЯ СПРАШИВАЕМ ЗАГОЛОВОК firstMatchEntry = (CalendarEventEntry) myResultsFeed.getEntries().get(0); myEntryTitle = firstMatchEntry.getTitle().getPlainText(); // ЕСЛИ НАШЛИ СОБЫТИЕ reanimationTxt ТО УДАЛЯЕМ ЕГО deleteUrl = new URL(firstMatchEntry.getEditLink().getHref()); myService.getRequestFactory().setHeader("If-Match", "*"); myService.delete(deleteUrl); System.out.println(">> ...deleting REANIMATION event"); } // ГОТОВИМ ПОИСКОВЫЙ ЗАПРОС myQuery.setFullTextQuery(alertTxt); // СОБЫТИЕ С ТЕКСТОМ alertTxt myResultsFeed = myService.query(myQuery, CalendarEventFeed.class); if (myResultsFeed.getEntries().size() > 0) { // ЕСЛИ УДАЛОСЬ НАЙТИ System.out.println(">> >> ALERT event found"); // У ПЕРВОГО ПОПАВШЕГОСЯ СОБЫТИЯ СПРАШИВАЕМ ЗАГОЛОВОК 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)) { // ЕСЛИ У СТАРОГО СОБЫТИЯ ВРЕМЯ НАЧАЛА МЕНЬШЕ ЧЕМ timePeriod МИНУТ НАЗАД // УДАЛЯЕМ ЕГО 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 { // ВРЕМЯ БОЛЬШЕ ЧЕМ timePeriod МИНУТ НАЗАД, А ЗНАЧИТ смс УЖЕ УШЛА // УДАЛЯЕМ ЕГО 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(">> >> #### ### # ### #### "); // СОЗДАЕМ НОВОЕ СОБЫТИЕ myEntry = new CalendarEventEntry(); myEntry.setTitle(new PlainTextConstruct(reanimationTxt)); // ЗАДАЕМ ЕГО ЗАГОЛОВОК newDate.setTime(newDate.getTime() + 1000 * 60 * 1); // ТЕКУЩЕЕ ВРЕМЯ + 1 МИНУТa startTime = DateTime.parseDateTime(formater.format(newDate)); // ВСТАВЛЯЕТ СТРОКУ ПО ШАБЛОНУ "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); // ПРИБАВИМ ЕЩЕ РАЗ ТЕКУЩЕЕ ВРЕМЯ // + 1 МИНУТa 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; // ИЗВЕСТИТЬ ЗА 1 МИНУТУ reminder.setMinutes(reminderMinutes); reminder.setMethod(methodType); myEntry.getReminder().add(reminder); // СОЗДАЕМ НОВЫЙ Calendar service, КОННЕКТ К Google CalendarService tempService = new CalendarService("My Application"); tempService.setUserCredentials(args[0], args[1]); // ЛОГИН И ПАРОЛЬ В КАЧЕСТВЕ АРГУМЕНТОВ // СТАВИМ СОБЫТИЕ В ОЧЕРЕДЬ НА ИСПОЛЕНЕНИЕ tempService.insert(feedUrl, myEntry); } } // СОЗДАЕМ НОВЫЙ Calendar service, КОННЕКТ К Google CalendarService basicService = new CalendarService("basic Application"); basicService.setUserCredentials(args[0], args[1]); // ЛОГИН И ПАРОЛЬ В КАЧЕСТВЕ АРГУМЕНТОВ // СОЗДАЕМ НОВОЕ СОБЫТИЕ CalendarEventEntry basicEntry = new CalendarEventEntry(); basicEntry.setTitle(new PlainTextConstruct(alertTxt)); // ЗАДАЕМ ЕГО ЗАГОЛОВОК if (mode == 0) { // ОБЫЧНАЯ ДНЕВНАЯ СИТУАЦИЯ newDate.setTime(newDate.getTime() + 1000 * 60 * timePeriod); // ТЕКУЩЕЕ ВРЕМЯ + timePeriod МИНУТ startTime = DateTime.parseDateTime(formater.format(newDate)); // ВСТАВЛЯЕТ СТРОКУ ПО ШАБЛОНУ "2009-06-30T20:55:00" newDate.setTime(newDate.getTime() + 1000 * 60 * 0); // ПРИБАВИМ ЕЩЕ РАЗ ТЕКУЩЕЕ ВРЕМЯ // + timePeriod МИНУТ endTime = DateTime.parseDateTime(formater.format(newDate)); } if (mode == 1) { // СИТУАЦИЯ НАЗНАЧЕНИЯ СОБЫТИЯ НА СЛЕДУЮЩЕЕ УТРО, ИЛИ УТРО ПОНЕДЕЛЬНИКА Date curDate = new Date(); GregorianCalendar gDate = new GregorianCalendar(); gDate.setTime(curDate); // УСТАНАВЛИВАЕМ ЧАСЫ И МИНУТЫ В НЕОБХОДИМЫЕ 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) { // ПН - ЧТ gDate.set(GregorianCalendar.DAY_OF_MONTH, gDate.get(GregorianCalendar.DAY_OF_MONTH) + 1); } else { // ПТ 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("создаю событие УПАЛ!"); 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; // ИЗВЕСТИТЬ ЗА 1 МИНУТУ reminder.setMinutes(reminderMinutes); reminder.setMethod(methodType); basicEntry.getReminder().add(reminder); // СТАВИМ СОБЫТИЕ В ОЧЕРЕДЬ НА ИСПОЛЕНЕНИЕ 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(); } } }
Код .bat файла
Так как создание бат-файлов в MQL4 пока невозможно, то на практике я использую 2 бат-файла, которые отличаются Режимом(Mode).
@rem +------------------------------------------------------------------+ @rem | Прикладное приложение производит отправку СМСок | @rem | ------------------------------------------------------ | @rem | Правила: | @rem | | @rem | USER - учетная запись отправителя | @rem | PASS - пароль отправителя | @rem | ALER - Alert, тестовка предупредительного сообщения | @rem | (использовать только латиницу) | @rem | REAN - Reanimated, текст сообщения о восстановлении связи | @rem | (использовать только латиницу) | @rem | STOP - STOP, заголовок события, которое запрещает выполнение | @rem | действий программы | @rem | (использовать только латиницу) | @rem | PERI - Period, период-таймер на отправку сообщения Alert | @rem | MODE - Режим | @rem | (0 - обычная работа, | @rem | 1 - отправка сообщения на завтрашнее утро или | @rem | утро понедельника) | @rem | HOUR - Час "завтрашнего утра, или утра понедельника", на | @rem | который необходимо поставить сообщение. | @rem | MINU - Minutes, минуты "завтрашнего утра..." | @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%
Код в советнике
По правилам вашей Торговой Системы в блоке "действий по расписанию" вам понадобится вызывать функции, код которых приведен ниже.
Ну а в конце торговой сессии я вызываю функцию для установки СМС на следующее утро.
В ссылках используются именно ярлыки .lnk, чтобы можно было назначить режим вызова окошка консоли "Свернутое в значок", дабы не пугать своим выскакиванием.
//+------------------------------------------------------------------+ //| Отправляет статусное SMS | //+------------------------------------------------------------------+ int sendSMS() { string destination = StringConcatenate(TerminalPath(),"\TerminalWatch\launch.lnk"); ShellExecuteA(WindowHandle(Symbol(),0),"open", destination, NULL, NULL,1); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Отправляет статусное SMS на следующее утро | //+------------------------------------------------------------------+ int sendSMSnextDate() { string destination = StringConcatenate(TerminalPath(),"\TerminalWatch\launch_next_date.lnk"); ShellExecuteA(WindowHandle(Symbol(),0),"open", destination, NULL, NULL,1); } //+------------------------------------------------------------------+
Слабые стороны
Небольшая ложка дегтя - на практике очень трудно подстроится под синхронную работу часов на Терминале, на Сервер Котировок и на Сервере Google, поэтому в коде советника использую такую методику: каждые 5 минут устанавливаю смс с таймером через 8 минут. Такая тактика в 95% случаев работает хорошо, но пока я все еще сталкиваюсь с оставшимися 5% "дегтя".
Заключение
Всем, кому интересно, ну и кто не поленится заиметь себе аккаунт на Google, будет достаточно просто проверить, на что способна моя нехитрая система.
Повторюсь, многие детали Java-разработки мне не до конца понятны. Я использовал помощь моих товарищей девелоперов (Дима, большое спасибо!), так что не на все вопросы смогу ответить полноценно.
Большое спасибо за внимание!
Добавлено по результатом обсуждения статьи
В текущей версии учтено полезное замечание komposter по логике работы:
- не удалять реанимационное сообщение, если оно не отправилось;
К статье прикреплен файл: TerminalWatch_03.rar





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Ладно, попробую реализовать, но сроков не буду говорить :-Р.
Если это - только для меня, не стоит этим заниматься.
Я не буду использовать этот способ контроля за соединением, причины описывал ниже.
И все равно спасибо )
Сильно не вникал, но смотрю название статьи и сколько в ней кода, и начинает казаться что я решил такую же проблему с меньшей головной болью.
есть отличная программка SimpleSMSLight, которая запускается из командной строки (см. в гугле)
таким образом чтобы отправить смс, достаточно купить любой БУшный телефон со встроенным модемом, купить USB адаптер к нему с функцией подзарядки, пакет с симкартой с опцией "бесплатные смс" или что-нибудь просто подешевле
у меня работает:
- указанная программа simpleSMSLight
- к ПК подключен телефон CX70 (куплен за 170грн)
- кабель подключения USB - за 20грн
- симкарта 10 грн
- каждая смс стоит несколько копеек.
- три строчки кода в советнике или индикаторе
я туда даже не заглядываю, телефон слоем пыли покрылся за системным блоком, работает без сбоев.
дополнительная удобная фича - с этим же телефоном параллельно работает VentaFax (см. гугле) и иногда в отдельных случаях звонит мне на мой мобильный.
Ventafax так же можно запускать и отключать (чтобы она освобождала порт после звонка) из командной строки.
Небольшая работа с кодом, и советник настраивается чтобы в ночное время звонил и слалс смски только в особо важных случаях (пропадание связи с инетом, пропадание питания, открытие позиций и т.п.).
Добрый вам день.
Ситуация ясна - каждому свое.
Спасибо, что осветили свой метод тут, может кому-то он окажется простым и удобным.
У меня ситуация другая: я арендую виртуальный выделенный сервер за 190 грн./месяц, который работает круглые сутки в 18 км. от точки, где я работаю, и 3 км., где я живу, а основная работа - "летать по встречам по Киеву".... поэтому что толку в смс-ке, если вам негде развернуть бекап терминала или законектится и разгребать ситуацию.
dimonsky писал(а):
Сильно не вникал, но смотрю название статьи и сколько в ней кода, и начинает казаться что я решил такую же проблему с меньшей головной болью.
Добрый вам день.
Ситуация ясна - каждому свое.
Спасибо, что осветили свой метод тут, может кому-то он окажется простым и удобным.
У меня ситуация другая: я арендую виртуальный выделенный сервер за 190 грн./месяц, который работает круглые сутки в 18 км. от точки, где я работаю, и 3 км., где я живу, а основная работа - "летать по встречам по Киеву".... поэтому что толку в смс-ке, если вам негде развернуть бекап терминала или законектится и разгребать ситуацию.
dimonsky писал(а):
Сильно не вникал, но смотрю название статьи и сколько в ней кода, и начинает казаться что я решил такую же проблему с меньшей головной болью.
Чтобы законнектиться к терминалу(запущенному круглосуточно дома на другом конце города) со своего рабочего ПК я уже год использую TeamViewer.
Прошу прощения у автора, надеюсь я никаких правил не нарушаю, да и пригодиться может.
Ко мне обращаются с просьбой разъяснить как я реализовал отправку смс, поэтому я решил кратко описать мой метод здесь. Заодно и тему апнем.
итак, берем в архиве прогарммку simplesmslite. я исполняемый файл переименовал в com, чтобы можно было создать файл настроек окна программы PIF (В котором прописал не всплывать поверх всех и т.п.)
1) в корень диска С кидаете файл "sendsms.bat" c текстом внутри:
где c:\Program Files\!Forex\Broco Trader 2\ - путь к вашему торговому терминалу.
2) В корень диска С кидаете фалы из архива.
В SimpleSMSlite.Ini пишете эти строки:
при этом стока Comport=COM3 указывает, на каком порту висит ваш мобильный телефон (в качестве модема).
Чтобы узнать, идете "Пуск/Панель управления/ Система/ влкадка Оборудование/ кнопка Диспетчер устройств" и там смотрите ветка "Модемы". В ней должен висеть ваш мобильный телефон (у меня - как "Siemens Mobile Phone USB Modem"). Щелкаете по нему два раза, появляется окно. в нем - на вкладку "Модем" и в самом верху будет написан порт. У меня - COM3.
К слову, если такой группы "Модемы" нет, или в ней в открывающемся списке нет вашего телефона, значит он не установлен как модем. Или драйвера не стоят, или он вообще не подключен к ПК. Способ подключения к ПК зависит от модели телефона. У меня телефон сименс CX70, соответственно подключен он кабелем 510 (гугле в помощь, а также барахолки на рынке цифровых устройств, еще можно заказать в инете). Конечно, телефон по техописанию должен быть с модемом.
Так, идем далее.
в советнике или индикаторе пишем такой код:
функция отправки
конечно, вместо 38050ХХХХХХХ пишете номер телефона, накоторый надо отправить сообщение.
Вроде все....
dimonsky писал(а):............................
конечно, вместо 38050ХХХХХХХ пишете номер телефона, накоторый надо отправить сообщение.
Вроде все....