finsight / QUIKSharp

QUIK# (QUIK Sharp) is the QUIK Lua interface ported to .NET.
Other
232 stars 135 forks source link

[Lua] Ряд дополнений и исправлений #248

Closed avently closed 4 years ago

avently commented 4 years ago

Здравствуйте. Предлагаю внести некоторые изменения в код lua скриптов. Привожу код, который можно для некоторых идей использовать, если хотите:

  1. В функции getTradeAccounts нет проверки на то, пустой ли торговый аккаунт или нет. В итоге для брокера Промсвязьбанка она отправляет около двух тысяч пустых торговых аккаунтов. Да, для чего-то их наделали две тысячи. Выглядят элементы в массиве в таком виде:
{"description":"","class_codes":"","fullcoveredsell":0,"main_trdaccid":"","bankid_tplus":"","trdacc_type":0,"firmid":"SPBFUT","depunitid":"","bankid_t0":"","firmuse":0,"status":0,"depaccid":"","trdaccid":"SPBFUT00JK9","bank_acc_id":""}

То есть все пусто, кроме фирмы и названия торгового аккаунта. В квике при выставлении заявки такие аккаунты не отображаются. И не мудрено - классов то не указано. Предлагаю фильтровать такие строки на наличию классов. Если их нет, то не добавлять в ответ клиенту сокета. Типа вот так:

        if trade_accounts.class_codes ~= "" then
            table.insert(ListAccounts,trade_accounts)
        end
  1. На данный момент нет никакой возможности, насколько я могу судить, определить код клиента и связать его с торговым аккаунтом. То есть не хватает нескольких функций, которые эти данные предоставят. А без них я не могу даже баланс проверить, не говоря уже о выставлении заявок. Мне нужно предоставить юзеру на выбор связки торговый счет -> Код клиента. Вот такой код будет полезен:
    
    function qsfunctions.getMoneyLimits(msg)
    local limits={}
    for i=0,getNumberOf("money_limits")-1 do
        local limit = getItem("money_limits",i)
        table.insert(limits,limit)
    end
     msg.data=limits
    return msg
    end

function qsfunctions.getFuturesClientLimits(msg) local limits={} for i=0,getNumberOf("futures_client_limits")-1 do local limit = getItem("futures_client_limits",i) table.insert(limits,limit) end msg.data=limits return msg end

И там, и там можно найти полезную информацию: первая функция дает client_code и firmid, а вторая дает торговый счет и firmid. Основываясь на этих данных можно для того или иного инструмента знать список кодов клиента и торговых счетов, потому как для всех инструментов известен класс, а для всех классов известен firmid.

3. В функции `getTradeAccount(msg)` есть недоработка. Там используется `string.find(trade_account.class_codes,msg.data,1,1)`. Это чревато тем, что, как в моем случае, будет найдет неверный торговый аккаунт. 
Пример. У меня есть в списке торговых счетов счет, который имеет такую структуру (и он самый первый в списке):
```json
{"description":"....","class_codes":"|SPBFUT_HOL|SPBOPT_HOL|SPBXM_HOL|TQBR_HOL|EQOB_HOL|EQEO_HOL|EQDB_HOL|TQDE_HOL|TQOB_HOL|TQIF_HOL|TQTF_HOL|TQTD_HOL|TQOD_HOL|TQCB_HOL|CETS_HOL|TQBR|PSEQ|EQOB|EQEO|PSAU|PSBB|PSSU|PSOB|AUCT|AUCT_BND|RPMA|RPMO|EQDB|PSDB|SMAL|PSQI|OQQI|PSQI_BND|PSDE|INDX|EQRP|PSRP|EQRP_BND|PSRP_BND|TQDE|TQOB|TQIF|TQTF|PSIF|PSTF|PAUS|TQBD|TQTD|TQOD|PTSD|PSSD|EQWP|EQWP_BND|EQRD|EQRD_BND|EQTD|TQDD|PTDD|PSDD|TQTE|EQTU|TQCB|EQRP_INFO|","fullcoveredsell":0,"main_trdaccid":"L01~00000F00","bankid_tplus":"NCC","trdacc_type":4,"firmid":"MC0003300000","depunitid":"...","bankid_t0":"NCC","firmuse":0,"status":0,"depaccid":"...","trdaccid":"L01-00000F00","bank_acc_id":"..."}

То есть при попытке найти торговый аккаунт для класса SPBFUT я найду первый, в котором есть упоминание SPBFUT. А данном случае это не то, что нужно.

  1. getOrder_by_ID и getOrder_by_Number не ищут стоп ордера. И нет функции для их поиска. Надо либо добавить в эти функции поиск по стоп ордерам, либо добавить отдельные функции исключительно для стопов. Я бы предпочел первый вариант, но второй вариант логичнее с учетом архитектуры API квика (там, почему-то, есть разделение на отдельно стопы, отдельно обычные ордера).

  2. При подписке на параметры бумаги нельзя подписаться с помощью одного вызова на все интересующие параметры. В моем случае мне необходимы несколько параметров бумаги, на которые я сразу же после старта программы подписываюсь (LAST, BID, OFFER, LOW, HIGH и т.д.). Также после изменения одного из параметров мне нужно получить значения всех этих параметров заново, потому как Квик не присылает изменившиеся. Что приводит к чему? К множеству тысяч запросов и ответов (если включены классы русской фонды и американской фонды). Проблема здесь даже не в том, что столько запросов, а в latency канала связи с lua сервером, на котором установлен Квик. Потому как на localhost'e скачивание всех бумаг, подписка на параметры, получение параметров занимает 5 секунд (и это на мощном железе и быстром интернете). В случае, если я подключаюсь по локальной сети через вай-фай, то такие же действия занимают пару минут (потому что пинг по вай-фай 3-5 мс). У меня просто программа работает еще и на Android и идея запускать lua сервер на 0.0.0.0, чтобы подключаться по локалке, имеет смысл.

Я считаю, что можно как-то функции paramRequest и getParamEx2 сделать такими, чтобы они имели возможность принимать массивы по три элемента в каждом, а не просто три элемента. Может сделать какую-нибудь запись вида classCode|secCode|paramName;classCode|secCode|paramName, а потом дважды делать split, чтобы получить массив массивов. Ответ возвращать в виде разделенных элементов по порядку их упоминания типа 123|234|345|456. Вместо пары минут все займет пару секунд. Да, немножко сложно будет обработать ответ на стороне клиента, зато гораздо эффективнее и быстрее.

Что скажете насчет этих предложений? Есть в них польза для вас и сообщества?

buybackoff commented 4 years ago

Пришлите pull request пожалуйста, без diff view очень сложно понимать изменения, и тем более тестировать.

Что скажете насчет этих предложений? Есть в них польза для вас и сообщества?

1,3,5 поддерживаю, 2,4 возможно breaking changes.

В идеале по каждому пункту хотелось бы видеть pull request. Изменения звучат логичными, но есть риск, что починив одно, у кого-то сломается другое.

а потом дважды делать split, чтобы получить массив массивов.

По последнему пункту - надо просто использовать JSON

Пишу с телефона, может что-то упустил, посмотрю внимательнее позже.

avently commented 4 years ago

У меня для крипты при получении ордера всегда указана средняя цена всех трейдов этого ордера. Это дико удобно, ведь получая ордер через веб сокет или REST я уже знаю среднюю цену покупки/продажи, а значит могу определить цену ответного ордера. Почему-то Квик такой строчки в ордере не указывает и чтобы ее получить, нужно парсить трейды и самому считать среднюю цену. К тому же, я привык к тому, что у ордера есть timestamp последнего изменения (поменялся статус, изменился купленный объем и т.д.). Квик этого тоже не предоставляет (только время отмены ордера и выставления, а также срабатывание стопа). Так или иначе, этого мало. Поэтому я в Lua добавил код, позволяющий увидеть время последнего трейда и среднюю цену. Вопрос в том, в каком виде внести код в PR. Потому как средняя цена должна считаться во всех функциях, где возвращается объект Order, а это:

Я понимаю, что это надо не всем (или всем надо?). И можно было бы сделать дублирующие фунции или возможность указать в вызовах уже имеющихся функций, что клиент хочет получить эту информацию в ответе. Однако в случае callback'a ничего указать не выйдет - там либо делать, либо не делать. Вопрос таков: как лучше реализовать данный функционал с вашей точки зрения?

Пример кода:

--- Функция возвращает таблицу заявок (всю или по заданному инструменту) вместе с датой последнего трейда и средней ценой
function qsfunctions.get_orders2(msg)
    if msg.data ~= "" then
        local spl = split(msg.data, "|")
        class_code, sec_code = spl[1], spl[2]
    end

    local orders = {}
    for i = 0, getNumberOf("orders") - 1 do
        local order = getItem("orders", i)
        if msg.data == "" or (order.class_code == class_code and order.sec_code == sec_code) then
            table.insert(orders, fill_order_with_trades_data(order))
        end
    end
    msg.data = orders
    return msg
end

function fill_order_with_trades_data(order)
    local qty_of_trades = 0
    local price_qty = 0
    for i = 0, getNumberOf("trades") - 1 do
        local trade = getItem("trades", i)
        if trade.order_num == order.order_num then
            price_qty = price_qty + trade.price * trade.qty
            qty_of_trades = qty_of_trades + trade.qty
            order.last_trade_datetime = trade.datetime
        end
    end
    if (qty_of_trades > 0) then
        order.average_price = price_qty / qty_of_trades
    end
    return order
end
avently commented 4 years ago

К тому же, надо еще определиться, как делать функцию поиска стоп ордера по transId и orderNum - ведь сейчас можно только обычный (не стоп ордер) искать:

Второй вариант немного ломает текущую логику, а первый вариант требует создания еще трех почти одинаковых функций. Я бы, как пользователь, предпочел второй вариант, но вы, наверное, за первый вариант?

Edit: второй вариант невозможен, так как в Quik'e стопы и лимитки ими порожденные имеют одинаковые trans_id, следовательно надо делать отдельную функцию по поиску стопа и лимитки.

avently commented 4 years ago

Кому будут нужны средняя цена, время последнего трейда и запросы стоп ордеров, вот тут код: #265