MT5 et trans2quik.dll - page 5

 

J'ai complètement abandonné l'idée de lier MT5 et Quick, je me suis contenté de Quick (serveur DEE + trans2quik.dll).

J'envisage de réaliser ce programme.


Selector1 reçoit constamment des données du serveur DDE et les "stocke" dans Storage et appelle la fonction OnTick dans Child correspondant.

Lorsque vous appelez GetStorageData, le serveur DDE doit être mis en pause et les données doivent être stockées dans Storage.

Lorsque Selector2 appelle le callback, le serveur DDE et l'enregistrement du stockage doivent être suspendus et l'appel GetStorageData doit être désactivé.

C'est-à-dire que Selector2 a une priorité élevée, GetStorageData une priorité normale, et Selector1 une priorité faible.

Questions :

Comment synchroniser Selector1, Selector2 et GetStorageData de manière gracieuse ?

Peut-être existe-t-il des exemples concrets d'une telle synchronisation (je n'ai jamais implémenté une telle chose) ?

 
prostotrader:

Abandon de l'idée de lier MT5 et Quick, choix de Quick uniquement (serveur DEE + trans2quik.dll)

J'envisage de réaliser ce programme.

1. la très bonne décision de ne laisser que Quick.

2. La connexion via DEE est une solution très controversée. Beaucoup disent que DDE est instable, mais je ne sais pas.

À mon avis, une solution meilleure et plus polyvalente est une application Lua-DLL. J'utilise cette option. Bien sûr, c'est au propriétaire de décider.

 
Yuriy Asaulenko:

1. la décision de ne garder que le Quick était la bonne.

2. La communication via DDE est une décision très controversée. Beaucoup de gens disent que DDE est instable, mais, je ne sais pas.

À mon avis, une solution meilleure et plus polyvalente est une application Lua-DLL. J'utilise cette option. Bien sûr, c'est à l'hôte de décider.

J'ai écrit il y a longtemps un serveur DDE pour Quick - il fonctionne sans problème et assez rapidement (pas plus lent que Lua - DLL),

et il n'est pas du tout nécessaire d'écrire du code supplémentaire pour le récepteur de données Lua et DDL.

Ajouté

En fait, j'ai déjà écrit le programme présenté dans le diagramme (et il fonctionne), mais j'ai rencontré un problème de synchronisation.

 
prostotrader:

J'ai écrit il y a longtemps un serveur DDE pour Quick - il fonctionne sans problème et assez rapidement (pas plus lent que Lua - DLL),

et il n'est pas du tout nécessaire d'écrire du code supplémentaire en Lua.

Comme je n'ai pas fait de DDE, la question est la suivante : comment fait-on de la DDE ? Je pense qu'il est nécessaire de créer un tableau avec des données, puis de le faire passer par DDE.

Il y a une confusion avec les événements. Quelque chose a changé, et il semble que la table entière soit passée au DDE. Ou ai-je tort ?

Disons que j'ai tort. Alors comment identifier l'événement du côté récepteur ?

prostotrader:

En fait, j'ai déjà écrit le programme présenté dans le diagramme (et il fonctionne), mais j'ai rencontré un problème de synchronisation.

Synchroniser quoi avec quoi ?

Avec Lua, ce problème est résolu par des callbacks de la DLL vers des données arbitraires.

 
Yuriy Asaulenko:

Comme je n'ai pas eu affaire à la DDE, la question est de savoir comment elle est fabriquée. Il semble que vous deviez créer un tableau avec des données, puis le faire passer par le DDE.

Il y a une confusion avec les événements. Quelque chose a changé, et il semble que la table entière soit passée au DDE. Ou ai-je tort ?

Disons que j'ai tort. Alors comment identifier l'événement du côté récepteur ?

Dans Quick, la table requise est générée pour la sortie.

Enfin, nous lançons notre propre application avec le serveur DDE et sortons cette table via DDE.

A la première sortie de Quick, la table entière est envoyée à DEE, puis seulement la ligne de la table

dans lesquels des changements ont eu lieu.

Dans le tableau lui-même (il est transmis dans son intégralité) il y a (par exemple dans mon cas) un nom d'outil - c'est l'identifiant


 

Le serveur DDE lui-même a quelques lignes (je l'ai en Pascal, mais il y a de nombreux exemples sur Internet dans d'autres langages)

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.
 

Une fenêtre enfant est créée par le nom de l'outil (comme dans MT 5)


 
Yuriy Asaulenko:

Avec quoi ?


J'ai décrit le problème dans le sujet du diagramme

 
prostotrader:

J'ai décrit le problème dans le sujet du diagramme

Désolé, je n'avais pas réalisé. Si j'ai bien compris :

À mon avis, la solution consiste à utiliser un SGBD commestockage, par exemple MS SQL Server. Il peut s'agir d'une solution partielle.

La seconde consiste à utiliser des buffers-collections intermédiaires, comme le dernier entré, premier sorti. Eh bien, et la séparation des fils.

Il n'est alors pas nécessaire d'arrêter quoi que ce soit, tout est simplement écrit dans les tampons. Eh bien, et le SGBD a un accès multi-utilisateurs.

J'applique tout ça, mais je ne suis pas ami avec Pascal depuis 6 ans.

PS Ils disent que vous pouvez utiliser les bibliothèques NET à partir de Pascal. Pour l'utiliser commestockage, il peut être judicieux d'utiliserSystem.Data,System.Data.DataSet etSystem.Data.DataTable. Si je me souviens bien, il n'y avait aucun problème d'accès multi-utilisateurs dansDataTable.

ZZY2 Maintenant, j'essaie d'utiliser SQLite comme base de données, mais pas encore de résultats définitifs. Et ce n'est certainement pas un SGBD, mais sous une forme dépouillée, l'accès multi-utilisateurs est possible, et il est possible de créer une base de données en mémoire.

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

Non, j'ai juste besoin de synchroniser 3 threads (en fait, écrire un Synchronizer), mais...

Je ne sais pas comment.