Вслед за релизом ATcl и эскизом панельки, делаем набросок модельки
В реальной жизни пишется всё гораздо-гораздо проще и быстрее чем в местных оф.публикациях :-) Потому-что сам себе за lines-of-code и объём не заплатишь.
Чтобы наш GUI работал, он должен уметь получать данные бизнес-логики. А бизнес-логика должна уметь отдавать данные. Данные между ними это моделька. То есть данные надо просто представить в виде удобным и для работы как бизнес-логики (то есть программы на MQL) так и GUI на tcl.
При этом программа MQL не должна даже задумываться о том сколько, как, что и где рисуется на экране (или отдаётся браузером через http или транслируется). Не её это собачье дело. В модели никаких опций связанных с отображением, сортировкой или выборками нет.
В терминале наша бизнес-логика и все сведения о мире сосредоточены в базе инструментов (то что получаем через SymbolInfoXXX, PositionInfo и так далее), эти базы мы можем читать в основном только по опросу. Единственные оповещения об изменениях - OnTick,OnBookEvent,OnTrade и то "бабушка надвое" :-) То есть на уровне MQL придётся заводить свои массивы объектов и постоянно их синхронизовать с базами терминала.
Со стороны tcl естественным представлением являются ассоциативные массивы, с произвольными данными внутри. Значит при синхронизации баз терминала со своими массивами будет ещё обновлять массивы tcl. Tcl очень гибкий и когда надо будет генерировать события об отдельных изменениях, это несложно будет сделать.
Фигачим код, куда деваться :-)
#include <ATcl/ATcl.mqh> #include <nektomk/Algo.mqh> #resource "MajorTrade.tcl" as string MajorTrade_tcl; ATcl *tcl=NULL; class SymbolInfo { public: SymbolInfo(string name); ~SymbolInfo(); void FullUpdate(); // полностью обновить данные по инструменту bool CheckTick(); public: int id; // индекс в таблице символов string name; string base; string quote; int digits; double point; double tickSize; double tickValue; double lotSize,minLot,maxLot,lotStep; datetime time; double bid,ask; MqlTick tick; }; SymbolInfo::SymbolInfo(string _name) { name=_name; id=-1; FullUpdate(); } SymbolInfo::~SymbolInfo() { } void SymbolInfo::FullUpdate() { base=SymbolInfoString(name,SYMBOL_CURRENCY_BASE); quote=SymbolInfoString(name,SYMBOL_CURRENCY_PROFIT); digits=(int)SymbolInfoInteger(name,SYMBOL_DIGITS); point=SymbolInfoDouble(name,SYMBOL_POINT); tickSize=SymbolInfoDouble(name,SYMBOL_TRADE_TICK_SIZE); tickValue=SymbolInfoDouble(name,SYMBOL_TRADE_TICK_VALUE); lotSize=SymbolInfoDouble(name,SYMBOL_TRADE_CONTRACT_SIZE); minLot=SymbolInfoDouble(name,SYMBOL_VOLUME_MIN); maxLot=SymbolInfoDouble(name,SYMBOL_VOLUME_MAX); lotStep=SymbolInfoDouble(name,SYMBOL_VOLUME_STEP); time=0; bid=0.0; ask=0.0; ZeroMemory(tick); PrintFormat("symbol %s, base %s quote %s",name,base,quote); } bool SymbolInfo::CheckTick(void) { MqlTick tmpTick; datetime tmpTime; if (SymbolInfoTick(name,tmpTick)) { if (tmpTick.ask!=tick.ask || tmpTick.bid!=tick.bid || tmpTick.time!=tick.time) { tick=tmpTick; bid=tick.bid; ask=tick.ask; time=tick.time; return true; } } else if ((tmpTime=(datetime)SymbolInfoInteger(name,SYMBOL_TIME))!=time) { double tmpBid=SymbolInfoDouble(name,SYMBOL_BID); double tmpAsk=SymbolInfoDouble(name,SYMBOL_ASK); if (tmpBid!=bid || tmpAsk!=ask) { ZeroMemory(tick); time=tick.time=tmpTime; bid=tick.bid=tmpBid; ask=tick.ask=tmpAsk; return true; } } return false; } class AssetInfo { public: AssetInfo(string name,SymbolInfo *sym=NULL); ~AssetInfo(); public: int id; // индекс в таблице активов string name; // имя актива SymbolInfo *symbol; // инструмент к USD }; AssetInfo::AssetInfo(string _name,SymbolInfo *_sym) { id=-1; name=_name; symbol=_sym; } AssetInfo::~AssetInfo() { } class ModelProvider { public: ModelProvider(); ~ModelProvider(); int OnInit(); void OnDeinit(const int reason); void OnTimer(); void CheckTicks(); public: SymbolInfo *SYMBOLS[]; AssetInfo *ASSETS[]; void InitSymbols(); SymbolInfo *AddSymbol(string symbol); AssetInfo *AddAsset(string asset,SymbolInfo *sym); void SyncSymbol(SymbolInfo *sym); void SyncTick(SymbolInfo *sym); datetime time; ulong nextCheckTicks; }; ModelProvider::ModelProvider(void) { time=0; } ModelProvider::~ModelProvider() { for(int pos=ArraySize(ASSETS)-1;pos>=0;pos--) { if (ASSETS[pos]!=NULL) { delete ASSETS[pos]; ASSETS[pos]=NULL; } } ArrayResize(ASSETS,0); for(int pos=ArraySize(SYMBOLS)-1;pos>=0;pos--) { if (SYMBOLS[pos]!=NULL) { delete SYMBOLS[pos]; SYMBOLS[pos]=NULL; } } ArrayResize(SYMBOLS,0); }; int ModelProvider::OnInit(void) { InitSymbols(); return INIT_SUCCEEDED; } void ModelProvider::OnDeinit(const int reason) { } void ModelProvider::OnTimer() { CheckTicks(); } void ModelProvider::CheckTicks() { datetime tmpTime=TimeCurrent(); if (tmpTime!=time) { for(int pos=ArraySize(SYMBOLS)-1;pos>=0;pos--) { SymbolInfo *sym=SYMBOLS[pos]; if (sym==NULL) continue; if (sym.CheckTick()) { //PrintFormat("%s tick bid=%f ask=%f",sym.name,sym.bid,sym.ask); SyncTick(sym); } } } } // строим таблицы символов и активов void ModelProvider::InitSymbols(void) { if (ArraySize(ASSETS)==0) { AddAsset("USD",NULL); } int total=SymbolsTotal(false); for(int pos=0;pos<total;pos++) { string symbol=SymbolName(pos,false); if (symbol==NULL || symbol=="") continue; string base=SymbolInfoString(symbol,SYMBOL_CURRENCY_BASE); string quote=SymbolInfoString(symbol,SYMBOL_CURRENCY_PROFIT); if (base==NULL || base=="" || base==symbol) continue; if (quote==NULL || quote=="" || quote==symbol || quote==base) continue; if (StringFind(symbol,base)<0) continue; if (StringFind(symbol,quote)<0) continue; SymbolInfo *sym=AddSymbol(symbol); if (sym==NULL) continue; if (base=="USD") { AddAsset(quote,sym); } else if (quote=="USD") { AddAsset(base,sym); } SyncSymbol(sym); } } void ModelProvider::SyncSymbol(SymbolInfo *sym) { if (tcl.Call(tcl.Obj("UpdateSymbol"),tcl.Obj(sym.name), tcl.Obj("name base quote digits point tickSize tickValue lotSize minLot maxLot lotStep"), tcl.Obj(sym.name), tcl.Obj(sym.base), tcl.Obj(sym.quote), tcl.Obj(sym.digits), tcl.Obj(sym.point), tcl.Obj(sym.tickSize), tcl.Obj(sym.tickValue), tcl.Obj(sym.lotSize), tcl.Obj(sym.minLot), tcl.Obj(sym.maxLot), tcl.Obj(sym.lotStep))!=TCL_OK) { PrintFormat("UpdateSymbol error %s",tcl.StringResult()); } SyncTick(sym); } void ModelProvider::SyncTick(SymbolInfo *sym) { if (tcl.Call(tcl.Obj("UpdateSymbol"),tcl.Obj(sym.name), tcl.Obj("time bid ask tickValue tickSize"), tcl.Obj(sym.name), tcl.Obj(sym.time), tcl.Obj(sym.bid), tcl.Obj(sym.ask), tcl.Obj(sym.tickValue), tcl.Obj(sym.tickSize))!=TCL_OK) { PrintFormat("SyncTick error %s",tcl.StringResult()); } } SymbolInfo *ModelProvider::AddSymbol(string symbol) { SymbolInfo *sym=new SymbolInfo(symbol); if (sym==NULL) return NULL; int id=ArrayPush(SYMBOLS,sym); sym.id=id; return sym; } AssetInfo *ModelProvider::AddAsset(string name,SymbolInfo *sym) { AssetInfo *asset=new AssetInfo(name,sym); if (asset==NULL) return NULL; int id=ArrayPush(ASSETS,asset); asset.id=id; PrintFormat("Asset %s%s",asset.name,asset.symbol==NULL?"":" via "+asset.symbol.name); return asset; } ModelProvider *model=NULL; void OnStart() { ATcl_OnInit(); tcl=new ATcl(); tcl.OnInit(); tcl.Eval(MajorTrade_tcl); model=new ModelProvider(); model.OnInit(); tcl.Eval("package require tkcon"); tcl.Eval("tkcon show"); while(!IsStopped()) { model.OnTimer(); tcl.Update(); Sleep(20); } if (model!=NULL) { model.OnDeinit(REASON_PROGRAM); delete model; model=NULL; } if (tcl!=NULL) { //tcl.OnDeInit(); delete tcl; tcl=NULL; } ATcl_OnDeinit(REASON_PROGRAM); }
и маленький довесок tcl (процедуры обновляющие ассоциативные массивы)
array set SYMBOLS {} array set ASSETS {} proc Update { table key fields args } { upvar #0 $table data if { [ info exists data($key) ] } { set record $data($key) } else { set record {} } foreach field $fields value $args { dict set record $field $value } set data($key) $record } proc UpdateSymbol { symbol fields args } { tailcall Update SYMBOLS $symbol $fields {*}$args } proc UpdateAsset { asset fields args } { tailcall Update ASSETS $symbol $fields {*}$args }
Моделька начала работать. В целях отладки поставлен вызов tkcon - можно смотреть что там пишется/обновляется в массивах SYMBOLS ASSETS:

время выдастся, вечерком свяжу полученную эскизную панель с этой моделью и 80% сделано ;-) останется только исполнение


