MT5 e trans2quik.dll - pagina 5

 

Ho completamente abbandonato l'idea di collegare MT5 e Quick, mi sono accontentato di Quick (DEE server + trans2quik.dll).

Sto considerando la realizzazione di questo programma.


Selector1 riceve costantemente i dati dal server DDE e li "immagazzina" in Storage e chiama la funzione OnTick nel Child corrispondente.

Quando si chiama GetStorageData, il server DDE dovrebbe essere in pausa e i dati dovrebbero essere memorizzati in Storage.

Quando Selector2 chiama callback, il server DDE e la registrazione di Storage dovrebbero essere sospesi e la chiamata GetStorageData dovrebbe essere disabilitata

Cioè il selettore2 ha priorità alta, GetStorageData normale e il selettore1 bassa.

Domande:

Come posso sincronizzare Selector1, Selector2 e GetStorageData con grazia?

Forse ci sono esempi concreti di tale sincronizzazione (non ho mai implementato una cosa del genere)?

 
prostotrader:

Abbandonata l'idea di collegare MT5 e Quick, si è deciso di usare solo Quick (server DEE + trans2quik.dll)

Sto considerando la realizzazione di questo programma.

1. la decisione molto giusta di lasciare solo Quick.

2. La connessione via DEE è una soluzione molto controversa. Molti dicono che DDE è instabile, ma io non lo so.

Secondo me, una soluzione migliore e più versatile, è un'applicazione Lua-DLL. Io uso questa opzione. Naturalmente, dipende dal proprietario.

 
Yuriy Asaulenko:

1. la decisione di tenere solo la Quick è stata quella giusta.

2. La comunicazione via DDE è una decisione molto controversa. Molte persone dicono che DDE è instabile, ma non lo so.

Secondo me, una soluzione migliore e più versatile, è un'applicazione Lua-DLL. Io uso questa opzione. Naturalmente, dipende dall'ospite.

Ho scritto molto tempo fa un server DDE per Quick - funziona senza problemi e abbastanza veloce (non più lento di Lua - DLL),

e non è affatto necessario scrivere codice aggiuntivo per il ricevitore di dati Lua e DDL.

Aggiunto

In effetti, ho già scritto il programma mostrato nel diagramma (e funziona), ma ho incontrato un problema di sincronizzazione.

 
prostotrader:

Ho scritto molto tempo fa un server DDE per Quick - funziona senza problemi e abbastanza veloce (non più lento di Lua - DLL),

e non c'è affatto bisogno di scrivere codice aggiuntivo in Lua.

Dato che non ho fatto nessun DDE, la domanda è: come si fa il DDE? Penso che ci sia bisogno di fare una tabella con i dati, e poi eseguirla tramite DDE.

C'è una confusione con gli eventi. Qualcosa è cambiato, e sembra che l'intera tabella sia passata al DDE. O mi sbaglio?

Diciamo che mi sbaglio. Allora come faccio a identificare l'evento sul lato ricevente?

prostotrader:

In effetti, ho già scritto il programma mostrato nel diagramma (e funziona), ma ho incontrato un problema di sincronizzazione.

Sincronizzare cosa con cosa?

Con Lua, questo problema è risolto da callback dalla DLL a dati arbitrari.

 
Yuriy Asaulenko:

Dato che non ho avuto a che fare con il DDE, la domanda è come è fatto il DDE? Sembra che si debba fare una tabella con i dati, e poi eseguirla attraverso il DDE.

C'è una confusione con gli eventi. Qualcosa è cambiato, e sembra che l'intera tabella sia passata al DDE. O mi sbaglio?

Diciamo che mi sbaglio. Allora come identificare l'evento sul lato ricevente?

In Quick, la tabella richiesta viene generata per l'output.

Infine, lanciamo la nostra applicazione con il server DDE e facciamo uscire questa tabella via DDE.

Al primo output da Quick, l'intera tabella viene inviata a DEE, poi solo la riga della tabella

in cui si sono verificati i cambiamenti.

Nella tabella stessa (è trasmessa per intero) c'è (per esempio nel mio caso) un nome di strumento - questo è l'identificatore


 

Il server DDE stesso ha poche righe (io ce l'ho in Pascal, ma ci sono molti esempi su Internet in altri linguaggi)

unit DdeUnit;

interface

uses
  Winapi.Windows, System.Classes, System.Types, Vcl.Forms, Winapi.DdeMl,
  System.SysUtils, System.StrUtils, Vcl.Controls, Winapi.Messages, QTypes;

const
  WM_DDE_ADDQUE = WM_USER + 2;

var
  ServiceName: string = 'quikdde';             // DDE server name

//  procedure ClearTable(Table: TDataTable); overload;

type
  TPokeAction = (paAccept, paPass, paReject);
  TDdePokeEvent = procedure(const Topic: string; var Action: TPokeAction) of object;
  TDdeDataEvent = procedure(const Topic: string; Cells: TRect; Data: TDataTable) of object;

  PDdeQueItem = ^TDdeQueItem;
  TDdeQueItem = packed record
    data: Pointer;
    size: integer;
    sTopic: String;
    sCells: String;
  end;

  TDdeServer = class(TWinControl)
  private
    Inst: Integer;
    ServiceHSz: HSz;
    TopicHSz: HSz;
    fOnPoke: TDdePokeEvent;
    fOnData: TDdeDataEvent;
    fDdeQue: TThreadList;
  protected
    function XLTDecodeV(data: pointer; datasize: integer): TDataTable;
    function DecodeCellAddr(CellAddr: string): TRect;
    procedure AddQue(var Message: TWMSysCommand); message WM_DDE_ADDQUE;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property OnPoke: TDdePokeEvent read fOnPoke write fOnPoke;
    property OnData: TDdeDataEvent read fOnData write fOnData;
  end;

//--- Pointer functions ----
function addp(p: Pointer; increment: int64 = 1): Pointer; overload;
function addp(p: Pointer; increment: pointer): Pointer; overload;
function subp(p: Pointer; decrement: int64 = 1): Pointer; overload;
function subp(p: Pointer; decrement: pointer): Pointer; overload;


implementation
uses main;

function CallbackProc(CallType, Fmt: UINT; Conv: HConv; hsz1, hsz2: HSZ;
                      Data: HDDEData; Data1, Data2: DWORD): HDDEData stdcall;
var
  action: TPokeAction;
  sTopic: String;
  P: PDdeQueItem;
  Buff: array[0..255] of WideChar;
begin
  result:= DDE_FNOTPROCESSED;
//---
  case CallType of
    XTYP_CONNECT: result:= 1;
    XTYP_POKE: begin
      DdeQueryString(QTrader.DdeServer.Inst, HSz1, @Buff,
                                        sizeof(Buff), CP_WINUNICODE);
      sTopic:= string(Buff);
      action:= paPass;
      if Assigned(QTrader.DdeServer.fOnPoke) then
        QTrader.DdeServer.fOnPoke(sTopic, action);
//---
      case action of
        paAccept: begin
          DdeQueryString(QTrader.DdeServer.Inst, HSz2,
                         @Buff, sizeof(Buff), CP_WINUNICODE);
          New(P);
          P^.sTopic:= sTopic;
          P^.sCells:= string(Buff);
          P^.size:= DdeGetData(Data, nil, 0, 0);
          GetMem(P^.data, P^.size);
          DdeGetData(Data, P^.data, P^.size, 0);
//---
          with QTrader.DdeServer.fDdeQue.LockList do
          try
            add(P);
          finally
            QTrader.DdeServer.fDdeQue.UnlockList;
          end;
          if (QTrader.DdeServer.Handle <> 0) then
            PostMessage(QTrader.DdeServer.Handle, WM_DDE_ADDQUE, 0, 0);
          result:= DDE_FACK;
        end;
        paPass: result:=  DDE_FACK;
        paReject: result:=  DDE_FNOTPROCESSED;
      end;
    end;
  end;
end;


constructor TDdeServer.Create;
begin
  inherited;
  fDdeQue := TThreadList.Create;
  Parent := TWinControl(AOwner);
  CreateHandle;
  Inst := 0;
  if (DdeInitialize(Inst, @CallbackProc, APPCLASS_STANDARD, 0) = DMLERR_NO_ERROR) then
  begin
    ServiceHSz := DdeCreateStringHandle(Inst, PWideChar(ServiceName), CP_WINUNICODE);
    if(DdeNameService(Inst, ServiceHSz, 0, DNS_REGISTER) = 0) then
      raise Exception.Create( 'Не удалось зарегистрировать имя DDE-сервиса ''' +
                                                           ServiceName + '''' );
    TopicHSz := DdeCreateStringHandle(Inst, PWideChar('Topic'), CP_WINUNICODE);
  end else
    raise Exception.Create( 'Не удалось выполнить инициализацию DDE' );
end;

destructor TDdeServer.Destroy;
begin
  DdeNameService(Inst, ServiceHsz, 0, DNS_UNREGISTER);
  fDdeQue.Free;
  DdeFreeStringHandle(Inst, ServiceHsz);
  DdeUninitialize(Inst);
  inherited;
end;

procedure TDdeServer.AddQue(var Message: TWMSysCommand);
var
  vt: TDataTable;
  Cells: TRect;
  p: PDdeQueItem;
  i: integer;
begin
  with fDdeQue.LockList do
  try
    p:= PDdeQueItem(Items[Count - 1]);
    Cells:= DecodeCellAddr(p^.sCells);
    vt:= XLTDecodeV(p^.data, p^.size);
    if (Assigned(QTrader.DdeServer.fOnData)) then
      QTrader.DdeServer.fOnData(p^.sTopic, Cells, vt);
    for i:= (Count - 1) downto 0 do
    begin
      p:= Items[i];
      FreeMem(p^.data, p^.size);
      Delete(i);
      Dispose(p);
    end;
  finally
    fDdeQue.UnlockList;
  end;
end;


function TDdeServer.XLTDecodeV(data: pointer; datasize: integer): TDataTable;
var
  i: integer;
  curr: pointer;
  BlockType: word;
  BlockSize: word;
  StringSize: byte;
  RealData: real;
  StringData: shortstring;
  DataNum: integer;
begin
  curr:= addp(data, 4);
  result.RowCount := Word(curr^);
  curr:= addp(curr, 2);
  result.ColCount := Word(curr^);
  curr:= addp(curr, 2);
//--- set array size ---
  SetLength(result.Cells, result.RowCount);
//---
  for i := 0 to result.RowCount - 1 do
    SetLength(result.Cells[i], result.ColCount);
//--- data block --------
  DataNum := 0;
  while(Integer(subp(curr, data)) < datasize) do
  begin
    BlockType:= Word(curr^);
    curr:= addp(curr, 2);
    BlockSize:= Word(curr^);
    curr:= addp(curr, 2);
    case BlockType of
      1: begin
           while(BlockSize > 0) do
           begin
             RealData:= Real(curr^);
             curr:= addp(curr, 8);
             dec(BlockSize, 8);
             result.Cells[(DataNum div result.ColCount),
                          (DataNum mod result.ColCount)]:= FloatToStr(RealData);
             inc(DataNum);
           end;
         end;
      2: begin
           while(BlockSize > 0) do
           begin
             StringSize:= Byte(curr^);
             curr:= addp( curr );
             StringData[0]:= AnsiChar(Chr(StringSize));
//---
             for i:= 1 to StringSize do
             begin
               StringData[i]:= AnsiChar(Char(curr^));
               curr:= addp(curr);
             end;
             result.Cells[(DataNum div result.ColCount),
                          (Datanum mod result.ColCount)]:= string(StringData);
             inc(DataNum);
             dec(BlockSize, StringSize + 1);
           end;
         end;
    end;
  end;
end;


function TDdeServer.DecodeCellAddr( CellAddr: string ): TRect;
var
  tmp1, tmp2: integer;
begin
  CellAddr:= UpperCase(CellAddr);
  tmp1:= PosEx('R', CellAddr);
  tmp2:= PosEx('C', CellAddr, tmp1);
  try
    result.Top:= StrToInt(copy(CellAddr, tmp1 + 1, tmp2 - tmp1 - 1)) - 1;
  except
    exit;
  end;
  tmp1:= PosEx('R', CellAddr, tmp2);
  try
    Result.Left:= StrToInt(copy(CellAddr, tmp2 + 1, tmp1 - tmp2 - 1 - 1)) - 1;
  except
    exit;
  end;
  tmp2:= PosEx('C', CellAddr, tmp1);
  try
    result.Bottom:= StrToInt(copy(CellAddr, tmp1 + 1, tmp2 - tmp1 - 1)) - 1;
    result.Right:= StrToInt(copy(CellAddr, tmp2 + 1, Length(CellAddr) - tmp2)) - 1;
  except
    exit;
  end;
end;

function addp(P: Pointer; increment: int64 = 1): Pointer; overload;
begin
  result:= Pointer(Int64(p) + increment);
end;

function addp(P: Pointer; increment: Pointer): Pointer; overload;
begin
  result:= Pointer(Int64(p) + Int64(increment));
end;

function subp(P: Pointer; decrement: int64 = 1): Pointer; overload;
begin
  result:= Pointer(Int64(p) - decrement);
end;

function subp(P: Pointer; decrement: Pointer): Pointer; overload;
begin
  result:= Pointer(Int64(p) - Int64(decrement));
end;

end.
 

Una finestra figlia viene creata dal nome dello strumento (come in MT 5)


 
Yuriy Asaulenko:

Con cosa?


Ho descritto il problema nel topic del diagramma

 
prostotrader:

Ho descritto il problema nel topic del diagramma

Scusa, non mi ero reso conto. Se ho capito bene:

Imho, la soluzione è usare un DBMS comeStorage, diciamo MS SQL Server. Questa può essere una soluzione parziale.

La seconda è quella di usare buffer intermedi, come last in, first out. Bene, e separazione dei fili.

Allora non c'è bisogno di fermare nulla, tutto viene semplicemente scritto nei buffer. Bene, e il DBMS ha un accesso multiutente.

Applico tutto questo, ma non sono buona amica di Pascal dal 6.

PS Dicono che si possono usare le librerie NET da Pascal. Per usare comeStorage, potrebbe avere senso usareSystem.Data,System.Data.DataSet eSystem.Data.DataTable. Se ricordo bene, non c'era nessun problema con l'accesso multiutente inDataTable.

ZZY2 Ora sto cercando di usare SQLite come database, ma ancora nessun risultato definitivo. E certamente non è un DBMS, ma in una forma ridotta l'accesso multiutente è possibile, ed è possibile creare un database in memoria.

System.Data Namespace
System.Data Namespace
  • douglaslMS
  • docs.microsoft.com
Пространство имен обеспечивает доступ к классам, представляющим архитектуру ADO.NET. The namespace provides access to classes that represent the ADO.NET architecture. ADO.NET позволяет создавать…
 

No, ho solo bisogno di sincronizzare 3 thread (fondamentalmente scrivere un Synchronizer), ma

Non so come.