MT5 und trans2quik.dll - Seite 5

 

Die Idee, MT5 und Quick zu verknüpfen, habe ich komplett verworfen, ich habe mich auf Quick (DEE-Server + trans2quik.dll) beschränkt.

Ich erwäge, dieses Programm zu realisieren.


Selector1 holt ständig Daten vom DDE-Server ab und "speichert" sie im Storage und ruft die OnTick-Funktion im entsprechenden Child auf.

Beim Aufruf von GetStorageData sollte der DDE-Server angehalten werden und die Daten sollten in Storage gespeichert werden.

Wenn Selector2 den Callback aufruft, sollten der DDE-Server und die Speicheraufzeichnung ausgesetzt und der GetStorageData-Aufruf deaktiviert werden.

D.h. Selektor2 hat hohe Priorität, GetStorageData normale und Selektor1 niedrige.

Fragen:

Wie kann ich Selector1, Selector2 und GetStorageData zuverlässig synchronisieren?

Vielleicht gibt es konkrete Beispiele für eine solche Synchronisation (ich habe so etwas nie implementiert)?

 
prostotrader:

Die Idee, MT5 und Quick zu verbinden, wurde aufgegeben und nur Quick verwendet (DEE-Server + trans2quik.dll)

Ich erwäge, dieses Programm zu realisieren.

1. die sehr richtige Entscheidung, nur Quick zu verlassen.

2. Die Verbindung über DEE ist eine sehr umstrittene Lösung. Viele Leute sagen, dass DDE instabil ist, aber ich weiß es nicht.

Eine bessere und vielseitigere Lösung ist meiner Meinung nach eine Lua-DLL-Anwendung. Ich verwende diese Option. Das ist natürlich Sache des Eigentümers.

 
Yuriy Asaulenko:

1. Die Entscheidung, nur den Quick zu behalten, war richtig.

2. Die Kommunikation über die DDE ist eine sehr umstrittene Entscheidung. Viele Leute sagen, dass DDE instabil ist, aber ich weiß es nicht.

Eine bessere und vielseitigere Lösung ist meiner Meinung nach eine Lua-DLL-Anwendung. Ich verwende diese Option. Das hängt natürlich vom Gastgeber ab.

Ich schrieb vor langer Zeit einen DDE-Server für Quick - funktioniert reibungslos und schnell genug (nicht langsamer als Lua - DLL),

und es ist überhaupt nicht notwendig, zusätzlichen Code für Lua und DDL-Datenempfänger zu schreiben.

Hinzugefügt

Tatsächlich habe ich das im Diagramm gezeigte Programm bereits geschrieben (und es funktioniert), bin aber auf ein Synchronisierungsproblem gestoßen.

 
prostotrader:

Ich schrieb vor langer Zeit einen DDE-Server für Quick - funktioniert reibungslos und schnell genug (nicht langsamer als Lua - DLL),

und es ist überhaupt nicht nötig, zusätzlichen Code in Lua zu schreiben.

Da ich noch nie mit DDE gearbeitet habe, stellt sich die Frage: Wie wird DDE hergestellt? Ich denke, es ist notwendig, eine Tabelle mit Daten zu erstellen und sie dann über DDE laufen zu lassen.

Es gibt eine Verwechslung mit Ereignissen. Etwas hat sich geändert, und es sieht so aus, als ob die gesamte Tabelle an DDE übergeben wird. Oder liege ich da falsch?

Nehmen wir an, ich liege falsch. Wie kann man dann das Ereignis auf der Empfangsseite identifizieren?

Prostotrader:

Tatsächlich habe ich das im Diagramm gezeigte Programm bereits geschrieben (und es funktioniert), aber ich bin auf ein Synchronisierungsproblem gestoßen.

Was mit was synchronisieren?

Mit Lua wird dieses Problem durch Rückrufe von der DLL auf beliebige Daten gelöst.

 
Yuriy Asaulenko:

Da ich mich nicht mit der DDE beschäftigt habe, stellt sich die Frage, wie die DDE hergestellt wird. Es scheint, dass Sie eine Tabelle mit Daten erstellen und diese dann über DDE laufen lassen müssen.

Es gibt eine Verwechslung mit Ereignissen. Etwas hat sich geändert, und es sieht so aus, als ob die gesamte Tabelle an DDE übergeben wird. Oder liege ich da falsch?

Nehmen wir an, ich liege falsch. Wie kann man dann das Ereignis auf der Empfangsseite identifizieren?

In Quick wird die gewünschte Tabelle für die Ausgabe erzeugt.

Schließlich starten wir unsere eigene Anwendung mit dem DDE-Server und geben diese Tabelle über DDE aus.

Bei der ersten Ausgabe von Quick wird die gesamte Tabelle an DEE gesendet, dann nur die Zeile aus der Tabelle

in denen Änderungen aufgetreten sind.

In der Tabelle selbst (sie wird vollständig übertragen) gibt es (in meinem Fall) einen Werkzeugnamen - das ist der Bezeichner


 

Der DDE-Server selbst besteht aus ein paar Zeilen (ich habe ihn in Pascal, aber es gibt viele Beispiele im Internet in anderen Sprachen)

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.
 

Ein untergeordnetes Fenster wird durch den Werkzeugnamen erstellt (wie in MT 5)


 
Yuriy Asaulenko:

Womit?


Ich habe das Problem im Thema Diagramm beschrieben

 
prostotrader:

Ich habe das Problem in dem Diagrammthema beschrieben

Entschuldigung, das habe ich nicht bemerkt. Wenn ich es richtig verstanden habe:

Imho besteht die Lösung darin, ein DBMS alsSpeicher zu verwenden, beispielsweise MS SQL Server. Dies könnte eine Teillösung sein.

Die zweite Möglichkeit ist die Verwendung von Zwischenpuffern, wie z. B. "last in, first out". Nun, und die Trennung der Fäden.

Dann muss nichts mehr angehalten werden, alles wird einfach in Puffer geschrieben. Nun, und DBMS hat einen Mehrbenutzerzugang.

Ich wende all dies an, aber ich bin seit 6 Jahren nicht mehr mit Pascal befreundet.

PS Es heißt, man könne NET-Bibliotheken von Pascal aus verwenden. Für die Verwendung alsSpeicher könnte es sinnvoll sein,System.Data,System.Data.DataSet undSystem.Data.DataTable. Wenn ich mich recht erinnere, gab es kein Problem mit dem Mehrbenutzerzugriff inDataTable.

ZZY2 Jetzt versuche ich, SQLite als Datenbank zu verwenden, habe aber noch keine definitiven Ergebnisse. Und es ist sicherlich kein DBMS, aber in einer abgespeckten Form ist der Mehrbenutzerzugriff möglich, und es ist möglich, eine Datenbank im Speicher zu erstellen.

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 позволяет создавать…
 

Nein, ich muss nur 3 Threads synchronisieren (im Grunde einen Synchronizer schreiben), aber

Ich weiß nicht, wie das geht.