Система принятия решений и/или Нечеткая логика(FuzzyLogic)

Страницы: Пред. 1 ... 6 7 8 9 10 След.
RSS
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
 
Цитата
Nikolay написал:
А значит выполняться она может тоже только через вызовы в основном цикле mian. И тоже частота вызовов регулируется задержкой цикла main.Собственно ваш любимый hacktrade так и работает.
Вы не поняли сути hacktrade, как раз он как пример такой асинхронности.

1. При первом в ходе создается корутина и мы работаем в ее цикле, а у нее никаких задержек, (кроме накладных), в ней идет вся торговая логика расчеты, вычисление и так далее. Останавливаем исполнение когда выполнены условия на отправку заявки и заявка отправлена (выполняется yield).

2. Структурно код стал на выполнение в цикле маин (с задержками sleep)  функционально идет работа с заявкой. Где тут последовательность?
Самая что не на есть асинхронность.

Выполнение кода структурно разделено на две составляющие:
А) Поиск условия выполнения заявки (в цикле корутины);
Б) Не посредственное исполнение заявки (взаимодействие с апи квик - в цикле маин).  
 
Нашел. Вот когда то я сохранял для себя понимание. Что то с родни Вашему подходу.

Lua предоставляет "лёгкие потоки" — coroutines (сопрограммы). Это кооперативные задачи, которые работают внутри одного потока исполнения, но могут приостанавливать и возобновлять выполнение, передавая управление.

--Ключевая идея лёгкого потока (coroutine)

local is_run = true
local threads = {}

function worker(name, delay)
   local i = 0
   while is_run do
       i = i + 1
       message(string.format("[%s] шаг %d", name, i))
       for _ = 1, delay do coroutine.yield() end
   end
end

function main()
   threads[1] = coroutine.create(function() worker("A", 2) end)
   threads[2] = coroutine.create(function() worker("B", 5) end)

   while is_run do
       for _, co in ipairs(threads) do
           if coroutine.status(co) ~= "dead" then
               coroutine.resume(co)
           end
       end
       sleep(200)
   end
end

function OnStop() is_run = false end

Это — мини-диспетчер потоков на чистом Lua.
Каждый "поток" (worker) — корутина с собственным ритмом, а цикл в main() играет роль планировщика.

* Корутину можно считать лёгким потоком Lua — она живёт, имеет собственный контекст и исполняется пошагово.

* А main() — просто планировщик, который возобновляет корутины.
 
Цитата
VPM написал:
Вы не поняли сути hacktrade, как раз он как пример такой асинхронности.
Я на него смотрел еще в году так 16-ом. Как он работает предельно понятно. Подход далеко не новый, да и с чего ему быть новым если корутины в Lua появились в далеком 5.0 от 2003

Цитата
А main() — просто планировщик, который возобновляет корутины
Правильно. А значит с тем же успехом можно планировать и вызовы процедур, созданных иначе. Какая разница что вызывать - корутину или процедуру. Вызывается из одного и того же места, с тем же ритмом и скоростью. Отличия только в обертке одних в корутины, а другие нет.
 
Да но есть ключевое отличие. yield() - приостанавливает только задачу, не весь скрипт как sleep.

--- Ключевая идея лёгкого потока (coroutine - кооперативная задача). Этот код создаёт лёгкий поток (фактически, сопрограмму), который выполняется по шагам, каждый шаг — между yield() и resume().
Код
local co = coroutine.create(function()
    for i = 1, 5 do
        print("Работаю...", i)
        coroutine.yield() -- приостанавливаем "поток"
    end
end)

while coroutine.status(co) ~= "dead" do
    coroutine.resume(co) -- возобновляем
    sleep(100)
end
Код
--- Пример «лёгкого фонового потока» в QUIK

local is_run = true

-- "Лёгкий поток" для фоновой задачи
local function background_task()
    local count = 0
    while is_run do
        count = count + 1
        message("Фоновая задача: шаг " .. count)
        coroutine.yield()  -- уступаем управление
    end
end

function main()
    local co = coroutine.create(background_task)
    message("Запуск фонового лёгкого потока...")
    while is_run do
        coroutine.resume(co)  -- возобновляем поток
        sleep(1000)           -- имитация "асинхронного" поведения
    end
end

function OnStop()
    is_run = false
    message("Остановка фонового потока.")
end


-- * Корутину можно считать лёгким потоком Lua — она живёт, имеет собственный контекст и исполняется пошагово.
 
 
Тогда давайте еще раз, на пальцах. Берем этот пример и "брюки превращаются":

local is_run = true
-- "Лёгкий поток" для фоновой задачи
local function create_task()
   local count = 0
   return function()
       count = count + 1
       message("Фоновая задача: шаг " .. count)
       return  -- уступаем управление
   end
end
function main()
   local co = create_task()
   message("Запуск фонового лёгкого потока...")
   while is_run do
       co()                  -- возобновляем поток
       sleep(1000)           -- имитация "асинхронного" поведения
   end
end
function OnStop()
   is_run = false
   message("Остановка фонового потока.")
end



Как говорится - найдите десять отличий.
 
Отличие прежнее.

Вы в потоке маин вызываете псевдоним функции выше, после выполнения функции цикл  приостанавливается sleep(1000) .
В то время как в примере с корутиной  имеется свой легки поток без задержек, а   coroutine.yield()  -- уступаем управление (подвешивает задачу) не создавая ни каких задержек, но цикл то маин выполняется. И таких задач можно вешать сколько угодно, в то время как в Вашем варианте после каждой задачи задержка.
Код
-- "Лёгкий поток" для фоновой задачиlocal function background_task()
    local count = 0
    while is_run do
        count = count + 1
        message("Фоновая задача: шаг " .. count)
        coroutine.yield()  -- уступаем управление
    end
end
 
Нет такого. Я просто переделал Ваш пример. У Вас там есть задержка. Уберите, если хотите и не будет никаких задержек.
 
Цитата
VPM написал:
Отличие прежнее.

Вы в потоке маин вызываете псевдоним функции выше, после выполнения функции цикл  приостанавливается sleep(1000) .
В то время как в примере с корутиной  имеется свой легки поток без задержек, а   coroutine.yield()  -- уступаем управление (подвешивает задачу) не создавая ни каких задержек, но цикл то маин выполняется. И таких задач можно вешать сколько угодно, в то время как в Вашем варианте после каждой задачи задержка.
Код
   -- "Лёгкий поток" для фоновой задачиlocal function background_task() 
     local  count  =   0 
     while  is_run  do 
        count  =  count  +   1 
         message ( "Фоновая задача: шаг "   ..  count)
         coroutine.yield ()   -- уступаем управление 
     end 
 end   
У Вас тоже задержка в main() на 1 секунду, т.е. в общем потоке скрипта, будет точно также задерживаться. У Вас и у Nikolay абсолютно синхронно будут исполняться вызовы, только у него быстрее будет проходить сам вызов в десятки раз.
 
Ну хорошо вот другой пример:
Код
function main()
    threads[1] = coroutine.create(function() worker("A", 2) end)
    threads[2] = coroutine.create(function() worker("B", 5) end)

    while is_run do
        for _, co in ipairs(threads) do
            if coroutine.status(co) ~= "dead" then
                coroutine.resume(co)
            end
        end
        sleep(200)
    end
end
 
Подсобрался с мыслями. Можно некий итог подвести.

Различия двух подходов, про применение см. выше мнение не поменял.

1. Подход с замыканием.
Нет возможности приостанавливать сложные задачи на середине выполнения.
Сложно реализовать ожидание событий или таймеров внутри задачи без внешнего кода.
Каждая задача должна сама заботиться о том, как уступать управление (через sleep в основном цикле).

2. Подход с корутиной.
Внутри корутины можно писать сложные алгоритмы и «разбивать» их на шаги.
Более гибкая модель.
Можно приостанавливать задачу на любом шаге.
Можно легко комбинировать несколько фоновых задач в одном цикле.
Более похоже на настоящие асинхронные потоки.
Сложнее.
Немного больше накладных расходов на создание и управление корутиной (хотя для QUIK это не критично).
coroutine.yield() - приостанавливает выполнение задачи, позволяя основному циклу выполняться и управлять её запуском.

Пока разбирались на кидал лёгкий планировщик фоновых задач для QUIK. Всем пока хорошего кода.
 
Нет. Это все не так. Корутина точно также работает. Где в корутине yield, в процедуре return. Где коде coroutine.resume - просто вызов процедуры. Все с точностью до стоки кода. Никаких чудес.
Полная аналогия.
 
Цитата
Nikolay написал:
Полная аналогия.
  С довесочком:
 1. Программировать корутины сложнее.
 2.
Цитата
VPM написал:
1   1   29.10.2025   10:31:19   Скрипт запущен. Идёт фоновое измерение производительности...
2   1   29.10.2025   10:31:20   Фон: N=100000, coroutine=584.000 мс (c=100000), func=6.000 мс (f=100000), T1/T2=97.33
 По вашим же тестам накладные расходы на вызов корутин в 97.33 раз больше, чем на вызов функций (coroutine=584.000 мс , func=6.000 мс ). Чтобы для вас было наглядно, при всяком вызове корутины, прежде чем она начнет что-то делать, делается 97 пустых вызовов функций.
 
TGB,  Ну так это без тестов пoнятно конечно, чуть больше, т.к. стек корутины сохраняется.

Александр М, Да Вы правы, все так, по той же самой причине - стек корутины сохраняется! Данный пример с count, это простейшее действие, а корутину создаем для решения сложных, затратных задач, где нужно не зависимая от основного цикла обработка данных (фон). Так что довод не корректен, так как при решении сложной задачи будет нивелирован, а возможно и получен выигрыш (как в обсуждаемом примере с фреймворком).

Nikolay,  Ну как же нет? Смотрим на саму суть:

* Что делает замыкание? Создало локальную переменную.

* Что делает корутина? Создало независимы поток (без задержек и не блокирует QUIK с минимальной нагрузкой на CPU)

Преимущества очевидны:
1.  Не блокирует QUIK.
2.  Можно запустить много задач одновременно (мониторинг, логирование, анализ, обновление лимитов).
3.  Лёгкая расширяемая структура — можно добавлять задачи динамически.
4. Поддерживает интервалы выполнения (как cron).

Посмотрим, что такое «лёгкий поток» — coroutines (сопрограммы) в контексте Lua? Это кооперативные задачи, которые работают внутри одного потока исполнения, но могут приостанавливать и возобновлять выполнение, передавая управление вручную. yield() - приостанавливает только задачу, не весь скрипт.

По каким признакам это "лёгкий поток"?
1. Лёгкий по памяти. Корутине нужно всего несколько КБ стека.
2. Быстрый. Не создаёт системных потоков/контекстов.
3. Кооперативный. Поток сам решает, когда уступить управление.
4. Полный контекст исполнения. Сохраняются локальные переменные, состояние и позиция.
5. Пауза без блокировки QUIK. yield() приостанавливает только задачу, не весь скрипт.

* Каротину можно считать лёгким потоком Lua — она живёт, имеет собственный контекст и исполняется пошагово.
* А `main()` — просто планировщик, который возобновляет _ Ну так это без тестов пoнятно конечно, чуть больше, т.к. стек корутины сохраняется.

Резюме. В Lua корутины — это лёгкие, управляемые потоки. Они позволяют выполнять фоновую работу, не блокируя основной цикл QUIK. В QUIK это единственный безопасный способ организовать "многозадачность" без внешних библиотек.
 
Любая процедура, вызванная из main не блокирует QUIK. Любая. Хоть завернутая в корутину, хоть вызванная напрямую, хоть в замыкании.
Также любая процедура сама принимает решение когда выйти из неё. Для этого есть оператор return. В корутине для этого используется yield. return тоже просто возвращает управление в точку вызова процедуры, тоже не останавливая скрипт.

Любое действие через корутины может быть формализовано через процедуры, с точно такой же последовательностью выполнения и получения результатов. Любое.
 
Да, но в подходе с замыканиями - приходится вручную хранить состояние "этапа" в цикле "подожди, пока". А код постепенно превращается в "машину состояний". При добавлении новых этапов он становится всё громоздче и сложней. Отсюда и вывод для простых задач!

В подходе с корутинами. Никаких ручных "этапов" — код читается как обычный последовательный сценарий. Можно использовать yield где угодно, чтобы "заморозить" выполнение.  Можно легко добавить ожидание событий, сетевые операции и т.д. Логика выглядит линейно, но выполняется асинхронно.
 
1.  
Цитата
VPM написал:
чуть больше
  В ~100 раз.  Вы философ :smile: .
2. У вас есть понятие, что при использовании корутин надо синхронизироваться, если в них используются общие изменяемые данные? То есть следить за тем, чтобы данные недоделанные в одной корутине не использовались в другой. Вы когда-нибудь занимались параллельным программированием?
 
TGB,  А Вам это о чем то говорит: ОС > Терминал > Lua? У Вас мощная машина, а у меня старенький 13 летний? 2. Еще в позапрошлом ВЕКЕ придумали "семафор" и он прекрасно справляется с пропусками поездов до сих пор. А пользоваться или нет параллельным программированием - решает пользователь для себя сам. Что касается меня так я примитивный пользователь - СЕМАФОР!  :smile:  
 
Цитата
VPM написал:
а у меня старенький 13 летний
 Зачем же вы его мучаете тяжелыми корутинами :smile: ? Когда можно легко обойтись легкими функциями. Вам же Nikolay все объяснил.
 
TGB,  Вы в начале полностью прочитайте обсуждение, а затем рекомендации выдавайте. Если рассмотреть пример с фреймворком то он значительно легче, если подобную логику выстраивать подходом с замыканиями.

По образованию инженер тяжело расстаюсь с техникой. А мой еще хоть куда, даст фору некоторым что сегодня в продаже. Это лучшая пока машинка из всех что были у меня (Sony, Toshiba, Asus ..) хотя и 3 поколение i7 прекрасно справляется с 3 квиками 5 браузерами и еще всякой всячиной. Проблема есть с WiFi старое поколение ограничена скорость приема данных 200, а нужно 1000.  
 
Все о великом да о великом! А элементарные вещи: Как при торговле на маржинальном счете Акциями, получить "Свободный денежный остаток"? Во Задача!
Сколько лет уже пользуюсь QUIK но ответ на этот вопрос взывает смятение.
Элементарный вопрос для каждого, оценить капитал в распоряжении. Стоимость Активов в бумагах + Свободные денежные средства = Активы клиента.

Как получить Эти "Свободные денежные средства", без выкрутасов? Во Задача?
 
Цитата
VPM написал:
В подходе с корутинами. Никаких ручных "этапов" — код читается как обычный последовательный сценарий. Можно использовать yield где угодно, чтобы "заморозить" выполнение.  Можно легко добавить ожидание событий, сетевые операции и т.д. Логика выглядит линейно, но выполняется асинхронно.
Да нет ничего сложного писать такие же легкие функции и без корутин. Если под легкостью подразумевается "загнать в корутину длинную портянку с множественными yield", то это спорный вопрос, насколько это легко воспринимается.

Цитата
Да, но в подходе с замыканиями - приходится вручную хранить состояние "этапа" в цикле "подожди, пока".
Ничего хранить вне замыкания не надо, оно само хранит на стеке свое состояние. Просто вызываете метод и он помнит окружение. Это кстати один из базовых методов организации своих колбеков, помещенных в очередь исполнения. Создали окружение, запихнули в очередь. И выполняете. Когда закончится выполнение, он сам вызовет колбек, который помнит все, чтобы отработать. Никаких дополнительных действий.

Думаю, что спорить дальше не стоит. Как уже писал, я корутины использую только для своих итераторов (hack-trade, кстати, и есть бесконечный итератор) или если надо пробросить varargs ... на следующий уровень. Более нужды нет.
Любая длительная операция, требующую ожидания неопределенное время, выполняется через очередь задач, построенную на простых функциях, где-то замыканиях.  
 
Поле -* Свободно - Доступно для вывода средств при сохранении обеспеченности позиций либо открытия позиций по не маржинальным активам, с учетом заблокированных средств под активные заявки, с точностью валюты цены инструмента.

Соответствует значению параметра «НаПокупкуНеМаржин» в таблице «Клиентский портфель».

Для срочного рынка соответствует значению параметра «План. чист. поз.» в таблице «Ограничения по клиентским счетам».
параметр - lim_non_margin STRING Оценка денежных средств, доступных для покупки не маржинальных инструментов .

По факту - это средства доступные для торговли (для проведению торговых операций), А каково, как оформлено, судите сами?

Ну что тут скажешь, не в нашу копилочку талантов! Ну не специально же люди так старались?
 
Nikolay,  Я с Вами не спорю, и ни чего не утверждаю, просто пытаюсь разобраться в возможностях LUA, то что подразумевается под понятием оптимизация, когда какой метод применять? Для себя некую шпаргалку сделать, пределы использования подхода. Да, обсуждение полезно, не много по другому взглянул на подходы. Замыкание использовал в основном при написании индикаторов там это логично. Ну чтобы с Вами окончательно согласиться или нет, нужна собственная история, буду пробовать. Надеюсь что не только мне было интересно и познавательно, но и многим нашим коллегам. Вам большое спасибо за предметное обсуждение.
 
Ну так в индикаторах замыкания используются не зря. Это позволяет хранить стек между вызовами OnCalculate. Сделки смогут проходит раз в час. И вызовы будут помнить все, что было час назад. И не надо городить глобальных переменных, ничего передавать не надо (в этом плане примеры от ARQA некорректны, т.к. передают каждый раз настройки). Т.е. это конструктор некого алгоритма расчета. Да, это можно реализовать через корутины, а кто-то будет упорно доказывать, что это надо решать через классы, они ведь тоже помнят контекст. Тем более, что целое поколение всем вдалбливали, что ООП - это основа, основ. Но как все в истории повторяется, теперь уже не так активно стали это говорить, и даже больше, ООП - это не очень удачная затея в целом.
 
Да это ранее мы обсуждали, пока настройки передаю, не сильно затратно, но более надежно (ни куда не пропадают при "прыгании" по инструментам, тф.) .
ООП  в индикаторах тоже попробовал, мнения определенного пока не сложилось, понравилось что таблицы луа органично ложатся на этот подход, классная расширяемость модульность, такое в впечатление что он под луа и создавался (шучу), что не понравилось накладные больше, значительно сложней в отладке. Сейчас пишу небольшие задачи в двух вариантах подходов. Пытаюсь как мы с Вами сейчас разобраться, что для меня приемлемей.
 
Пример карутинного элегантного подхода.

ВНИМАНИЕ! РЕАЛЬНО ТОРГУЕТ!  
Для начала закомментируйте вывод заявок! Вместо отправке поставьте сообщение.
Это просто демонстрация возможностей! "Иметь или не иметь" решаем самостоятельно.
Код
- Pобот - торговый менеджер с реальным риск-контролем QUIK
--[[ Цель. Создать систему, которая:

1. Определяет начальный капитал (из QUIK).
2. Каждые несколько секунд получает текущую оценку капитала.
3. Вычисляет просадку в процентах от начального капитала.
4. Если просадка превышает лимит (например, 2%), —
       останавливает все стратегии, фиксирует в логах и выводит уведомление.
--]]
--[[Пример: простая стратегия на основе корутины!

Идея: Следим за ценой инструмента (SBER), и 
   если она падает больше чем на "drop_trigger %" от последней зафиксированной — покупаем 1 лот.
   Если поднимается больше чем на "drop_trigger %"% — продаём 1 лот.
   
Все операции выполняются пошагово, без блокировки основного потока. --]]

local is_run = true

------------------------------------------------------------
-- === Конфигурация ===
------------------------------------------------------------
local ACCOUNT     = ""
local CLIENT_CODE = ""
local FIRM_ID     = "" 

local    order_type = "LIMIT"
local    time_in_force = "GTC"

local LIMIT_KIND  = 0            -- 0 = по деньгам
local RISK_LIMIT  = -0.02        -- максимум 2% просадки
local LOT_SIZE    = 1

local drop_trigger = 0.001
local take_profit  = 0.002
local stop_loss    = 0.001

local instruments = {
    { class = "TQBR", sec = "SBER" },
    { class = "TQBR", sec = "GAZP" },
    { class = "TQBR", sec = "ROSN" },
}

------------------------------------------------------------
-- === Логирование ===
------------------------------------------------------------

-- Объявляем log_file глобально для скрипта
local log_file; --= getScriptPath() .. "\\robot_capital_risk_log.txt"

-- Функция для логирования
local function log(msg)
    local line = os.date("%Y-%m-%d %H:%M:%S") .. " | " .. msg
    message(line)
    local f = io.open(log_file, "a")
    if f then
        f:write(line .. "\n")
        f:close()
    else
        message("x Не удалось открыть файл для записи: " .. log_file)
    end
end

--[[ ЭЛЕГАНТНАЯ ФУНКЦИЯ ОКРУГЛЕНИЯ удалением ,0]]--
local round = function (num, idp)
    
    -- Если num некорректное, вернуть как есть
    if not num or type(num) ~= "number" then return num end
    -- добавbv обработку очень малых чисел:
    if math.abs(num) < 1e-10 then return 0 end  -- ДОБАВИТЬ
    
    -- Если idp не указан, использовать 0 (округление до целого числа)
    idp = idp or 0 
    local mult = 10^idp

    -- Универсальное Округление для любого числа 
    local rounded = num >= 0 and math.floor(num * mult + 0.5) / mult or num < 0 and math.ceil(num * mult - 0.5) / mult or 0
    
    -- Если число целое, убрать .0
    if rounded == math.floor(rounded) then
        return math.floor(rounded)
    end

    return rounded
end
local function fit(price, step, scale)

  local result = math.floor(price / step) * step;
  log('result = '..tostring(round(result, scale)) );
  return round(result, scale)
end

------------------------------------------------------------
-- === Модуль Risk Manager (по реальному капиталу) ===
------------------------------------------------------------

-- Получаем текущий капитал клиента (денежный баланс в рублях или общие активы)
local function get_current_capital()

--* Свободно - Доступно для вывода средств при сохранении обеспеченности позиций либо открытия позиций по немаржинальным активам, с учетом заблокированных средств под активные заявки, с точностью валюты цены инструмента. Соответствует значению параметра «НаПокупкуНеМаржин» в таблице «Клиентский портфель». Для срочного рынка соответствует значению параметра «План. чист. поз.» в таблице «Ограничения по клиентским счетам» 
-- lim_non_margin STRING Оценка денежных средств, доступных для покупки немаржинальных инструментов 

  if instruments and instruments[1] and instruments[1].class == "TQBR" then
    -- Если не удалось — пробуем через портфель
    local pf = getPortfolioInfoEx(FIRM_ID, CLIENT_CODE, 2)
    if pf and pf.lim_non_margin then
        -- return tonumber(pf.assets)
      --message( "Свободные средства для покупки немаржинальных активов: ".. tostring(pf.lim_non_margin) )
      return tonumber(pf.lim_non_margin)
    end
  end

  if instruments and instruments[1] and instruments[1].class == "SPBFUT" then
   local limits = getLimitInfo(FIRM_ID, CLIENT_CODE)
    if limits and limits["План. чист. поз."] then
        local free_for_futures = tonumber(limits["План. чист. поз."])
        message(string.format("Свободные средства для открытия чистых позиций на срочном рынке: %.2f руб.", free_for_futures))
    else
        message("Не удалось получить информацию о свободных средствах для открытия позиций на срочном рынке.")
    end
  end

    -- Если ничего не удалось получить — вернём nil
    return nil
end

local Risk = {
    start_capital = nil,
    current_capital = nil,
    total_change = 0.0
}
-- Инициализация начального капитала
function Risk:init()
    self.start_capital = get_current_capital()
    if not self.start_capital then
        log("x Не удалось определить начальный капитал!" .. tostring(self.start_capital) )
        self.start_capital = 1
    else
        log(string.format("v Стартовый капитал: %.2f руб.", self.start_capital))
    end
end
-- Проверка текущего состояния
function Risk:check()
    local capital = get_current_capital()
    if not capital then
        coroutine.yield()
        return
    end
    self.current_capital = capital
    self.total_change = (capital - self.start_capital) / self.start_capital
    log(string.format("v Текущий капитал: %.2f руб. (%.2f%%)",
        capital, self.total_change * 100))

    if self.total_change <= RISK_LIMIT then
        log(string.format("v Просадка %.2f%% превысила лимит %.2f%% — остановка торгов!",
            self.total_change * 100, RISK_LIMIT * 100))
        is_run = false
    end
end

------------------------------------------------------------
-- === Отправка заявок ===
------------------------------------------------------------
local function send_order(class, sec, direction, price)
    local order = {
        CLASSCODE   = class,
        SECCODE     = sec,
        ACCOUNT     = ACCOUNT,
        CLIENT_CODE = CLIENT_CODE,
        OPERATION   = direction,
        PRICE       = tostring(price),
        QUANTITY    = tostring(LOT_SIZE),
        TYPE        = "L",
      ACTION      = "NEW_ORDER",
        TRANS_ID    = tostring(math.random(1000000, 9999999))
    }

    local res = sendTransaction(order)
    if res ~= "" then
        log(string.format("[%s] x Ошибка при заявке: %s", sec, res))
    else
        log(string.format("[%s] v %s по %.2f (%d)", sec,
            direction == "B" and "Куплено" or "Продано", price, LOT_SIZE))
    end
end

------------------------------------------------------------
-- === Стратегия по одному инструменту ===
------------------------------------------------------------
local function strategy(class, sec)
    local base_price = nil
    log(string.format("[%s] v Старт стратегии", sec))
    local step = tonumber(getParamEx(class, sec, "sec_price_step").param_value)
    local scale = tonumber(getParamEx(class, sec, "sec_scale").param_value)
    while is_run do
        local price = tonumber(getParamEx(class, sec, "LAST").param_value)
        if not price or price <= 0 then
            coroutine.yield()
        else
            if not base_price then
                base_price = price
                log(string.format("[%s] Базовая цена: %.2f", sec, price))
            end

            while is_run do
                local cur = tonumber(getParamEx(class, sec, "LAST").param_value)
                if cur and cur > 0 then
                    local drop = (cur - base_price) / base_price
                    if drop <= -drop_trigger then
                        log(string.format("[%s] v Падение %.2f%% > Покупка", sec, drop * 100))
                        cur = fit(cur, step, scale)
                        send_order(class, sec, "B", cur)
                        local entry = cur

                        -- Контроль позиции
                        while is_run do
                            local now = tonumber(getParamEx(class, sec, "LAST").param_value)
                            if now then
                                local change = (now - entry) / entry
                                if change >= take_profit then
                                    log(string.format("[%s]  Тейк +%.2f%% > Продажа", sec, change * 100))
                                    now = fit(now, step, scale)
                                    send_order(class, sec, "S", now)
                                    base_price = now
                                    break
                                elseif change <= -stop_loss then
                                    log(string.format("[%s] Стоп %.2f%% > Продажа", sec, change * 100))
                                    now = fit(now, step, scale)
                                    send_order(class, sec, "S", now)
                                    base_price = now
                                    break
                                end
                            end
                            coroutine.yield()
                        end

                        log(string.format("[%s] Позиция закрыта, возвращаемся к ожиданию.", sec))
                        break
                    end
                end
                coroutine.yield()
            end
        end
        coroutine.yield()
    end
    log(string.format("[%s] x Стратегия завершена", sec))
end

------------------------------------------------------------
-- === Менеджер стратегий ===
------------------------------------------------------------
local Manager = {}
Manager.tasks = {}
function Manager:add_strategy(class, sec)
    local co = coroutine.create(function()
        strategy(class, sec)
    end)
    table.insert(self.tasks, {co = co, class = class, sec = sec})
    log(string.format("[%s] v Добавлена стратегия", sec))
end
function Manager:run_all()
    local check_interval = os.time()

    while is_run do
        -- Проверка капитала каждые 5 секунд
        if os.time() - check_interval >= 5 then
            Risk:check()
            check_interval = os.time()
        end

        for _, task in ipairs(self.tasks) do
            local co = task.co
            if coroutine.status(co) == "dead" then
                log(string.format("[%s] Перезапуск стратегии", task.sec))
                task.co = coroutine.create(function()
                    strategy(task.class, task.sec)
                end)
            else
                local ok, err = coroutine.resume(co)
                if not ok then
                    log(string.format("[%s] Ошибка: %s", task.sec, tostring(err)))
                    task.co = coroutine.create(function()
                        strategy(task.class, task.sec)
                    end)
                end
            end
        end
        sleep(300)
    end
    log("x Менеджер остановлен (риск или пользователь).")
end

------------------------------------------------------------
-- === Основной запуск ===
------------------------------------------------------------
function main()
    math.randomseed(os.time())
    log("v Запуск торгового менеджера с реальным риск-контролем...")
    Risk:init()

    for _, inst in ipairs(instruments) do
        Manager:add_strategy(inst.class, inst.sec)
    end

    Manager:run_all()
end

------------------------------------------------------------
-- === Завершение ===
------------------------------------------------------------
function OnStop()
    is_run = false
    log("v Скрипт остановлен пользователем.")
end
 
Такое запускать только с надписью - "на свой страх и риск, понимая, что вероятность потери 99.5%"

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

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

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

Далее, что еще более настораживает - это как организованы сделки. Почему-то отправка транзакции приравнивается к сделке. При этом транзакция лимитная, что никак не гарантирует исполнение. Она может быть отвергнута, может не исполнится, цена банально убежит от неё. Также, если за время отправки транзакции, цена ушла от цены внутрь, то ордер исполнится не по цене отправки. А в примере нет контроля сделок по ордеру, т.е. нет реальной цены исполнения. Также стоит учитывать, что исполнение транзакций может быть долгим. Отправили транзакцию, а ответ и сам ордер появились через минуты. За это время цена сходит десять раз туда и обратно. Т.е. организация работы с транзакциями должна быть транзакционной. Собственно поэтому она так и называется. Да и любое взаимодействие клиент-сервер должно быть таким: Запрос->ответ->принятие решения.

Это просто что первое бросилось в глаза. Напоминает пакеты для торговли через Web - интерфейс к командам, контроля работы ноль, К реальности малоприменимо.

Я могу понять, что это пример, показывающий работу корутин. Ок. Но к реальной торговле он малоприменим. Как это выглядит - дело вкуса. Как по мне - элегантности здесь нет, т.к. приходится сооружать длинные конструкции с большой вложенностью. Если вложенность кода больше 3-4 (а я здесь вижу аж 8), то уже стоит задуматься, что явно что-то не так.
 
С утра "чесались руки" слепил из того что было, хочу такой же с замыканиями и таблицей состояний сделать. Ну и сравнить. За анализ благодарю! А Страх и Смелость просто не обходимы!  :smile:  
 
Еще раз про код, для тех кто рискнет пробовать и разбираться, обратите внимание на замечания от Nikolay,  все они имеют место быть, всё что перечислено реально критично для боевого робота. Нужна конкретная переработка подхода + рабочая, практичная версия кода, да и сам подход желает лучшего. Я своей целью ставил демонстрацию возможного применения корутин, и максимально сложного написания аналога с помощью подхода использования замыканий на луа без сторонних библиотек. Это просто не кая демонстрация возможностей.
 
Теперь относительно самого кода: Что можно сделать и что важно знать?
  1. Отрицательные / нулевые цены — проверяем и игнорируем, пока не придёт реальная сделка (OnTrade).

  2. Пакеты приходят неупорядоченно — полагаемся на локальную метку - os.time(), при несоответствии времени пакета можно игнорировать «старые» пачки.

  3. sendTransaction ≠ сделка — сделать транзакционный цикл: (отправка → ожидание принятия → ожидание исполнения → таймаут → отмена).

  4. OnOrder / OnTrade — обязательные точки контроля. Без них нельзя корректно знать цену исполнения и состояние позиции?

  5. Закрытие позиций на стоп — реализовать опционально флаг CLOSE_POS_STOP.

  6. Адаптация к брокеру / версии QUIK — нужно проверить реальные поля, которые приходят в OnOrder / OnTrade от брокера, и использовать парсер.

  7. Обратите внимание что в этом подходе есть скрытые зависимости и они могут вносить свою лепту.

 

Как поступлю. Адаптируем “умный” блок (SmartOrder, reply, sdelka, обработчики событий QUIK) в уже готовый скрипт, чтобы он:

  • поддерживал открытие / закрытие позиций (лонг и шорт),

  • корректно работал через callbacks,

  • не зависел от ожиданий (основной поток не блокируется),

  • сохранял информацию о транзакциях и сделках в reply / sdelka,

  • и синхронизировался с реальными ордерами QUIK.

Что изменилось и почему это круто, судите сами;
БылоСтало
send_and_wait с циклом ожиданияsend_order_async — не блокирует поток, обрабатывает ответ асинхронно
Прямые проверки result_msgПереход на централизованное хранилище SmartOrder.pool
Потеря информации о транзакцииВся история сохраняется в reply и sdelka
Не обновлялась позицияПозиции обновляются автоматически при OnTrade
Возможна гонка данныхКаждая транзакция отслеживается независимо через свой trans_id
 
Цитата
VPM написал:
Я своей целью ставил демонстрацию возможного применения корутин, и максимально сложного написания аналога с помощью подхода использования замыканий на луа без сторонних библиотек
Какая-то странная позиция по процедурному подходу. Я бы сказал, что подход корутин сложен, т.к. написан с большой вложенностью, не очевидные выходы и входы. банально понятьт в каком месте сейчас находится алгоритм сложно.

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

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

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

local function check_chng(ctx, cur)
   if (cur or 0) <= 0 then return end
   if (ctx.base_price or 0) == 0 then return end
   local drop = (cur - ctx.base_price) / ctx.base_price
   if drop <= -ctx.drop_trigger then
       return drop
   end
   if drop >= ctx.drop_trigger then
       return drop
   end
end


local function process_chng(ctx)
   local cur   = tonumber(getParamEx(ctx.class, ctx.sec, "LAST").param_value)
   if (cur or 0) <= 0 then return end
   local drop  = check_chng(ctx, cur)
   if drop < 0 then
       log(string.format("[%s] v Падение %.2f%% > Покупка", ctx.sec, drop * 100))
       cur = fit(cur, ctx.step, ctx.scale)
       send_order(ctx.class, ctx.sec, "B", cur)
       ctx.entry = cur
       return true
   end
end


local function process_close(ctx)
   if (ctx.entry or 0) == 0 then return end
   local now = tonumber(getParamEx(ctx.class, ctx.sec, "LAST").param_value)
   if (now or 0) <= 0 then return end
   local change = (now - ctx.entry) / ctx.entry
   if change >= ctx.take_profit then
       log(string.format("[%s]  Тейк +%.2f%% > Продажа", ctx.sec, change * 100))
       now = fit(now, ctx.step, ctx.scale)
       send_order(ctx.class, ctx.sec, "S", now)
       ctx.base_price = now
       return true
   elseif change <= -stop_loss then
       log(string.format("[%s] Стоп %.2f%% > Продажа", ctx.sec, change * 100))
       now = fit(now, ctx.step, ctx.scale)
       send_order(ctx.class, ctx.sec, "S", now)
       ctx.base_price = now
       return true
   end
end


local function strategy(class, sec)


   log(string.format("[%s] v Старт стратегии", sec))


   local ctx = {
       sec             = sec,
       class           = class,
       step            = tonumber(getParamEx(class, sec, "sec_price_step").param_value),
       scale           = tonumber(getParamEx(class, sec, "sec_scale").param_value),
       drop_trigger    = drop_trigger,
       base_price      = nil,
       entry           = nil,
       take_profit     = nil
   }


   local step = 1


   local steps = {}


   steps[1] = function()
       local price = tonumber(getParamEx(class, sec, "LAST").param_value)
       if not price or price <= 0 then
           return
       end
       if not ctx.base_price then
           ctx.base_price = price
           log(string.format("[%s] Базовая цена: %.2f", sec, price))
           step = step + 1
       end
   end


   steps[2] = function()
       if not ctx.base_price then
           step = step - 1
           return
       end
       if process_chng(ctx) then
           step = step + 1
       end
   end


   steps[3] = function()
       if process_close(ctx) then
           log(string.format("[%s] Позиция закрыта, возвращаемся к ожиданию.", ctx.sec))
           step = step - 1
       end
   end


   return function()


       if not steps[step] then
           log(string.format("[%s] x Стратегия завершена", sec))
           return 0
       end


       steps[step]()


   end
end
 
Nikolay,  Супер подход, на каждом шаге процедура, так? Я даже не задумывался о такой возможности.
 
Разбираюсь с жизненным циклом заявки, наткнулся на подход Atomic Order Lifecycle (AOL) представляет собой последовательность этапов, через которые проходит ордер в торговой системе. Все этапы жизненного цикла ордера — от его создания и отправки до исполнения, таймаута или отмены — должны быть атомарными, т.е. все операции должны завершаться

* либо успехом (ордер выполнен),
* либо ошибкой (ордер отменён или отказан).

Это гарантирует, что трейдер или торговая система всегда знает, в каком состоянии находится ордер и что с ним происходит.

Процесс жизненного цикла ордера в торговой системе можно разбить на следующие этапы:

  1. Создание ордера

  2. Отправка ордера

  3. Ожидание принятия

  4. Ожидание исполнения

  5. Исполнение

  6. Таймаут или отказ

  7. Отмена ордера

Правильная обработка каждого из этих этапов и корректная реакция на возможные ошибки позволяют эффективно управлять ордерами и минимизировать риски на финансовых рынках. Но что делать в ситуации когда нет атомарной гарантии "all-or-nothing":   ПРОБЛЕМА: Частичное исполнение нарушает атомарность? Или другая,  ПРОБЛЕМА: Разные идентификаторы на разных этапах? Непонятно?

 
А если жизненный цикл (путь), расписать по шагам в конкретные процедуры (как Nikolay, ) подход сам напрашивается. И что делать с ожиданиями? Нужен фоновый подход?
 
Ожидания - это просто проверка флагов. Например, ожидание появления ордера в таблице ордеров - это проверка, что пока еще ордер на найден. А если не найден, то ищем. нашли - переходим к следующему шагу.
Пока не найден - остаемся в этом же состоянии.

Также и с любыми ожиданиями. В этом же примере есть ожидание получения текущей цены (хоть и тривиальное). Пока не получено остаемся там же.
 
Как адаптировать AOL к реальности?

ПроблемаТеория AOLРеальный подход
Частичное исполнениеНарушает атомарностьСчитать атомарным весь ордерный контекст, а не сделку
Разные IDОдин идентификаторВвести сопоставление ID между уровнями
Перезапуск / сбойНе допускаетсяВосстанавливать состояние из текущих таблиц QUIK
Ошибки транзакцийОткат всей операцииРеагировать по статусам OnTransReply и отменять при status > 3
ТаймаутНет отдельного понятияРеализовать вручную (watchdog coroutine)
┌──────────────┐
│ sendTransaction()
└──────┬───────┘
                 │ TRANS_ID
                ▼
┌──────────────┐
│ OnTransReply(status=0..1)
└──────┬───────┘
                 │ order_num
                ▼
┌──────────────┐
│ OnOrder(update)
└──────┬───────┘
                 │ trade_num
                ▼
┌──────────────┐
│ OnTrade() — исполнение
└──────┬───────┘
                 │
                ▼
┌──────────────┐
│ FILLED / CANCELLED
└──────────────┘
 
Тогда, Atomic Order Lifecycle (AOL) контроллер для QUIK на Lua.

Идея: мы создаём отдельный модуль OrderManager, который обеспечивает атомарный жизненный цикл ордера независимо от стратегии.
Стратегия просто говорит: «открыть лонг / шорт», а менеджер следит за статусом, частичным исполнением, таймаутами и отменами. Следовательно:
[*]

Вся логика отправки, ожидания, исполнения, таймаута и отмены — централизована в OrderManager.

[*]

Стратегия просто делает запрос на ордер и получает атомарное событие исполнения.

[*]

Поддерживается частичное исполнение, таймаут, отмена.

[*]

Состояние ордера можно сохранить на диск и восстановить при перезапуске.

 
1. Структура данных ордера.
Код
OrderManager = {
    pool = {},       -- все активные ордера по TRANS_ID
    timeout_sec = 10 -- время ожидания исполнения
}
Каждый ордер хранит:
Код
-- контекст ордера
local order_ctx = {
    trans_id   = "12345", -- уникальный локальный ID
    class      = "TQBR",
    sec        = "SBER",
    operation  = "B",     -- B / S
    price      = 123.45,
    qty        = 1,
    status     = "NEW",   -- NEW / SENT / PARTIALLY_FILLED / FILLED / CANCELLED / REJECTED / TIMEOUT
    order_num  = nil,     -- номер заявки на сервере QUIK
    trades     = {},      -- массив исполненных частей
    timestamp  = os.time()
}
2. Отправка ордера (атомарный цикл).
Код
function OrderManager:send(order)
    local trans_id = tostring(math.random(1000000,9999999))
    order.trans_id = trans_id
    order.status = "NEW"
    order.timestamp = os.time()
    self.pool[trans_id] = order

    local txn = {
        ACCOUNT     = ACCOUNT,
        CLIENT_CODE = CLIENT_CODE,
        CLASSCODE   = order.class,
        SECCODE     = order.sec,
        ACTION      = "NEW_ORDER",
        OPERATION   = order.operation,
        PRICE       = tostring(order.price),
        QUANTITY    = tostring(order.qty),
        TYPE        = "L",
        TRANS_ID    = trans_id
    }

    local result = sendTransaction(txn)
    if result ~= "" then
        log(string.format("[%s] x Ошибка отправки: %s", order.sec, result))
        order.status = "REJECTED"
        return false
    else
        order.status = "SENT"
        log(string.format("[%s] v Ордер отправлен (TRANS_ID=%s)", order.sec, trans_id))
        return true
    end
end
3. Колбэк OnTransReply.
Код
function OnTransReply(tr)
    local ctx = OrderManager.pool[tr.trans_id]
    if not ctx then return end

    if tr.status == 3 then
        ctx.order_num = tr.order_num
        log(string.format("[%s] v Ордер принят биржей (order_num=%s)", ctx.sec, ctx.order_num))
    elseif tr.status >= 4 then
        ctx.status = "REJECTED"
        log(string.format("[%s] x Ордер отклонен: %s", ctx.sec, tr.result_msg))
        OrderManager.pool[tr.trans_id] = nil
    end
end
4. Колбэк OnOrder (обновление состояния).
Код
function OnOrder(ord)
    for _, ctx in pairs(OrderManager.pool) do
        if ctx.order_num and ctx.order_num == ord.order_num then
            ctx.balance = ord.balance
            if ctx.balance == 0 then
                ctx.status = "FILLED"
                log(string.format("[%s] v Ордер полностью исполнен", ctx.sec))
            elseif ord.balance < ctx.qty then
                ctx.status = "PARTIALLY_FILLED"
                log(string.format("[%s] v Частичное исполнение: %d/%d", ctx.sec, ctx.qty-ord.balance, ctx.qty))
            end
        end
    end
end
5. Колбэк OnTrade (фиксируем сделки).
Код
function OnTrade(trade)
    for _, ctx in pairs(OrderManager.pool) do
        if ctx.order_num and ctx.order_num == trade.order_num then
            table.insert(ctx.trades, {
                trade_num = trade.trade_num,
                qty       = trade.qty,
                price     = trade.price,
                dir       = trade.flags % 2 == 0 and "B" or "S"
            })
        end
    end
end
 
6. Таймаут и отмена ордера.
Код
function OrderManager:check_timeouts()
    local now = os.time()
    for trans_id, ctx in pairs(self.pool) do
        if ctx.status == "SENT" and now - ctx.timestamp > self.timeout_sec then
            ctx.status = "TIMEOUT"
            log(string.format("[%s] x Таймаут ордера, отправляем отмену", ctx.sec))
            self:cancel(ctx)
        end
    end
end

function OrderManager:cancel(ctx)
    if not ctx.order_num then return end
    local txn = {
        ACCOUNT     = ACCOUNT,
        CLIENT_CODE = CLIENT_CODE,
        CLASSCODE   = ctx.class,
        SECCODE     = ctx.sec,
        ACTION      = "KILL_ORDER",
        ORDER_KEY   = ctx.order_num,
        TRANS_ID    = tostring(math.random(1000000,9999999))
    }
    sendTransaction(txn)
    ctx.status = "CANCELLED"
end
7. Использование стратегии.   Теперь стратегия не работает напрямую с sendTransaction, она обращается к менеджеру:
Код
local ctx = {
    sec = "SBER",
    class = "TQBR",
    base_price = nil,
    drop_trigger = 0.002,
    qty = 1
}

local function strategy_tick()
    local price = tonumber(getParamEx(ctx.class, ctx.sec, "LAST").param_value)
    if not price or price <= 0 then return end

    if not ctx.base_price then
        ctx.base_price = price
        return
    end

    local change = (price - ctx.base_price)/ctx.base_price
    if change <= -ctx.drop_trigger then
        OrderManager:send({
            class = ctx.class,
            sec = ctx.sec,
            operation = "B",
            price = price,
            qty = ctx.qty
        })
    elseif change >= ctx.drop_trigger then
        OrderManager:send({
            class = ctx.class,
            sec = ctx.sec,
            operation = "S",
            price = price,
            qty = ctx.qty
        })
    end
end
 
Тогда Архитектура:
1.  Trading Strategy.
    (логика входа/выхода, анализ данных, решения)
+-------------------------------------------------------------+

2.  OrderManager (AOL).
   (отправка, принятие, исполнение, таймаут, отмена)
+-------------------------------------------------------------+
3.  RiskManager:
    (контроль капитала, просадки, стопы)
+-------------------------------------------------------------+
4.  QUIK API Layer:
   ( DataManager:  getParamEx, ...
   sendTransaction,
   OnTransReply, OnOrder, OnTrade)  
 
Цитата
VPM написал:
А если жизненный цикл (путь), расписать по шагам в конкретные процедуры (как Nikolay, ) подход сам напрашивается. И что делать с ожиданиями? Нужен фоновый подход?
У меня все гораздо проще построено.
Для заявок и стоп-заявок одинаково.
Есть три таблицы
----------------------
Код
Tnt={[0]=0}; --таблица активных транзакций  содержит trans_id,jts
Tor={[0]=0}; --таблица активных заявок 1-trans_id 2- номер в таблице QUIK 0-если выставляется , минус на удаление
Tso={[0]=0}; --таблица активных стоп-заявок 1-trans_id 2- номер в таблице QUIK 0-если выставляется , минус на удаление
Любая транзакция записывается в таблицу транзакций,
Эта таблица обрабатывается в OnTransReply
Если транзакция не прошла или прошла, то она удаляется из Tnt. Таким образом мы всегда знаем сколько транзакций отправили но не получили ответ.
-----------------------
Если была ошибка, то отменяются действия по ней.
Например транзакция на действия над существующей заявкой (удаляем или переставляем).
----------------------
Если выставляется новая заявка, то она записывается в таблицу активных, когда поступит сообщение об успешной отправки транзакции.
------------------------
Если пришло сообщение об исполнении или удалении то она удаляется из таблицы активных.
======================
Вот и весь алгоритм .
-------------------------------
вот рабочий фрагмент конечного автомата, который обрабатывает эти три колбека
Код
elseif m==2 then --onOrder
local N=Tor[0]; local flags=t.flags local flag=flags&1; local num=t.order_num; local id=t.trans_id;
for i=1,N do
   local s=Tor[i]; --s,id,js,num
   if s[4]==num or (id~=0 and s[2]==id) then
      if flag==0 then -- заявка не активна удаляем из списка активных если она есть в списке
      if N>1 then Tor[i]=Tor[N] end Tor[N]=nil; Tor[0]=N-1;
      else  --заявка активная записываем ее номер
      s[4]=num --записываем номер новой заявки по транзакции
      if s[3]==0 then s[3]=Nor; Nor=Nor+1 end
      end
   break;
   end  -- заявка в списках
   end
   if flag==1 and id==0 then
   N=N+1; Tor[N]={t.sec_code,0,Nor,num}; Nor=Nor+1 Tor[0]=N;--- активная заявка не найдена заявка выставлена не из скрипта создаем ее
end;   
elseif m==3 then --onStopOrder
local   N=Tso[0]; local flag=t.flags&1;    local num=t.order_num; local id=t.trans_id;
for i=1,N do
   local s=Tso[i]; --s,id,js,num
   if s[2]==id or s[4]==num then
   if flag==0 then -- заяка не активна удаляем из списка активных если она есть в списке
   if N>1 then Tso[i]=Tso[N] end Tso[N]=nil; Tso[0]=N-1;
   else  --заявка активная записываем ее номер
   s[4]=num --записываем номер новой заявки по транзакции
   if s[3]==0 then s[3]=Nso; Nso=Nso+1 end
   end
   break;
   end; -- таблица заявок,стоп-заявок инструмента
end
if flag==1 and id==0 then
N=N+1; Tso[N]={t.sec_code,0,Nso,num}; Nso=Nso+1 Tso[0]=N;--- активная заявка не найдена заявка выставлена не из скрипта создаем ее
end
elseif m==4 then
local x=t.status local msg=t.result_msg;
if x==3 or 2>x or x==15 then return end
   ---удаляем ошибочные транзакции сделок
   message("status="..x..": транзакция "..t_mes[x+1]..">"..msg, 3)
local id=t.trans_id; local s,js;   local N=Tor[0]; --ищем по заявкам
for i=1,N do s=Tor[i]; --s,id,js,num
   if s[2]==id then  js=s[3];
   if js==0 then if N>1 then Tor[i]=Tor[N] end Tor[N]=nil; Tor[0]=N-1; return  --отменяем выставление
   else s[3]=-js; return end -- отменяем удаление
   end
   end
N=Tso[0]; --ищем по стоп-заявкам
for i=1,N do local s=Tso[i]; --s,id,js,num
   if s[2]==id then local js=s[3];
   if js==0 then if N>1 then  Tso[i]=Tso[N]  end Tor[N]=nil; Tor[0]=N-1; return  --отменяем выставление
   else s[3]=-js; return end -- отменяем удаление
   end
end   
 
Если вынести повторяющуюся часть кода в функции (для чего они и придуманы):
Код
function order(t,Tor,Nor)
local N=Tor[0]; local flags=t.flags local flag=flags&1; local num=t.order_num; local id=t.trans_id;
for i=1,N do
   local s=Tor[i]; --s,id,js,num
   if s[4]==num or (id~=0 and s[2]==id) then
      if flag==0 then -- заявка не активна удаляем из списка активных если она есть в списке
         if N>1 then Tor[i]=Tor[N] end Tor[N]=nil; Tor[0]=N-1;
   else  --заявка активная записываем ее номер
      s[4]=num --записываем номер новой заявки по транзакции
      if s[3]==0 then s[3]=Nor; Nor=Nor+1 end
   end
   break;
   end  -- заявка в списках
   end
   if flag==1 and id==0 then
   N=N+1; Tor[N]={t.sec_code,0,Nor,num}; Nor=Nor+1 Tor[0]=N;   --- активная заявка не найдена заявка выставлена не из скрипта создаем ее
   end;   -- таблица заявок,стоп-заявок инструмента
end

function transOrd(t,Tor)
      local id=t.trans_id; local s,js;
   for i=1,N do s=Tor[i]; --s,id,js,num
      if s[2]==id then  js=s[3];
      if js==0 then if N>1 then Tor[i]=Tor[N] end Tor[N]=nil; Tor[0]=N-1; return  --отменяем выставление
         else s[3]=-js; return end -- отменяем удаление
         end
   end
end
то конечный автомат для этих колбеков будет совсем простой:
Код
elseif m==2 then --onOrder
order(t,Tor,Nor);
elseif m==3 then --onStopOrder
order(t,Tso,Nso);
elseif m==4 then
local x=t.status local msg=t.result_msg;
if Log then Log:write("status="..x..": транзакция "..t_mes[x+1]..">"..msg.."\n"); Log:flush();end
if x==3 or 2>x or x==15 then return end
---удаляем ошибочные транзакции
message("status="..x..": транзакция "..t_mes[x+1]..">"..msg, 3)
transOrd(t,Tor);
transOrd(t,Tso);

Вот и все.
 
Цитата
VPM написал:
OnTransReply
OnTransReply может не вернуть номер ордера. Более того, он может прийти после первого OnOrder.

Так что он нужен только для проверки ошибка транзакции. Для получения номера ордера необходимо обращаться к таблице ордеров по номер транзакции.

С таймоутами тоже аккуратней, т.к. время ответа на транзакцию вполне может достигать минут. Все зависит от того, как работает сервер брокера. Отправили транзакцию, а ответы и записи в таблице ордеров появляются через 10 минут. Понятно, что это редкое явление, но достаточно одного такого случая, чтобы алгоритм наставил дублей ордеров, если нет проверок.

Ну и главное - колбек дело хорошее, но он не гарантирован.
 
nikolz,  Хотя читать тяжеловато логика понятна, решение "класс". Не понял только как в Вашем варианте поступать с "частичное исполнение" и восстановления текущих ордеров и стоп-заявок при запуске  или сбоях из данных QUIK?

Да вон еще Nikolay, страстей написал? Получается и периодическую сверку запускать нужно? Ну либо вообще от подхода отказаться?
 
Обработка OnTransReply.  Ожидание ответа в цикле main приводит к проблеме - зависанию. Асинхронная модель: реагирует на события в колбэках, не ожидая их в основном потоке не блокирует его?
 
Вот и пришли с чего начали. Для создания стабильно работающего AOL-контроллера необходимо переработать его архитектуру в соответствии с асинхронной моделью?
 
Lua-модуль,  реализующий атомарный жизненный цикл заявок (Atomic Order Lifecycle) в QUIK.

Должен быть построен по принципу:

* независим от стратегии,
* обрабатывает все три потока событий (OnTransReply, OnOrder, OnStopOrder),
* поддерживает атомарность и согласованность данных,
* частичное исполнение,
* восстановления текущих ордеров и стоп-заявок при запуске  или сбоях из данных таблиц QUIK,
* проводить периодическую сверку с данными таблиц  QUIK,
* логирование,
* таймауты.

Для реализации стабильной работы AOL-контроллера необходимо чтоб его архитектура соответствовала асинхронной модели? Я ни чего не упустил?
 
Нет все таки упустил.

Нужна машина состояний - FSM (finite state machine) — для описания переходов состояний ордера.
Очередь событий или coroutine - менеджер обработки событий, следовательно  Асинхронную модель.
 
Ну и как Вам задачка для обычного пользователя QUIK!  :unamused:  
Страницы: Пред. 1 ... 6 7 8 9 10 След.
Читают тему
Наверх