MT5 y trans2quik.dll - página 5

 

He abandonado por completo la idea de enlazar MT5 y Quick, sólo me he conformado con Quick (servidor DEE + trans2quik.dll).

Estoy considerando la posibilidad de realizar este programa.


Selector1 obtiene constantemente datos del servidor DDE y los "almacena" en Storage y llama a la función OnTick en el Child correspondiente.

Cuando se llama a GetStorageData, el servidor DDE debe estar en pausa y los datos deben ser almacenados en Storage.

Cuando Selector2 llama a la devolución de llamada, el servidor DDE y la grabación de almacenamiento deben suspenderse y la llamada GetStorageData debe desactivarse

Es decir, Selector2 tiene alta prioridad, GetStorageData normal, y Selector1 baja.

Preguntas:

¿Cómo puedo sincronizar Selector1, Selector2 y GetStorageData con gracia?

¿Quizás haya ejemplos concretos de dicha sincronización (nunca he implementado algo así)?

 
prostotrader:

Abandonamos la idea de enlazar MT5 y Quick, nos conformamos con Quick solamente (servidor DEE + trans2quik.dll)

Estoy considerando la posibilidad de realizar este programa.

1. la decisión muy acertada de dejar sólo a Quick.

2. La conexión mediante DEE es una solución muy controvertida. Mucha gente dice que el DDE es inestable, pero yo no lo sé.

En mi opinión, una solución mejor y más versátil, es una aplicación Lua-DLL. Yo uso esta opción. Por supuesto, depende del propietario.

 
Yuriy Asaulenko:

1. la decisión de mantener sólo el Quick fue la correcta.

2. La comunicación a través del DDE es una decisión muy controvertida. Mucha gente dice que el DDE es inestable, pero, no lo sé.

En mi opinión, una solución mejor y más versátil, es una aplicación Lua-DLL. Yo uso esta opción. Por supuesto, depende del anfitrión.

Escribí hace mucho tiempo un servidor DDE para Quick - funciona sin problemas y lo suficientemente rápido (no más lento que Lua - DLL),

y no es necesario escribir código adicional para Lua y el receptor de datos DDL en absoluto.

Añadido

De hecho, ya escribí el programa que se muestra en el diagrama (y funciona), pero me encontré con un problema de sincronización.

 
prostotrader:

Escribí hace mucho tiempo un servidor DDE para Quick - funciona sin problemas y lo suficientemente rápido (no más lento que Lua - DLL),

y no hay necesidad de escribir código adicional en Lua en absoluto.

Como no he hecho ningún DDE, la pregunta es: ¿cómo se hace el DDE? Creo que es necesario hacer una tabla con datos y luego pasarla por DDE.

Hay una confusión con los acontecimientos. Algo ha cambiado, y parece que toda la tabla se pasa al DDE. ¿O me equivoco?

Digamos que me equivoco. Entonces, ¿cómo identifico el evento en el lado receptor?

prostotrader:

De hecho, ya he escrito el programa que se muestra en el diagrama (y funciona), pero me encontré con un problema de sincronización.

¿Sincronizar qué con qué?

Con Lua, este problema se resuelve con devoluciones de llamada desde la DLL a datos arbitrarios.

 
Yuriy Asaulenko:

Como no he tratado con el DDE, la pregunta es ¿cómo se hace el DDE? Parece que hay que hacer una tabla con datos y luego pasarla por el DDE.

Hay una confusión con los acontecimientos. Algo ha cambiado, y parece que toda la tabla se pasa al DDE. ¿O me equivoco?

Digamos que me equivoco. Entonces, ¿cómo identificar el evento en el lado receptor?

En Quick, se genera la tabla requerida para la salida.

Por último, lanzamos nuestra propia aplicación con el servidor DDE y damos salida a esta tabla a través de DDE.

En la primera salida de Quick, toda la tabla se envía a DEE, luego sólo la fila de la tabla

en los que se produjeron cambios.

En la propia tabla (se transmite en su totalidad) hay (por ejemplo en mi caso) un nombre de herramienta - este es el identificador


 

El propio servidor DDE tiene unas pocas líneas (yo lo tengo en Pascal, pero hay muchos ejemplos en Internet en otros lenguajes)

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.
 

Se crea una ventana secundaria con el nombre de la herramienta (como en MT 5)


 
Yuriy Asaulenko:

¿Con qué?


He descrito el problema en el tema del diagrama

 
prostotrader:

He descrito el problema en el tema del diagrama

Lo siento, no me di cuenta. Si lo he entendido bien:

En mi opinión, la solución es utilizar un DBMS comoalmacenamiento, por ejemplo MS SQL Server. Esto puede ser una solución parcial.

La segunda es utilizar topes intermedios-recogidas, como último en entrar, primero en salir. Bueno, y separación de hilos.

Entonces no hay necesidad de parar nada, todo se escribe simplemente en los buffers. Bueno, y el DBMS tiene acceso multiusuario.

Aplico todo esto, pero no soy amigo de Pascal desde el 6.

PS Dicen que se pueden utilizar las bibliotecas de NET desde Pascal. Para utilizarlo comoalmacenamiento, podría tener sentido utilizarSystem.Data,System.Data.DataSet ySystem.Data.Tabla de datos. Si no recuerdo mal, no había ningún problema con el acceso multiusuario enDataTable.

ZZY2 Ahora estoy intentando usar SQLite como base de datos, pero aún no hay resultados definitivos. Y ciertamente no es un SGBD, pero en una forma despojada es posible el acceso multiusuario, y es posible crear una base de datos en 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, sólo necesito sincronizar 3 hilos (básicamente escribir un sincronizador), pero

No sé cómo.


Razón de la queja: