<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title><![CDATA[QUIK -> DDE &mdash; Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
		<link>https://quik2dde.ru/viewtopic.php?id=301</link>
		<atom:link href="https://quik2dde.ru/extern.php?action=feed&amp;tid=301&amp;type=rss" rel="self" type="application/rss+xml" />
		<description><![CDATA[Недавние сообщения в теме «Получение данных из quik через "экспорт в системы тех.анализа..."».]]></description>
		<lastBuildDate>Fri, 01 Nov 2019 14:34:42 +0000</lastBuildDate>
		<generator>PunBB</generator>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2470#p2470</link>
			<description><![CDATA[<p>вот еще. функция для чтения структур такая:<br /></p><div class="codebox"><pre><code>function tQUIKTradePipeReader.ReadStructure(hPipe: THandle; atag: longint; adesc: pAnsiChar; abuf: pAnsiChar; abufsize: longint): boolean;
const errormsg : ansistring = &#039;error receiving struct, type &quot;%s&quot;&#039;;
var   len      : longint;
      tmpi     : longint;
      tmpi64   : int64;
      stop     : boolean;
  function getid(var desc: pAnsiChar): ansichar;
  begin result:= upcase(desc^); inc(desc); end;
  function getlen(var desc: pAnsiChar): longint;
  begin
    result:= 0;
    while (desc^ in [&#039;0&#039;..&#039;9&#039;]) do begin
      result:= result * 10 + (byte(desc^) - byte(&#039;0&#039;));
      inc(desc);
    end;
  end;
begin
  result:= false;
  if not ((atag = -1) or (ReadInt(hPipe) = atag)) then raise Exception.Create(&#039;struct tag missmatch&#039;);
  if assigned(adesc) then begin
    stop:= false;
    while (not stop) do begin
      case getid(adesc) of
        #0  : begin
                result:= (abufsize = 0);
                stop:= true;
              end;
        &#039;I&#039; : begin
                len:= getlen(adesc);
                if (abufsize &lt; len) or not SafeReadPipe(hPipe, abuf, len) then raise Exception.CreateFmt(errormsg, [&#039;I&#039;]);
                inc(abuf, len);
                dec(abufsize, len);
              end;
        &#039;D&#039; : begin
                if (abufsize &lt; 8) or not SafeReadPipe(hPipe, abuf, sizeof(double)) then raise Exception.CreateFmt(errormsg, [&#039;D&#039;]);
                inc(abuf, sizeof(double));
                dec(abufsize, sizeof(double));
              end;
        &#039;S&#039; : begin
                len:= getlen(adesc) + 1;
                if (abufsize &lt; len) then raise Exception.CreateFmt(errormsg, [&#039;S&#039;]);
                if not SafeReadPipe(hPipe, @tmpi, sizeof(tmpi)) then raise Exception.CreateFmt(errormsg, [&#039;S&#039;]);
                if (tmpi &gt;= 0) and (tmpi &lt;= 255) then begin
                  byte(abuf^):= tmpi;
                  if (tmpi &gt; 0) then
                    if not SafeReadPipe(hPipe, abuf + 1, tmpi) then raise Exception.CreateFmt(errormsg, [&#039;S&#039;]);
                end else Exception.CreateFmt(errormsg, [&#039;S&#039;]);
                inc(abuf, len);
                dec(abufsize, len);
              end;
        &#039;-&#039; : begin
                len:= getlen(adesc);
                while (len &gt; 0) do begin
                  if not SafeReadPipe(hPipe, @tmpi64, min(len, sizeof(tmpi64))) then raise Exception.CreateFmt(errormsg, [&#039;-&#039;]);
                  dec(len, sizeof(tmpi64));
                end;
              end;
        &#039;+&#039; : begin
                len:= getlen(adesc);
                if(abufsize &lt; len) then raise Exception.CreateFmt(errormsg, [&#039;+&#039;]);
                inc(abuf, len);
                dec(abufsize, len);
              end;
        &#039;?&#039; : begin
                if not SafeReadPipe(hPipe, @tmpi, sizeof(tmpi)) then raise Exception.CreateFmt(errormsg, [&#039;?&#039;]);
                if (tmpi &gt;= 0) then begin
                  while (tmpi &gt; 0) do begin
                    if not SafeReadPipe(hPipe, @tmpi64, min(tmpi, sizeof(tmpi64))) then raise Exception.CreateFmt(errormsg, [&#039;?&#039;]);
                    dec(tmpi, sizeof(tmpi64));
                  end;
                end else raise Exception.CreateFmt(errormsg, [&#039;?&#039;]);
              end;
        else  raise Exception.CreateFmt(errormsg, [&#039;unknown&#039;]);
      end;
    end;
  end;
end;</code></pre></div><p>структуры и их маппинг на принимаемые данные:<br /></p><div class="codebox"><pre><code>type  tSecBoard            = string[8];
      tSecCode             = string[20];
      tCurrCode            = string[6];
      tAccount             = string[40];
      tBrokerSymbolName    = string[128];
      tQUIKShortName       = string[30];


const QUIKOrderDesc : pAnsiChar = &#039;I4S8S20DI4???S40DI4I4D-8-8I4S40-8?-4?-4-8-4-4-4I8I8&#039;;

type  pQUIKOrder    = ^tQUIKOrder;
      tQUIKOrder    = packed record
        mode        : longint;
        secboard    : tSecBoard;
        seccode     : tSecCode;
        orderno     : double;
        flags       : longint;  // Flags: buysell, status 1E = 00011110
        account     : tAccount;
        price       : double;
        quantity    : longint;
        balance     : longint;
        value       : double;
        trsid       : longint;
        clientcode  : tAccount;
        ordertime   : TFileTime;
        wdtime      : TFileTime;
      end;

const QUIKTradeDesc : pAnsiChar = &#039;I4S8S20DD???S40DI4D-8-8??I4-8-8S40-8-8-8-4-8-8-8-4-8-8-8-8I4?S6??-4I8-4&#039;;

type  pQUIKTrade    = ^tQUIKTrade;
      tQUIKTrade    = packed record
        mode        : longint;
        secboard    : tSecBoard;
        seccode     : tSecCode;
        tradeno     : double;
        orderno     : double;
        account     : tAccount;
        price       : double;
        quantity    : longint;
        value       : double;
        flags       : longint;
        clientcode  : tAccount;
        settledate  : longint;
        curcode     : tCurrCode;
        tradetime   : TFileTime;
      end;

const QUIKTrsReply  : pAnsiChar = &#039;I4I4I4I4DS255&#039;;

type  pQUIKTrsReply = ^tQUIKTrsReply;
      tQUIKTrsReply = packed record
        trs_result  : longint;
        error_code  : longint;
        internal_id : longint;
        reply_code  : longint;
        orderno     : double;
        errortext   : shortstring;
      end;</code></pre></div><p>чтение из пайпа и tag:<br /></p><div class="codebox"><pre><code>заявка:
ReadStructure(hpipes[pipe_out], 100, QUIKOrderDesc, @order, sizeof(order));

сделка:
ReadStructure(hpipes[pipe_out], 200, QUIKTradeDesc, @trade, sizeof(trade));

transaction_result:
ReadStructure(hpipes[pipe_out], -1, QUIKTrsReply, @trsreply, sizeof(trsreply));</code></pre></div><p>надеюсь, это не очень сложно для понимания. смысл в том, чтобы прочитать данные из пайпа в struct по описанию. &quot;S&quot; - это &quot;паскалевские&quot; короткие строки shortstring длиной до 255 символов, нулевой символ - длина. таким образом, sizeof(tSecBoard) == 9; чтение в нее определяется описанием &quot;S8&quot;. при этом в пайпе ожидается &lt;int len&gt;&lt;[len bytes]&gt;. если при чтении в такую строку считалось len &gt; 8, то поднимаем exception.</p>]]></description>
			<author><![CDATA[null@example.com (toxa)]]></author>
			<pubDate>Fri, 01 Nov 2019 14:34:42 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2470#p2470</guid>
		</item>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2468#p2468</link>
			<description><![CDATA[<p>для исследования trans2quik я наваял небольшой патч, который дампит всю работу с пайпами. вот, выложил на dropbox: [url]https://www.dropbox.com/s/crg8w3fp5y3m9q9/api_tester_patched.zip?dl=0[/url] </p><p>это стандартное квиковое приложение, просто я в их родной trans2quik.dll внедрил импорт своей dll, которая перехватывает нужные вызовы и пишет лог. соединяемся с квиком, делаем действия, потом анализируем лог-файл. к счастью, trans2quik.dll написан по рабоче-крестьянски, на каждое поле делается readfile() и в логе видны отдельные поля (поля переменной длины вычитываются в два вызова, первый readfile - это size, второй - содержимое).</p><p>общий смысл протокола таков: после начального обмена хендшейком с неким &quot;magic number&quot;, обмен идет так: <br />&lt;command_id&gt;&lt;command_data&gt;<br />где command_id - это int, а &lt;command_data&gt; это некий набор из int, double и &lt;string&gt;, где string - это &lt;длина&gt;&lt;chars[длина]&gt;</p><p>команды такие:<br />0x02 - handshake result<br />0x0A - connection status<br />0x11 - заявка<br />0x12 - сделка<br />0x0B - orders request<br />0x0D - start orders<br />0x0E - trades request<br />0x10 - start trades<br />0x06 - transaction reply</p><p>ps: да, все это работает через два пайпа - один только на чтение, другой только на запись. программирование &quot;от сохи&quot;, что называется.</p>]]></description>
			<author><![CDATA[null@example.com (toxa)]]></author>
			<pubDate>Fri, 01 Nov 2019 13:29:05 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2468#p2468</guid>
		</item>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2467#p2467</link>
			<description><![CDATA[<div class="quotebox"><cite>toxa пишет:</cite><blockquote><p>да было б чо. как сюда файлы аттачить?</p></blockquote></div><p>Просто выложите файлы куда-нибудь, на файлообменник и ссылку здесь оставьте, админ перенесёт их на местный сервер, чтобы не потерялось.</p>]]></description>
			<author><![CDATA[null@example.com (swerg)]]></author>
			<pubDate>Fri, 01 Nov 2019 12:01:26 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2467#p2467</guid>
		</item>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2466#p2466</link>
			<description><![CDATA[<p>у меня и разобранный протокол trans2quik есть, в принципе, но там надо из проекта выдирать, а написать доку я не удосужился. если есть запал ковыряться - могу помочь.</p>]]></description>
			<author><![CDATA[null@example.com (toxa)]]></author>
			<pubDate>Fri, 01 Nov 2019 11:09:57 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2466#p2466</guid>
		</item>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2465#p2465</link>
			<description><![CDATA[<p>да было б чо. как сюда файлы аттачить?</p>]]></description>
			<author><![CDATA[null@example.com (toxa)]]></author>
			<pubDate>Fri, 01 Nov 2019 10:58:57 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2465#p2465</guid>
		</item>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2464#p2464</link>
			<description><![CDATA[<p>Спасибо за такую шикарную информацию!</p>]]></description>
			<author><![CDATA[null@example.com (swerg)]]></author>
			<pubDate>Fri, 01 Nov 2019 10:46:43 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2464#p2464</guid>
		</item>
		<item>
			<title><![CDATA[Re: Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2463#p2463</link>
			<description><![CDATA[<p>Z:\quik\wld_export&gt;listquikpipes.exe<br />Start export to wealthlab from QUIK or press Ctrl-C to exit...<br />13:14:49.461 ADD: QUIK_RIZ9_1MINUTES<br />13:14:49.461 ADD: QUIK_RIZ9_1MINUTES_EXISTING_DATA<br />13:14:49.461 ADD: QUIK_MSNG_5MINUTES<br />13:14:49.461 ADD: QUIK_MSNG_5MINUTES_EXISTING_DATA<br />^С</p><p>далее:<br />logpipedata.exe RIZ9 1MINUTES_EXISTING_DATA</p><p>или:<br />logpipedata.exe RIZ9 1MINUTES</p><p>тогда программа не завершится, а будет идти онлайн.</p>]]></description>
			<author><![CDATA[null@example.com (toxa)]]></author>
			<pubDate>Fri, 01 Nov 2019 10:16:47 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2463#p2463</guid>
		</item>
		<item>
			<title><![CDATA[Получение данных из quik через "экспорт в системы тех.анализа..."]]></title>
			<link>https://quik2dde.ru/viewtopic.php?pid=2462#p2462</link>
			<description><![CDATA[<p>Это довольно прикольная фича, она позволяет получать &quot;свечные&quot; данные из квика. Экспорт идет через named pipes, обеспечивает приемлемую скорость и довольно прост. Что-то я не разобрался, можно ли тут аттачить архивы, но вот исходники двух примеров - один из них мониторит появляющиеся в системе квиковые пайпы, другой - дампит данные в консоль. Чтобы нужные пайпы появились, в квике нужно выбрать &quot;экспорт в системы тех.анализа...&quot;, далее добавить вывод нужной бумаги с нужным таймфреймом в велслаб, потом запустить экспорт.</p><p>Оба проекта собираются fpc и работают, только для первого нужно взять модуль masks из дельфи или лазаруса, так как в дистрибутив fpc он не входит.</p><p>Монитор:<br /></p><div class="codebox"><pre><code>{$APPTYPE CONSOLE}
uses Windows, Classes, SysUtils, Masks;

const pipes_prefix = &#039;\\.\pipe\&#039;;

type  tPipeList = class(tStringList)
      private
        FNotify : THandle;
        FMask   : string;
      public
        constructor Create;
        destructor  Destroy; override;
        procedure   RefreshDirectoryList;
        function    WaitForChanges(aTimeOut: longint): boolean;
        procedure   AfterAddObject(const aobject: string); virtual;
        procedure   BeforeDeleteObject(const aobject: string); virtual;
        property    Mask: string read FMask write FMask;
      end;

var   FPipes  : tPipeList;
      FExit   : boolean = false;

function CtrlHandler(CtrlType: Longint): bool; stdcall;
begin FExit:= true; result:= true; end;

{ tPipeList }

constructor tPipeList.Create;
begin
  inherited Create;
  Sorted:= true;
  FNotify:= FindFirstChangeNotification(pipes_prefix, false, FILE_NOTIFY_CHANGE_FILE_NAME);
end;

destructor tPipeList.Destroy;
begin
  if (FNotify &lt;&gt; INVALID_HANDLE_VALUE) then FindCloseChangeNotification(FNotify);
  inherited destroy;
end;

procedure tPipeList.RefreshDirectoryList;
const seq : longint = 0;
var   idx : longint;
      sr  : tSearchRec;
begin
  inc(seq);
  try
    if findfirst(format(&#039;%s*&#039;, [pipes_prefix]), faAnyFile, sr) = 0 then
      repeat
        if MatchesMask(sr.name, FMask) then begin
          idx:= IndexOf(sr.name);
          if (idx &lt; 0) then begin
            AddObject(sr.name, pointer(seq));
            AfterAddObject(sr.name);
          end else Objects[idx]:= pointer(seq);
        end;
      until (findnext(sr) &lt;&gt; 0);
  finally findclose(sr); end;
  for idx:= count - 1 downto 0 do
    if (Objects[idx] &lt;&gt; pointer(seq)) then begin
      BeforeDeleteObject(Strings[idx]);
      delete(idx);
    end;
end;

function tPipeList.WaitForChanges(aTimeOut: longint): boolean;
begin
  result:= (FNotify &lt;&gt; INVALID_HANDLE_VALUE) and (WaitForSingleObject(FNotify, aTimeOut) = WAIT_OBJECT_0);
  if result then FindNextChangeNotification(FNotify);
end;

procedure tPipeList.AfterAddObject(const aobject: string);
begin
  writeln(formatdatetime(&#039;HH:NN:SS.ZZZ&#039;, now), &#039; ADD: &#039;, aobject);
end;

procedure tPipeList.BeforeDeleteObject(const aobject: string);
begin
  writeln(formatdatetime(&#039;HH:NN:SS.ZZZ&#039;, now), &#039; DEL: &#039;, aobject);
end;


begin
  SetConsoleCtrlHandler(@CtrlHandler, true);
  
  writeln(&#039;Start export to wealthlab from QUIK or press Ctrl-C to exit...&#039;);

  FPipes:= tPipeList.Create;
  FPipes.Mask:= &#039;QUIK_*&#039;;
  FPipes.RefreshDirectoryList;

  while not FExit do begin
    if FPipes.WaitForChanges(1000) then begin
      FPipes.RefreshDirectoryList;
    end;
  end;

  FreeAndNil(FPipes);
  readln;
end.</code></pre></div><p>Дампер:<br /></p><div class="codebox"><pre><code>{$APPTYPE CONSOLE}
uses Windows, SysUtils;

var   fh      : longint;
      buf     : array[0..1024] of ansichar;

function SafeReadPipe(fh: longint; buf: pAnsiChar; toread: longint): boolean;
var idx  : longint;
    read : DWORD;
begin
  idx:= 0;
  if assigned(buf) then
    while (toread &gt; 0) and ReadFile(fh, buf[idx], toread, read, nil) do begin
      dec(toread, read);
      inc(idx, read);
    end;
  result:= (toread = 0);
end;

function SafeReadHeader(fh: longint; var id: word): boolean;
var tmp: word;
begin
  if SafeReadPipe(fh, @tmp, 2) and (tmp = 1) then result:= SafeReadPipe(fh, @id, 2) else result:= false;
end;

var id, tmp : word;
    tmp_i   : longint;
    tmp_d   : double;
    tmp_i64 : int64;
    strbuf  : ansistring;
    st      : TSystemTime;
begin
  fillchar(buf, sizeof(buf), 0);

  strbuf:= format(&#039;\\.\pipe\QUIK_%s_%s&#039;, [paramstr(1), paramstr(2)]);

  if not fileexists(strbuf) then begin
    writeln(&#039;WARNING (pipe not found): &#039;, strbuf);
    writeln(&#039;usage: logpipedata.exe &lt;TICKER&gt; &lt;TIMEFRAME&gt;&#039;#$0d#$0a&#039;example: logpipedata.exe LKOH TICKS&#039;#$0d#$0a);
  end;

  WaitNamedPipe(PAnsiChar(strbuf), 1000);

  fh:= longint(CreateFile(PAnsiChar(strbuf),
                          GENERIC_READ,
                          FILE_SHARE_READ,
                          nil,
                          OPEN_EXISTING,
                          0,
                          0));

  if (fh &lt;&gt; -1) then begin

    while SafeReadPipe(fh, @id, 2) do begin
      case id of
        1       : SafeReadPipe(fh, @tmp, 2);
        2       : if SafeReadPipe(fh, @tmp, 2) then begin
                    setlength(strbuf, tmp);
                    SafeReadPipe(fh, @strbuf[1], tmp);
                    writeln(&#039;instrument: &#039;, strbuf);
                  end;
        6, 7, 9 : SafeReadPipe(fh, @tmp_i, 4);
        8       : begin
                    SafeReadPipe(fh, @tmp_i64, 8);
                    FileTimeToSystemTime(tFileTime(tmp_i64), st);
                    write(formatdatetime(&#039;DD-MM-YYYY HH:NN:SS &#039;, SystemTimeToDateTime(st)));
                    // open
                    SafeReadPipe(fh, @tmp_d, 8);
                    write(tmp_d:10:6, &#039; &#039;);
                    // high
                    SafeReadPipe(fh, @tmp_d, 8);
                    write(tmp_d:10:6, &#039; &#039;);
                    // low
                    SafeReadPipe(fh, @tmp_d, 8);
                    write(tmp_d:10:6, &#039; &#039;);
                    // close
                    SafeReadPipe(fh, @tmp_d, 8);
                    write(tmp_d:10:6, &#039; &#039;);
                    // volume
                    SafeReadPipe(fh, @tmp_d, 8);
                    writeln(tmp_d:10:6, &#039; &#039;);
                  end;
      end;
    end;

    CloseHandle(fh);
  end else writeln(&#039;ERROR Opening: &#039;, strbuf, &#039; error: &#039;, GetLastError);
end.</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (toxa)]]></author>
			<pubDate>Fri, 01 Nov 2019 09:51:23 +0000</pubDate>
			<guid>https://quik2dde.ru/viewtopic.php?pid=2462#p2462</guid>
		</item>
	</channel>
</rss>
