MT5 e trans2quik.dll - página 5

 

Abandonei completamente a idéia de ligar o MT5 ao Quick, só me instalei no Quick (servidor DEE + trans2quik.dll).

Estou considerando a realização deste programa.


O Selector1 recebe constantemente dados do servidor DDE e os "armazena" no Armazenamento e chama a função OnTick na criança correspondente.

Ao chamar GetStorageData, o servidor DDE deve ser pausado e os dados devem ser armazenados em Storage.

Quando o Selector2 chama de volta, o servidor DDE e a gravação de armazenamento devem ser suspensos e a chamada GetStorageData deve ser desativada.

Isto é, Selector2 tem alta prioridade, GetStorageData normal, e Selector1 baixa.

Perguntas:

Como posso sincronizar graciosamente Selector1, Selector2 e GetStorageData?

Talvez existam exemplos concretos de tal sincronização (eu nunca implementei tal coisa)?

 
prostotrader:

Abandonou a idéia de vincular o MT5 e o Quick, estabelecendo-se apenas no Quick (servidor DEE + trans2quik.dll)

Estou considerando a realização deste programa.

1. A decisão muito acertada de deixar apenas Quick.

2. A conexão via DEE é uma solução muito controversa. Muitas pessoas dizem que o DDE é instável, mas eu não sei.

Na minha opinião, uma solução melhor e mais versátil, é uma aplicação Lua-DLL. Eu uso esta opção. É claro que isso depende do proprietário.

 
Yuriy Asaulenko:

1. A decisão de manter apenas o Rápido foi a decisão certa.

2. A comunicação através do DDE é uma decisão muito controversa. Muitas pessoas dizem que o DDE é instável, mas, eu não sei.

Na minha opinião, uma solução melhor e mais versátil, é uma aplicação Lua-DLL. Eu uso esta opção. É claro, cabe ao anfitrião.

Eu escrevi há muito tempo um servidor DDE para Quick - funciona bem e rápido o suficiente (não mais lento do que Lua - DLL),

e não é necessário escrever código adicional para Lua e receptor de dados DDL de forma alguma.

Adicionado

De fato, eu já escrevi o programa mostrado no diagrama (e ele funciona), mas encontrei um problema de sincronização.

 
prostotrader:

Eu escrevi há muito tempo um servidor DDE para Quick - funciona bem e rápido o suficiente (não mais lento do que Lua - DLL),

e não há nenhuma necessidade de escrever código adicional em Lua.

Como eu não fiz nenhum DDE, a questão é - como o DDE é feito? Acho que existe a necessidade de fazer uma tabela com dados, e depois executá-la através do DDE.

Há um problema com os eventos. Algo mudou, e parece que a mesa inteira foi passada para o DDE. Ou eu estou errado?

Digamos que eu estou errado. Então, como identificar o evento no lado receptor?

prostotrader:

De fato, eu já escrevi o programa mostrado no diagrama (e ele funciona), mas encontrei um problema de sincronização.

Sincronizar o quê com o quê?

Com Lua, esta questão é resolvida através de chamadas de retorno da DLL para dados arbitrários.

 
Yuriy Asaulenko:

Como eu ainda não lidei com o DDE, a questão é como o DDE é feito? Parece que você tem que fazer uma tabela com dados e, em seguida, executá-la através do DDE.

Há uma confusão com os acontecimentos. Algo mudou, e parece que a mesa inteira foi passada para o DDE. Ou eu estou errado?

Digamos que eu estou errado. Então como identificar o evento no lado receptor?

Em Quick, a tabela necessária é gerada para a saída.

Finalmente, nós lançamos nossa própria aplicação com o servidor DDE e emitimos esta tabela via DDE.

Na primeira saída de Quick, toda a tabela é enviada para DEE, depois somente a linha da tabela

em que ocorreram mudanças.

Na própria tabela (é transmitida por completo) há (por exemplo, no meu caso) um nome de ferramenta - este é o identificador


 

O próprio servidor DDE tem algumas linhas (tenho-o em Pascal, mas há muitos exemplos na Internet em outros idiomas)

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.
 

Uma janela para crianças é criada pelo nome da ferramenta (como em MT 5)


 
Yuriy Asaulenko:

Com o que?


Eu descrevi o problema no tópico do diagrama

 
prostotrader:

Eu descrevi o problema no tópico do diagrama

Desculpe, não percebi. Se eu entendi corretamente:

Imho, a solução é usar um SGBD comoarmazenamento, digamos MS SQL Server. Esta pode ser uma solução parcial.

A segunda é utilizar coleções intermediárias de amortecedores, como o último a entrar, primeiro a sair. Bem, e a separação dos fios.

Então não há necessidade de parar nada, tudo é simplesmente escrito em amortecedores. Bem, e o SGBD tem acesso para múltiplos usuários.

Eu aplico tudo isso, mas não sou amigo de Pascal desde 6.

PS Dizem que você pode usar as bibliotecas NET de Pascal. Para usar comoarmazenamento, pode fazer sentido usarSystem.Data,System.Data.DataSet eSystem.Data.DataTable. Se bem me lembro, não houve problema com o acesso de múltiplos usuários naDataTable.

ZZY2 Agora estou tentando usar o SQLite como um banco de dados, mas nenhum resultado definitivo ainda. E certamente não é um SGBD, mas em uma forma descascada é possível o acesso de múltiplos usuários, e é possível criar um banco de dados em memória.

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

Não, eu só preciso sincronizar 3 fios (basicamente escrever um Sincronizador), mas

Eu não sei como.