MT5 and trans2quik.dll - page 5

 

I have completely abandoned the idea of linking MT5 and Quick, I have only settled on Quick (DEE server + trans2quik.dll).

I am considering realization of this program.


Selector1 constantly gets data from DDE server and "stores" it in Storage and calls OnTick function in corresponding Child.

When calling GetStorageData, the DDE server should be paused and the data should be stored in Storage.

When Selector2 calls callback, DDE server and Storage recording should be suspended and GetStorageData call should be disabled

I.e. Selector2 has high priority, GetStorageData normal, and Selector1 low.

Questions:

How can I synchronize Selector1, Selector2 and GetStorageData gracefully?

Maybe there are concrete examples of such synchronization (I've never implemented such a thing)?

 
prostotrader:

Abandoned the idea of linking MT5 and Quick, settled on Quick only (DEE server + trans2quik.dll)

I am considering realization of this program.

1. the very right decision to leave only Quick.

2. Connection via DEE is a very controversial solution. Many people say that DDE is unstable, but I don't know.

In my opinion, a better and more versatile solution, is a Lua-DLL application. I use this option. Of course, it's up to the owner.

 
Yuriy Asaulenko:

1. the decision to keep only the Quick was the right one.

2. Communication via DDE is a very controversial decision. Many people say that DDE is unstable, but, I don't know.

In my opinion, a better and more versatile solution, is a Lua-DLL application. I use this option. Of course, it's up to the host.

I wrote a long time ago a DDE server for Quick - works smoothly and fast enough (not slower than Lua - DLL),

and it's not necessary to write additional code for Lua and DDL data receiver at all.

Added

As a matter of fact, I already wrote the program shown in the diagram (and it works), but ran into a synchronization problem.

 
prostotrader:

I wrote a long time ago a DDE server for Quick - works smoothly and fast enough (not slower than Lua - DLL),

and there is no need to write additional code in Lua at all.

Since I haven't done any DDE, the question is - how is DDE made? I think there is a need to make a table with data, and then run it through DDE.

There is a confusion with events. Something has changed, and it looks like the whole table is passed to the DDE. Or am I wrong?

Let's say I'm wrong. Then how to identify the event on the receiving side?

prostotrader:

As a matter of fact, I've already written the program shown in the diagram (and it works), but I ran into a synchronization problem.

Synchronizing what with what?

With Lua, this issue is solved by callbacks from the DLL to arbitrary data.

 
Yuriy Asaulenko:

Since I haven't dealt with the DDE, the question is how is the DDE made? It seems that you have to make a table with data, and then run it through the DDE.

There is a confusion with events. Something has changed, and it looks like the whole table is passed to the DDE. Or am I wrong?

Let's say I'm wrong. Then how to identify the event on the receiving side?

In Quick, the required table is generated for output.

Finally, we launch our own application with DDE server and output this table via DDE.

At first output from Quick, the whole table is sent to DEE, then only the row from the table

in which changes occurred.

In the table itself (it is transmitted in full) there is (for example in my case) a tool name - this is the identifier


 

The DDE server itself has a few lines (I have it in Pascal, but there are many examples on the Internet in other languages)

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.
 

A child window is created by the tool name (as in MT 5)


 
Yuriy Asaulenko:

With what?


I described the problem in the diagram topic

 
prostotrader:

I described the problem in the diagram topic

Sorry, didn't realise. If I understood it correctly:

Imho, the solution is to use a DBMS asStorage, say MS SQL Server. This may be a partial solution.

The second one is to use intermediate buffers-collections, like last in, first out. Well, and separation of threads.

Then there is no need to stop anything, everything is simply written into buffers. Well, and DBMS has multi-user access.

I apply all this, but I'm not friends with Pascal since 6.

PS They say you can use NET libraries from Pascal. To use asStorage, it might make sense to useSystem.Data,System.Data.DataSet andSystem.Data.DataTable. As I recall, there was no problem with multi-user access inDataTable.

ZZY2 Now I'm trying to use SQLite as a database, but no definite results yet. And it's certainly not a DBMS, but in a stripped-down form multi-user access is possible, and it's possible to create a database in memory.

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, I just need to synchronise 3 threads (basically write a Synchronizer), but

I don't know how.