Организация бесперебойной торговли в терминале МТ5
Михаил | 25 февраля, 2015 | Вложения (1)
В статье рассматривается организация бесперебойной торговли в случае отключения электричества или интернета, а так же удалённое управление процессом торговли.
Введение
Коль скоро Вы решили использовать в торговле экспертов и советников,
чтобы ограничить своё времяпровождение за компьютером, то, вероятно,
необходимо позаботится и о том, чтобы советники нормально работали и
в аварийных ситуациях, таких как отключение электроэнергии и интернета.
А также необходимо получить удалённый доступ к компьютеру
из любого места, где есть интернет.
Итак...
Электропитание
Всем известно об источниках бесперебойного питания (ИП).
Но как же правильно выбрать его?
Самым главным параметром для ИП является скорость
переключения из штатного режима в аварийный.
И очень желательно, чтобы ИП имел USB связь с Вашим компьютером,
тогда Вы сможете составлять сценарии работы и выключения компьютера
в аварийном режиме (отключение электричества).
Следует также уделить внимание блоку питания (БП) Вашего
компьютера. Мощность потребления складывается и мощности потребления всех
компонентов, входящих в состав ПК, плюс запас в 20-30%.
Не следует покупать БП в два-три раза превышающий суммарную
мощность потребления ПК.
Но не только мощностью потребления характеризуются БП,
но и качеством. Наивысшим качеством исполнения
обладают БП c сертификатом 80 Plus Platinum
Это совсем не значит, что Вы должны купить именно такой,
но лучше приобрести БП с сертификатом (они гораздо надёжнее).
Интернет соединения
Вот тут и начинаются проблемы, которые и побудили меня написать эту статью.
Казалось чего проще?
Взял двух независимых провайдеров, подключил провода к компьютеру (или к маршрутизатору ), расставил метрики адаптеров и...
Но не тут-то было!
А не переключаются автоматически адаптеры ни в Windows, ни в маршрутизаторах, если кабель физически подключен и есть напряжение
на общем маршрутизаторе провайдера в вашем доме. А самое главное, сам Терминал МТ5 должен сообщить нам о потере связи.
Важно отметить, что ADSL соединение не зависит от электричества, и если у Вас в городе есть провайдер, который
предоставляет услуги интернета по телефонной линии, то очень рекомендую воспользоваться им в качестве резервного канала интернета.
Если нет, то можно воспользоваться "воздушными" соединениями сотовых операторов (значительно дороже),
но ОБЯЗАТЕЛЬНО резервное соединение никак не должно быть связано с поставщиком основного
канала интернета.
Приведённый ниже код библиотеки ( DLL ) написан в Delphi XE4.
Эта DLL позволяет осуществлять проверку состояния сетевых адаптеров в Вашей системе, а
также отключать и включать их.
ВАЖНО! Для нормальной работы библиотеки Терминал МТ5 необходимо запускать
от имени администратора.
//+------------------------------------------------------------------+ //| netswitch.dll | //| Copyright 2015, Mikalas | //| http://www.mql5.com | //+------------------------------------------------------------------+ library netswitch; uses Winapi.Windows, System.SysUtils, Winapi.ActiveX, System.Win.ComObj, Winapi.UrlMon, System.Variants; const wbemFlagForwardOnly = $00000020; //---Коды возврата инициализации DLL INIT_DLL_FAILED = -1; INIT_DLL_SUCCES = 0; INIT_DLL_WRONG_NAME_ONE = 1; INIT_DLL_WRONG_NAME_TWO = 2; //---Коды возврата состояния адаптеров STATE_UNKNOWN = -1; STATE_MAIN_SLAVE_DISABLED = 0; STATE_MAIN_SLAVE_ENABLED = 1; STATE_MAIN_ENABLED = 2; STATE_SLAVE_ENABLED = 3; var MainName, SlaveName: string; function GetAdapter( aName: string; ip_enabled: boolean ): boolean; var oBindObj: IDispatch; oNetAdapters, oNetAdapter, odnsAddr, oWMIService: OleVariant; i, iValue: longword; oEnum: IEnumvariant; oCtx: IBindCtx; oMk: IMoniker; sFileObj: WideString; a_str: string; k: integer; begin Result:= false; //---Инициализация ActiveX CoInitialize( nil ); try sFileObj:= 'winmgmts:\\.\root\cimv2'; //--- OleCheck( CreateBindCtx( 0, oCtx ) ); OleCheck( MkParseDisplayNameEx( oCtx,PWideChar( sFileObj ), i, oMk ) ); OleCheck( oMk.BindToObject( oCtx,nil, IUnknown, oBindObj ) ); oWMIService:= oBindObj; if ( ip_enabled ) then oNetAdapters:= oWMIService.ExecQuery( 'Select * from ' + 'Win32_NetworkAdapterConfiguration ' + 'where IPEnabled=TRUE') else oNetAdapters:= oWMIService.ExecQuery( 'Select * from ' + 'Win32_NetworkAdapterConfiguration ' + 'where IPEnabled=FALSE'); oEnum:= IUnknown( oNetAdapters._NewEnum ) as IEnumVariant; //--- while ( oEnum.Next( 1, oNetAdapter, iValue ) = 0 ) do begin try a_str:= oNetAdapter.Caption; k:= Pos( ']', a_str ); if ( k > 0 ) then begin Delete( a_str, 1, k + 1) ; if ( aName = a_str ) then begin Result:= true; oNetAdapter:= Unassigned; break; end; end; except end; oNetAdapter:= Unassigned; end; //--- odnsAddr:= Unassigned; oNetAdapters:= Unassigned; oWMIService:= Unassigned; finally CoInitialize( Nil ); end; end; function InitNetSwitch( main_name: PAnsiString; slave_name: PAnsiString ): Integer; stdcall; var is_main_found: boolean; is_slave_found: boolean; begin Result:= INIT_DLL_FAILED; is_main_found:= false; is_slave_found:= false; //--- MainName:= string( main_name ); SlaveName:= string( slave_name ); //---Проверка существования основного адаптера с указанным описанием if ( MainName <> '' ) then if ( GetAdapter( MainName, false ) ) then is_main_found:= true else if ( GetAdapter( MainName, true ) ) then is_main_found:= true; //---Проверка существования запасного адаптера с указанным описанием if ( SlaveName <> '' ) then if ( GetAdapter( SlaveName, false ) ) then is_slave_found:= true else if ( GetAdapter( SlaveName, true ) ) then is_slave_found:= true; //---Результаты проверки if ( is_main_found and is_slave_found ) then Result:= INIT_DLL_SUCCES else if ( is_main_found and ( not is_slave_found ) ) then Result:= INIT_DLL_WRONG_NAME_TWO else if ( ( not is_main_found ) and is_slave_found ) then Result:= INIT_DLL_WRONG_NAME_ONE; end; function GetAdaptersState(): Integer; stdcall; begin Result:= STATE_UNKNOWN; //---Проверка состояния адаптеров if ( ( MainName <> '' ) and ( SlaveName <> '' ) ) then begin if ( GetAdapter( MainName, true ) ) then begin if ( GetAdapter( SlaveName, true ) ) then Result:= STATE_MAIN_SLAVE_ENABLED else Result:= STATE_MAIN_ENABLED; end else begin if ( GetAdapter( SlaveName, true ) ) then Result:= STATE_SLAVE_ENABLED else Result:= STATE_MAIN_SLAVE_DISABLED; end; end; end; function EnableDisableAdapter( a_name: string; disable_enable: boolean ): boolean; var FSWbemLocator: OLEVariant; FWMIService: OLEVariant; FWbemObjectSet: OLEVariant; FWbemObject: OLEVariant; oEnum: IEnumvariant; iValue: LongWord; a_str: string; k: integer; begin Result:= false; //---Инициализация ActiveX CoInitialize( nil ); try if ( a_name <> '' ) then begin FSWbemLocator:= CreateOleObject( 'WbemScripting.SWbemLocator' ); FWMIService:= FSWbemLocator.ConnectServer('localhost', 'root\CIMV2', '', '' ); if ( disable_enable ) then FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_NetworkAdapter Where NetEnabled=False', 'WQL', wbemFlagForwardOnly ) else FWbemObjectSet:= FWMIService.ExecQuery('SELECT * FROM Win32_NetworkAdapter Where NetEnabled=True', 'WQL', wbemFlagForwardOnly ); oEnum:= IUnknown(FWbemObjectSet._NewEnum) as IEnumVariant; while oEnum.Next(1, FWbemObject, iValue ) = 0 do begin a_str:= FWbemObject.Caption; k:= Pos( ']', a_str ); if ( k > 0 ) then begin Delete( a_str, 1, k + 1 ); if ( a_name = a_str ) then begin if ( disable_enable ) then FWbemObject.Enable() else FWbemObject.Disable(); Result:= true; end; end; FWbemObject:= Unassigned; end; end; finally CoInitialize( nil ); end; end; function DisableMain(): Boolean; stdcall; begin Result:= EnableDisableAdapter( MainName, false ); end; function EnableMain(): Boolean; stdcall; begin Result:= EnableDisableAdapter( MainName, true ); end; function DisableSlave(): Boolean; stdcall; begin Result:= EnableDisableAdapter( SlaveName, false ); end; function EnableSlave(): Boolean; stdcall; begin Result:= EnableDisableAdapter( SlaveName, true ); end; exports InitNetSwitch, GetAdaptersState, DisableMain, EnableMain, DisableSlave, EnableSlave; begin end.
Библиотека должна находится в папке
C:\Users\ИМЯ_ПОЛЬЗОВАТЕЛЯ\AppData\Roaming\MetaQuotes\Terminal\НОМЕР_ТЕРМИНАЛА\MQL5\Libraries
Теперь рассмотрим как работает эксперт Net_switcher:
//+------------------------------------------------------------------+ //| Net_switcher.mq5 | //| Copyright 2015, Mikalas | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2015, Mikalas" #property link "http://www.mql5.com" #property version "1.00" // enum ENUM_INIT_DLL { INIT_DLL_FAILED = -1, INIT_DLL_SUCCES = 0, INIT_DLL_WRONG_NAME_ONE = 1, INIT_DLL_WRONG_NAME_TWO = 2 }; enum ENUM_NET_STATE { STATE_UNKNOWN = -1, STATE_MAIN_SLAVE_DISABLED = 0, STATE_MAIN_SLAVE_ENABLED = 1, STATE_MAIN_ENABLED = 2, STATE_SLAVE_ENABLED = 3 }; // #import "netswitch.dll" ENUM_INIT_DLL InitNetSwitch( const string NameOne, const string NameTwo ); ENUM_NET_STATE GetAdaptersState(); bool DisableMain(); bool EnableMain(); bool DisableSlave(); bool EnableSlave(); #import // input string p_expert = "=== Параметры эксперта ==="; // input uint TimerTime = 500; //Период таймера (мсек) input string AdaptNameOne = "Intel(R) 82579V Gigabit Network Connection"; //Описание основного адаптера input string AdaptNameTwo = "Realtek PCIe GBE Family Controller"; //Описание запасного адаптера input string p_chart = "=== Параметры графика ==="; // input color ButtDown = clrMagenta; //Цвет нажатой кнопки input color ButtUp = clrMediumSeaGreen; //Цвет отжатой кнопки input uint FontSize = 10; //Размер фонта input uint StrSpace = 13; //Расстояние между строками input color FontColor = clrWhite; //Цвет фонта input ENUM_CHART_MODE Chart_mode = CHART_CANDLES; //Режим графика input ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT; //Период графика input color Candle_bull = clrGreen; //Цвет Бычьей свечи input color Candle_bear = clrRed; //Цвет Медвежьей свечи input color BackGround = clrGray; //Цвет фона input color Color_last = clrWhite; //Цвет последней цены input color Chart_up = clrBlack; //Цвет свечи вверх input color Chart_down = clrBlack; //Цвет свечи вниз input color Chart_grid = clrDarkGray; //Цвет сетки // bool h_res; bool not_first; bool is_connected; MqlDateTime tick_time; ENUM_NET_STATE net_state; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { not_first = false; //---Инициализация DLL ENUM_INIT_DLL init_dll = InitNetSwitch( AdaptNameOne, AdaptNameTwo ); switch( init_dll ) { case INIT_DLL_FAILED: MessageBox( "Ошибка инициализации netswitch.dll!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); break; case INIT_DLL_WRONG_NAME_ONE: MessageBox( "Не найден сетевой адаптер с описанием " + AdaptNameOne + " .", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); break; case INIT_DLL_WRONG_NAME_TWO: MessageBox( "Не найден сетевой адаптер с описанием " + AdaptNameTwo + " .", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); break; } //--- Проверка состояния сетевых адаптеров net_state = GetAdaptersState(); //--- switch( net_state ) { case STATE_UNKNOWN: MessageBox( "Ошибка проверки адаптеров!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); break; case STATE_MAIN_SLAVE_DISABLED: h_res = EnableMain(); //Подключаем адаптеры. Первым - основной if ( h_res ) { EnableSlave(); } break; case STATE_MAIN_ENABLED: EnableSlave(); //Подключаем запасной адаптер break; case STATE_SLAVE_ENABLED: h_res = DisableSlave(); //Отключаем запасной, включаем основной, а затем, опять включаем запасной if ( h_res ) { h_res = EnableMain(); if ( h_res ) { EnableSlave(); } } break; case STATE_MAIN_SLAVE_ENABLED: h_res = DisableSlave(); //Так как мы не знаем какой адаптер используется, мы делаем рабочим основной адаптер if ( h_res ) { EnableSlave(); } break; } //--- //--- if( !ObjectCreate( 0, "reset_button", OBJ_BUTTON, 0, 0, 0 ) ) { MessageBox( "Кнопка 'Reset' не создана!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } else { ObjectSetInteger( 0, "reset_button", OBJPROP_CORNER, CORNER_RIGHT_LOWER ); ObjectSetInteger( 0, "reset_button", OBJPROP_XDISTANCE, 40 ); ObjectSetInteger( 0, "reset_button", OBJPROP_YDISTANCE, 18 ); ObjectSetInteger( 0, "reset_button", OBJPROP_XSIZE, 40 ); ObjectSetInteger( 0, "reset_button", OBJPROP_YSIZE, 18 ); ObjectSetInteger( 0, "reset_button", OBJPROP_BGCOLOR, ButtUp ); ObjectSetInteger( 0, "reset_button", OBJPROP_STATE, false ); ObjectSetString( 0, "reset_button", OBJPROP_TEXT, "Reset" ); } //---инфострока if( !ObjectCreate( 0, "info_label_1", OBJ_LABEL, 0, 0, 0 ) ) { MessageBox( "Инфострока 1 не создана!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } else { ObjectSetInteger( 0, "info_label_1", OBJPROP_CORNER, CORNER_LEFT_UPPER ); ObjectSetInteger( 0, "info_label_1", OBJPROP_ANCHOR, ANCHOR_LEFT_UPPER ); ObjectSetInteger( 0, "info_label_1", OBJPROP_XDISTANCE, 5 ); ObjectSetInteger( 0, "info_label_1", OBJPROP_YDISTANCE, 15 ); ObjectSetInteger( 0, "info_label_1", OBJPROP_FONTSIZE, FontSize ); ObjectSetInteger( 0, "info_label_1", OBJPROP_COLOR, FontColor ); ObjectSetInteger( 0, "info_label_1",OBJPROP_BACK, false ); ObjectSetString( 0, "info_label_1", OBJPROP_TEXT, "Инициализация..." ); } //--- Set chart in candles if ( !ChartSetInteger(0, CHART_MODE, Chart_mode ) ) { MessageBox( "Режим свечей не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set Time Frame H1 if ( !ChartSetSymbolPeriod( 0, NULL, TimeFrame ) ) { MessageBox( "Период графика не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set candles color if ( !ChartSetInteger( 0, CHART_COLOR_CANDLE_BULL, Candle_bull ) ) { MessageBox( "Цвет бычьих свечей не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- if ( !ChartSetInteger( 0, CHART_COLOR_CANDLE_BEAR, Candle_bear ) ) { MessageBox( "Цвет медвежьих свечей не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set fon color if ( !ChartSetInteger( 0, CHART_COLOR_BACKGROUND, BackGround ) ) { MessageBox( "Цвет фона не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set hide line bid if ( !ChartSetInteger( 0, CHART_SHOW_BID_LINE, false ) ) { MessageBox( "Не скрыта линия BID!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set show line last if ( !ChartSetInteger(0, CHART_SHOW_LAST_LINE, true ) ) { MessageBox( "Линия последней цены не установлена!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set line bid color if ( !ChartSetInteger(0, CHART_COLOR_LAST, Color_last ) ) { MessageBox( "Цвет линии последней цены не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set color bid up if ( !ChartSetInteger( 0, CHART_COLOR_CHART_UP, Chart_up ) ) { MessageBox( "Цвет свечи вверх не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Set color bid up if ( !ChartSetInteger( 0, CHART_COLOR_CHART_DOWN, Chart_down ) ) { MessageBox( "Цвет свечи вниз не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //--- Color grid if ( !ChartSetInteger( 0, CHART_COLOR_GRID, Chart_grid ) ) { MessageBox( "Не установлен цвет сетки!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } //---Проверка подключения терминала к торговому серверу if ( TerminalInfoInteger( TERMINAL_CONNECTED ) ) { is_connected = true; } else { is_connected = false; } ObjectSetString( 0, "info_label_1", OBJPROP_TEXT, "Соединение через основной адаптер." ); ChartRedraw(); //--- Установка таймера if ( !EventSetMillisecondTimer( TimerTime ) ) { MessageBox( "Таймер не установлен!", "Ошибка", MB_OK | MB_ICONHAND ); return( INIT_FAILED ); } return( INIT_SUCCEEDED ); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit( const int reason ) { //--- удаление таймера EventKillTimer(); //--- ObjectDelete( 0, "reset_button" ); ObjectDelete( 0, "info_label_1" ); } //+------------------------------------------------------------------+ //| Expert Switch To Main adapter function | //+------------------------------------------------------------------+ void SwitchToMain( const ENUM_NET_STATE a_state ) { switch( a_state ) { case STATE_UNKNOWN: case STATE_MAIN_SLAVE_DISABLED: h_res = EnableMain(); if ( h_res ) { EnableSlave(); } break; case STATE_MAIN_SLAVE_ENABLED: h_res = DisableSlave(); if ( h_res ) { EnableSlave(); } break; case STATE_MAIN_ENABLED: EnableSlave(); break; case STATE_SLAVE_ENABLED: h_res = DisableSlave(); if ( h_res ) { h_res = EnableMain(); if ( h_res ) { EnableSlave(); } } break; } } //+------------------------------------------------------------------+ //| Expert timer function | //+------------------------------------------------------------------+ void OnTimer() { TimeTradeServer( tick_time ); //--- if ( !TerminalInfoInteger( TERMINAL_CONNECTED ) ) { if ( !not_first ) { not_first = true; is_connected = false; Print( "OnTimer: Соединение потеряно!" ); SendMail( "Lost connection", "Соединение потеряно " + string( tick_time.hour ) + ":" + string( tick_time.min ) + ":" + string( tick_time.sec ) ); //--- net_state = GetAdaptersState(); //--- switch( net_state ) //Влючаем резервный канал { case STATE_UNKNOWN: case STATE_MAIN_SLAVE_DISABLED: h_res = EnableSlave(); if ( h_res ) { EnableMain(); } break; case STATE_MAIN_SLAVE_ENABLED: h_res = DisableMain(); if ( h_res ) { EnableMain(); } break; case STATE_MAIN_ENABLED: h_res = DisableMain(); if ( h_res ) { h_res = EnableSlave(); if ( h_res ) { EnableMain(); } } break; case STATE_SLAVE_ENABLED: EnableMain(); break; } } } else { //Соединение восстановлено if ( !is_connected ) { not_first = false; is_connected = true; Print( "OnTimer: Соединение восстановлено!" ); ObjectSetString( 0, "info_label_1", OBJPROP_TEXT, "Соединение через запасной адаптер." ); ChartRedraw(); } } } //+------------------------------------------------------------------+ //| Expert Chart event function | //+------------------------------------------------------------------+ void OnChartEvent( const int id, const long& lparam, const double& dparam, const string& sparam ) { long b_state; if ( id == CHARTEVENT_OBJECT_CLICK ) { if ( sparam == "reset_button" ) { if ( ObjectGetInteger( 0, "reset_button", OBJPROP_STATE, 0, b_state ) ) { if ( b_state == 1 ) { ObjectSetInteger( 0, "reset_button", OBJPROP_BGCOLOR, ButtDown ); ObjectSetString( 0, "reset_button", OBJPROP_TEXT, "Reset" ); ChartRedraw(); //--- net_state = GetAdaptersState(); SwitchToMain( net_state ); ObjectSetString( 0, "info_label_1", OBJPROP_TEXT, "Соединение через основной адаптер." ); ObjectSetInteger( 0, "reset_button", OBJPROP_STATE, false ); ObjectSetInteger( 0, "reset_button", OBJPROP_BGCOLOR, ButtUp ); ObjectSetString( 0, "reset_button", OBJPROP_TEXT, "Reset" ); ChartRedraw(); } } } } } //+------------------------------------------------------------------+ //| The END | //+------------------------------------------------------------------+
Библиотека использует описания (не имена в Windows) сетевых адаптеров ( см. рис)
которые необходимо ввести в настройках эксперта, другие настройки интуитивно понятны.
При инициализации эксперта, выключая и выключая адаптеры (мы не знаем через который установлено
соединение), эксперт устанавливает соединение через основной (1 в настройках эксперта) адаптер.
Далее запускается таймер, который с периодом 0,5 сек. проверяет состояние сети.
Логика проверки несложная.
При отсутствии соединения эксперт переключает соединение на запасной адаптер.
Нет смысла автоматически пытаться (потом) переключится на основной адаптер, так как
мы не знаем причины разрыва соединения ("упал" торговый сервер или нет интернета).
Кнопка "Reset" позволяет установить эксперта в исходное состояние (работа через основной адаптер).
Эксперт, при потере соединения, может отсылать e-mail на
указанный в настройках терминала адрес.
На www.mail.ru можно настроить почтовый ящик таким образом, чтобы в папку
"Входящие" помещались письма "от самого себя", а все остальные помещаются в
папку "СПАМ". Также, можно настроить отправку СМС, при появлении в папке
"Входящие" нового письма. Рекомендую придумать "навороченное" имя
н-р: jsdg!lzsnfdh486skldjg9824@mail.ru, чтобы имя ящика было как можно
более уникальным. Тогда Вы точно будете получать письма и СМС только от терминала.
Удалённое управление
В качестве инструмента удалённого управления успешно использую TeamViewer
(для личного пользования - бесплатный).
http://www.teamviewer.com/ru/index.aspx
Преимущество TeamViewer:
1. Не "привязан" к IP адресу компьютера
2. При его использовании происходит двойная проверка доступа к ПК.
( сам TeamViewer требует ID, пароль и пароль на Вашем ПК )
3. Суперпростой.
Заключение
Предпринятые меры по организации бесперебойной торговли позволят Вам
с невысокой долей беспокойства за процесс торговли отлучаться на недельку из дома ( н-р на рыбалку :) )
Надеюсь, что статья будет полезна не только начинающим трейдерам.
С уважением, и удачной торговли!
P/S Исходные и откомпилированные файлы в ZIP-архиве,
предоставляются для личного пользования.