finsight / QUIKSharp

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

Получение свечей фризит весь сокет #252

Closed avently closed 4 years ago

avently commented 4 years ago

Функция get_candles_from_data_source из lua кода фризит весь сокет на оооочень длительное время. Пока она исполняется, все остальные запросы к lua серверу истекают по таймауту. Я добавил логи в эту функцию, чтобы отловить баг и увидеть, насколько все плохо. Как всегда, если есть вечный while (в данном случае repeat) - жди беды. Виновник тут: https://github.com/finsight/QUIKSharp/blob/master/src/QuikSharp/lua/qsfunctions.lua#L689

С выводом логов получилась такая функция:

function qsfunctions.get_candles_from_data_source(msg)
    log("Inside candles request")
    local ds, is_error = create_data_source(msg)
    log("Created data source")
    if not is_error then
        log("No error while creating")
        --- датасорс изначально приходит пустой, нужно некоторое время подождать пока он заполниться данными
        repeat sleep(1) until ds:Size() > 0
        log("After sleep")
        local count = tonumber(split(msg.data, "|")[4]) --- возвращаем последние count свечей. Если равен 0, то возвращаем все доступные свечи.
        local class, sec, interval = get_candles_param(msg)
        local candles = {}
        local start_i = count == 0 and 1 or math.max(1, ds:Size() - count + 1)
        for i = start_i, ds:Size() do
            local candle = fetch_candle(ds, i)
            candle.sec = sec
            candle.class = class
            candle.interval = interval
            table.insert(candles, candle)
        end
        ds:Close()
        msg.data = candles
    end
    return msg
end

И результат исполнения функции:

2020-06-04 11:40:07.585 LOG 0: Inside candles request 2020-06-04 11:40:07.585 LOG 0: Created data source 2020-06-04 11:40:07.585 LOG 0: No error while creating 2020-06-04 11:41:09.612 LOG 0: After sleep

Такое бывает нечастно, но бывает. И все рушится на время простоя, все запросы фэйлятся по таймауту. И не удивительно, минуту ожидания в данном случае.

Причина такого поведения, видимо, никудышный интернет, который может перестать работать на минуту-другую. Но в других приложениях я такого "перерыва" не замечал. Возможно, брокерский сервер тупит и отсоединяет Квик временно. Хотелось бы как-то избавится от этого бага. Возможно, сделать внутренний таймаут на пару-тройку секунд до получения результата подписки на свечи, а если нет результата, то отправить null или пустой массив в ответ. А не просто фризить вечно.

Pr0phet1c commented 4 years ago

Не очень понятно, в чем суть Вашего предложения (возможно, только для меня). Если я правильно Вас понял, то если мы сразу не получили от квика набор данных по свечам, то функция должна завершить свою работу и вернуть пользователю пустой результат. Если это так, то в этом случае мы никогда никаких данных по свечам получить не сможем. Объем запрашиваемых на сервере данных достаточно большой, и системе требуется время на его получение. Как Вы предлагаете "пропустить"то время которое требуется совокупности "приложение-коннектор-терминал-интернет-сервер" на получение данных?

avently commented 4 years ago

@Pr0phet1c

Если это так, то в этом случае мы никогда никаких данных по свечам получить не сможем

Ну почему же. Квик же кэширует данные, и если повторно запросить информацию после завершения по таймауту, то данные уже будут готовы. Если данные в кэше, то они отдаются за миллисекунду. Считаю такой подход наилучшим вариантом.

Как Вы предлагаете "пропустить"то время которое требуется совокупности "приложение-коннектор-терминал-интернет-сервер" на получение данных?

Никак. Установить таймаут. Если данные не пришли, выкинуть ошибку. Юзер повторит еще раз, а пока он не повторит, другие запросы смогут исполнится успешно. И так по кругу до результата

Pr0phet1c commented 4 years ago

Это означает, что Вы ВСЕГДА будете получать ошибку при первом вызове этой функции по каждому инструменту и каждому ТФ. Даже не знаю. Надо хорошенько подумать. Я, например, запрашиваю исторические данные, по нескольким десяткам инструментов, и по нескольким ТФ для каждого из них. Да, ждать приходится несколько минут, пока все данные будут получены и прогружены. Но если я получу ошибку, то робот тут же попытается переподписаться на все эти данные заново (т.к. будет считаться что подписка прошла неудачно), и теоретически это может привести к перезаказу данных, со всеми вытекающими последствиями. Хотя, вероятно, это надо проверять. Функцию писал не я, но я пока не нашел более адекватного варианта ее исполнения.

avently commented 4 years ago

ВСЕГДА будете получать ошибку при первом вызове

Не страшно при первом вызове получать ошибку. Страшно не получать ответы от важных функций и не иметь возможности отменить ордер или ещё чего. Ну и наблюдать, как в программе ничего совершенно сделать нельзя в тот момент, когда луа зависла - такое себе удовольствие.

Pr0phet1c commented 4 years ago

Можете привести пример того, как Вы видите реализацию этой функции?

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

avently commented 4 years ago

Можете привести пример того, как Вы видите реализацию этой функции?

Я этот код не тестировал, но примерно так можно реализовать:

                local counter = 0
        --- датасорс изначально приходит пустой, нужно некоторое время подождать пока он заполниться данными
        -- если данные не придут через три секунды, возвращает nil вместо данных
        while (ds:Size() == 0 and counter < 3000) do
            sleep(1)
            counter = counter + 1
        end
        if (ds:Size() == 0) then
            msg.data = nil
            ds:Close()
            return msg
        end

Я применяю подписку на получение свечей.. Вы не пробовали пойти таким путем?

У меня торговый терминал, в котором можно открыть много вкладок и много графиков с разными таймфреймами. Собирать по крупицам колбэки OnNewCandle (если вы об этом способе говорите) просто неудобно и нецелесообразно. Именно так бы я и поступил, если бы данные от биржи я получал по веб сокету, а не через Quik (который их хранит локально за меня). К тому же, мне нужно еще запрашивать счечи по 500 штук, скажем. Чтобы график можно было скроллить влево. Проще раз в минуту получать обновленные свечи, чем все это вот.

Pr0phet1c commented 4 years ago

Что касается Вашего примера: Если этот кусок кода предполагается встроить внутрь указанной процедуры, то скорее всего, в тех случаях, когда получение исторических данных занимает больше 3-х секунд - Вы не получите их буквально никогда, т.к. после первого возврата nil, Ваш робот пошлет новый запрос на запуск той же самой функции, что в свою очередь приведет к обнулению ds и запуску процесса получения данных с самого начала. Что касается подписки: Мне кажется это все же более предпочтительный путь. Другое дело, что я сейчас понял, что у нас нет функции, позволяющей не следить за колбэками после подпииски, а просто вытащить набор свечей из данных, на которые мы подписались. Эту задачку действительно стоит решить.. Надо подумать на досуге о ее реализации

buybackoff commented 4 years ago

Как насчет coroutines? https://www.lua.org/pil/9.1.html

Это фича языка и не требует реальной многопоточности, но может в Квике не будет работать. Мы можем сделать это на уровне диспетчера: если функция X - в свою корутину, остальные в свою. Вместо Sleep - yield.

Это будет самый чистый/правильный подход, но не самый простой.

avently commented 4 years ago

Как насчет coroutines?

Забавно, только сейчас смотрел справку по Lua и увидел там корутины. Подумал, почему же lua часть не сделана на них, чтобы не приходилось синхронно работать с сокетом. Идея хорошая, но понятия не имею, как lua справится с этой задачей и какие будут ограничения. Например, неясно, как там устроена работа с несколькими потоками.

Это будет самый чистый/правильный подход, но не самый простой.

В чем сложность такого подхода? Был опыт работы с корутинами на Lua?

Кстати, насчет синхронности работы с сокетом. Это у меня криво написана реализация или lua код не приспособлен для асинхронного приема и обработки сотен собщений сразу? Пытался как-то ускорить получение данных, но 10 секунд иногда не хватает, чтобы получить по сокету ответ в случае, если сотни запросов сразу. То есть часть приходит, а часть ответов - теряется.

buybackoff commented 4 years ago

Почему не сделано Х

Изначально (в 2014) цель была автоматизация, а не оптимизация, и use case - замена ручной торговли, а не быстрые роботы. Да и глубоких познаний в Луа не было. Предполагалось, что если нужна скорость, но нужно использовать не Квик.

Как я понимаю, корутины и multithreading - разные вещи. Чтобы корутины работали, нужна кооперация: без yield другие рутины не получат возможности работать. Там нет планировщика, который бы давал возможность каждой рутине исполниться по timeslice.

Ускорить работу наверное можно с помощью batching: получить все доступные линии из сокета и обработать, потом посылать in non-blocking manner. Но нужно будет обрабатывать ошибки если сокет занят. Проще новый транспорт прикрутить, возможно ZMQ неплохая идея в итоге, учитывая, что все равно пришлось всё компайлить самому.

Я пока не собираюсь кардинально что-то менять. Не уверен, что асинхронный транспорт решит проблему при однопоточной исполнении запросов в Lua, которая при этом вызывает синхронные функции Квика.

avently commented 4 years ago

Как я понимаю, корутины и multithreading - разные вещи.

Обычно да. Но в Kotlin Coroutines упростили старт задачи в разных потоках. Может, и тут так можно.

Я пока не собираюсь кардинально что-то менять.

Аргументы понятны. А что делать с данным issue?

buybackoff commented 4 years ago

Можно попробовать корутины для этой функции и остальных функций на уровне диспетчера. Или изначальное предложение с пустым ответом и retry.

Если корутины работают в одном потоке в Квике, можно подумать о дальнейших оптимизациях.

Самое простое решение асинхронности - большой буффер сокета и non-blocking send/receive. Нужно будет обратаывать socket busy ошибку и не понятно, возможно ли это всё в Луа. Если да, то корутины тут идеально подходят - если сокет занят, yield в этот момент. Когда все корутины закончили работу

Pull requests на эту тему welcome.

Но тема совсем не простая, чтобы это довести до ума потребуется не мало времени, которого бесплатного сейчас у меня нет.

И такие изменения уже не будет наверное open source, учитывая с какой охотой QUIK# используют коммерческие проекты, не ставят ссылку и не приносят улучшения обратно сюда.

Интересно, готовы ли люди платить подкиску или разово за быстрый коннектор и техническую поддержку?

On Fri, Jun 5, 2020 at 11:38 AM avently notifications@github.com wrote:

Как я понимаю, корутины и multithreading - разные вещи.

Обычно да. Но в Kotlin Coroutines упростили старт задачи в разных потоках. Может, и тут так можно.

Я пока не собираюсь кардинально что-то менять.

Аргументы понятны. А что делать с данным issue?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/finsight/QUIKSharp/issues/252#issuecomment-639371328, or unsubscribe https://github.com/notifications/unsubscribe-auth/AADXS7FUFELHR4KEYDMBOYTRVC4INANCNFSM4NSNLH6Q .

avently commented 4 years ago

Pull requests на эту тему welcome

Если от меня, то очень не скоро, работы море. С этим Квиком приходится половину архитектуры приложения переделывать... Я думаю, если идти предложенным вами путем, то надо хотя бы провести сначала тесты - стоит ли овчинка выделки вообще. Может, профита в плане скорости не добьемся.

Можно попробовать корутины для этой функции и остальных функций на уровне диспетчера. Или изначальное предложение с пустым ответом и retry.

А можете хотя бы для этой функции протестировать свою идею с корутинами, когда будет время? Если не получится, то уже пусть будет while с таймаутом, как я предложил. Только надо, чтобы именно nil отправлялся, а не []. Чтобы знать наверняка, что данные есть, но не получены еще.

И такие изменения уже не будет наверное open source. Интересно, готовы ли люди платить подкиску или разово за быстрый коннектор и техническую поддержку?

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

buybackoff commented 4 years ago

Вы делаете платный продукт неограниченному кругу или под заказ что-то?

On Fri, Jun 5, 2020, 12:13 PM avently notifications@github.com wrote:

Pull requests на эту тему welcome

Если от меня, то очень не скоро, работы море. С этим Квиком приходится половину архитектуры приложения переделывать... Я думаю, если идти предложенным вами путем, то надо хотя бы провести сначала тесты - стоит ли овчинка выделки вообще. Может, профита в плане скорости не добьемся.

Можно попробовать корутины для этой функции и остальных функций на уровне диспетчера. Или изначальное предложение с пустым ответом и retry.

А можете хотя бы для этой функции протестировать свою идею с корутинами, когда будет время? Если не получится, то уже пусть будет while с таймаутом, как я предложил. Только надо, чтобы именно nil отправлялся, а не []. Чтобы знать наверняка, что данные есть, но не получены еще.

И такие изменения уже не будет наверное open source. Интересно, готовы ли люди платить подкиску или разово за быстрый коннектор и техническую поддержку?

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

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/finsight/QUIKSharp/issues/252#issuecomment-639387120, or unsubscribe https://github.com/notifications/unsubscribe-auth/AADXS7COV42G6ZCX4HM6BETRVDAMDANCNFSM4NSNLH6Q .

avently commented 4 years ago

@buybackoff условно-бесплатная программа для Google Play, компа (пока что в виде консольного приложения). Потом другие платформы будут. Но пока так. На заказ ничего, связанного с Квиком, не делаю.

buybackoff commented 4 years ago

@avently

Не знаю насчет остальных, но я бы в таком случае делал свой велосипед. Но это я. Потому что мне проще сделать самому, чем использовать закрытую библиотеку в таком ответственном деле.

Поэтому ничего принципиального нового без pull requests тут скорее всего не будет. Какая причина тратить часы своего времени на это за бесплатно?

Для @Pr0phet1c функционал текущий подходит, если что - он для себя может поправить. Других активных контрибьютеров в последнее время не было.

avently commented 4 years ago

Какая причина тратить часы своего времени на это за бесплатно?

Такая же причина, которая привела к созданию библиотеки QuikSharp. У каждого она своя. Мне бы на вашем месте было бы ценно получить прирост в производительности своего творения. Потому как корутины реально можно применить для увеличения производительности и параллелизации, ну и новые знания/мысли. Просто если есть время, то этот процесс может быть ценным. А если нет, то ничего интересно не будет. Я вот сам люблю законтрибьютить код, когда есть свободное время и потребность в функционале.

Классно, когда люди делают открытые проекты. Жалко, что зарабатывать на них неясно как, а время жизни человека ограничено. Так бы сутками код писал.

buybackoff commented 4 years ago

Мне бы на вашем месте было бы ценно получить прирост в производительности своего творения. Потому как корутины реально можно применить для увеличения производительности и параллелизации, ну и новые знания/мысли.

Lua кроме Квика мне встречалась в Redis (атомарные транзакции любой сложности очень удобно, если это нужно), в NginX, как встроенная БД, Kong целиком написал на Луа. За исключением последнего, везде это встроенный язык для быстрых однопоточных скриптов. Использовать Луа для построения системы целиком нецелесообразно, её сила во встроенности с интеграции с C, а не в языке и его фичах.

В случае с Квиком интереснее будет написать что-нибудь на Rust, скомпилировать как cdylib и вызывать из Луа C API. Это если говорить про спортивный интерес программирования ради новых знаний.

У меня есть куда тратить время: https://github.com/Spreads/Spreads и https://github.com/DataSpreads/DataSpreads. Интересно, что первая работала нормально для определенных целей уже 6 лет назад, а попытка "сделать лучше" 2 года назад завела в тупик. Вторая тоже работала 1,5 года назад, но зависит от первой, и тоже вторая итерация для исправления всего, что было выявлено в первой, оказалась очень большой с т.з. требуемого времени.

Так что не сломалось - не чини.

А я больше не в России и мне Квик не актуален сейчас совсем.

buybackoff commented 4 years ago

Review пулл реквестов, решение тупиковых проблем (как с 8.5) и релиз версий - это максимум, что я могу этому проекту уделять. @Pr0phet1c уже давно делает больше, чем я, для него.

Про корутины я просто вставил свои 2 цента из чисто программерского интереса.