Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
Пользователь
Сообщений: Регистрация: 15.06.2023
03.01.2026 16:31:58
Согласен, картину страшную описали для авто торговли. Думал заменить подход, теперь верну.
Код
----- Получаем цену последней сделки с графика цены
local I = getNumCandles(tag.price)
while I==nil or I==0 do
sleep (100)
I = getNumCandles(tag.price)
end
if I<=4 then toLog(log, 'Недостаточно бар для продолжения работы ' .. tostring(I) ) return end
Вывод. Получение данных сводится к отдельной задаче. Корректность данных можно свести к общим подходам так как данные сводим в таблицы. Так? В реализации подхода рассмотренного мною, без машины состояний вообще не вариант. State Machine - система состояний (FSM), будет отслеживать состояние робота — в состоянии ли он торгов, или ждёт обновлений данных или ждёт сигналов и т.д.
Итог. правильная модель (минимальный фундамент): -> рынок — это функция времени (ввести понятие дискретности "квант времени"), -> стратегия — функция состояния (снимок, срез в заданный момент).
Принципы. 1. Источник истины — время 2. Минимальный ТФ > двигатель времени 3. Остальные ТФ > состояния мира 4. Стратегия никогда не входит в источники данных 5. Индикаторы — просто ещё один Source.
Каркас - итоговый поток:
DataSource v TimeEngine (tick - "квант времени") v WorldState (snapshot - снимок) v Strategy (signal) v SignalFilter v PositionManager v FSM v TradeAdapter v Equity
Каждый слой делает только своё. Попробую реализовать. Не могу сказать что упростил, но "руки чешутся" по пробовать.
Пользователь
Сообщений: Регистрация: 27.01.2017
03.01.2026 16:37:39
Радуйтесь, что в Lua нет понятия интерфейса, как концепции языка. Вот где начинается веселье. Но все же возникает ощущение, что Вы всегда уходите в процесс, вместо результата. Углубится и написать фреймворк иногда полезно. Но все же, обычно, это процесс долгий. А для получения результата сейчас лучше пойти намного более простым путем.
Пользователь
Сообщений: Регистрация: 15.06.2023
03.01.2026 16:44:20
Цитата
Nikolay написал: А для получения результата сейчас лучше пойти намного более простым путем.
А чем же он проще? Все модули нужны везде?
Пользователь
Сообщений: Регистрация: 15.06.2023
09.01.2026 12:25:30
Разделение ответственности, на примере создания индикатора. Единый принцип архитектуры торговой системы в QUIK.
Что общего в получении исторической информации в QUIK разными способами. Ответ очевиден - хранение информации в массивах данных и извлечения ее с помощью параметра "index".
1. OnCalculate. Задается на прямую QUIK. 2. getCandlesByIndex. Получаем количество свечей "==" local index = getNumCandles(CFG.tags.price), индекс текущей свечи. 3. CreateDataSource. По факту тоже самое, получаем уже размер массива Size() "== index". Зачем введены разные смыслы, остается загадкой?
Понимание этого, приводит к пониманию принципа построения единой архитектуры для разных источников данных. Принципиальная ИТОГОВАЯ АРХИТЕКТУРА: [ QUIK Chart ] -> [ Indicator/ CandlesByIndex/DataSource] -> [ SignalEngine ] -> [ Regime Filter ] -> [ Trading Core ].
Коротко почему это наиболее правильный подход: * безопасно для QUIK; * расширяемо; * легко диагностировать; * нет переобучения внутри источника (индикатора); * можно менять стратегию без переписывания индикаторов. Пример, индикатор источник данных, перенос ответственности.
Код
local path = getWorkingFolder()
local f;
local save = {
position = 0,
d = 0,
t = 0
}
Settings={}
Settings.Name = "*BreakoutIndicator"
Settings.period = 5
Settings.k = 1
Settings.wid = 0
function Init()
local Cached = dofile(path .. "\\cached.lua");
f = BreakoutIndicator()
Cached=nil
Settings.line = {
{Name = "BUY", Type = TYPE_TRIANGLE_UP, Width = 1, Color = RGB(0, 0, 255) },
{Name = "SELL", Type = TYPE_TRIANGLE_DOWN, Width = 1, Color = RGB(255, 0, 128) },
{Name = "save.upper", Type = TYPE_BAR , Width = 1, Color = RGB(0, 128, 255) },
{Name = "save.lower", Type = TYPE_BAR, Width = 1, Color = RGB(255, 0, 128) },
{Name = "SL", Type =TYPE_DASH, Width = 1, Color = RGB(255, 0, 0) }
}
return #Settings.line
end
function OnCalculate(index)
-- ====== ТЕКУЩИИ ЗНАЧЕНИЯ ======
local position1 = save.position
local d1 = save.d
local t1 = save.t
-- ====== Расчет ЗНАЧЕНИЙ ======
local breakout = f( index, Settings )
-- ====== Обработка ======
local newbar = (breakout and d1 ~= breakout.d or breakout.t ~= t1)
save.position = breakout and breakout.position
save.d = breakout and breakout.d
save.t = breakout and breakout.t
--====== RETURN LINES ======
if Settings.wid == 2 then
return nil,nil,nil,nil, breakout and breakout.volatilityRatio > 1 and breakout.volatilityRatio or nil
end
return breakout and (d1 ~= breakout.d or breakout.t ~= t1) and breakout.position == 1 and breakout.entryLevel or nil, -- BUY
breakout and (d1 ~= breakout.d or breakout.t ~= t1) and breakout.position == -1 and breakout.entryLevel or nil, -- SELL
breakout and breakout.position == 1 and breakout.upperPivot or nil,
breakout and breakout.position == -1 and breakout.lowerPivot or nil,
breakout and breakout.position ~= 0 and breakout.slLevel or nil
end
Пользователь
Сообщений: Регистрация: 27.01.2017
09.01.2026 12:56:05
Вы забыли данные из других источников. Индикаторы - это настолько малая часть и почти бесполезная часть, что её проще реализовать отдельно. Основное же использование - это скрипты, зачастую без интерфейса, а просто выполняющие расчеты, сохраняющие результаты в файлы. Так банально проще, т.к. анализ данных проще делать в других системах, в том же Python, а не в индикаторах Квика. Даже если это торгующий скрипт, то не ясно зачем ему график. График нужен человеку, для самоуспокоения, что всё правильно.
Еще важно, что иногда набор данных включает очень далекую историю, которую Квик просто не может предоставить. Например, данные баров выгружались за 5 лет. На основе этих данных строится свой источник данных с ТФ, например, равный 12 минут, 3 часа, 5 часов и т.д. Квик просто не дает такие данные. И уже этот источник поступает на вход алгоритма. Этот источник будет состоять из двух частей - кэшированные, верифицированные данные (что очень важно) и текущие данные реального времени.
Также не редко у брокера бывают проблемы с данными. Например у меня один брокер три дня подряд пропускал данные за прошлый день. День прошел - данные были. Наступает следующий день - данных за прошлый день нет. Переподключения, сбросы - ничего не решает вопрос, т.к. это ошибка на сервере брокера. И так каждый день, возникает скользящая дыра в данных. Можно сказать - зачем такой брокер - ответ прост: я такое наблюдал у многих брокеров.
Т.е. здесь вопрос консистентности данных - и это самый важный вопрос, намного важнее архитектуры. Точнее это требование очень сильно влияет на реализацию архитектуры. Т.к. если об этом не думать, то всегда возникнет ситуация когда алгоритм будет оперировать неактуальными данными и все его решения будут из-за этого под большим вопросом.
Пользователь
Сообщений: Регистрация: 15.06.2023
09.01.2026 14:50:56
Nikolay, Вы подняли очень важную проблему "вопрос консистентности данных", все мы сталкиваемся, так как повлиять на ситуацию целиком мы не можем, но должны учитывать эти особенности, должны применять какие то инженерные подходы в обеспечении безопасности. На мой взгляд проблема слишком большая, и требует отдельного обсуждения, может даже выноса в отдельную ветку (если еще не вынесена).
Но давайте по порядку.
Цитата
Nikolay написал: Вы забыли данные из других источников.
Я их умышленно обошел, и задачу свел к получению исторических данных из QUIK, предполагая что они уже есть, и показу единообразия в подходах. Запись в файл и чтения из него, здесь сводится к единой структуре данных. По сути заданию некоего индекса.
Расчет local breakout = f( index, Settings ) , если не нежны графики? выводим в скрипте f( index, Settings, ds ). По сути сменили источник с сохранением архитектуры, ровно то что Вы описали выше. Давно уже задался задачей сохранения архитектуры при изменяемых модулях.
Пользователь
Сообщений: Регистрация: 15.06.2023
09.01.2026 19:12:45
Концептуально, такую архитектуру можно свести к последовательным шагам взросления системы, в стиле боевого QUIK-safe. Важных, самостоятельных 4 - логических слоя, как в профессиональных торговых платформах:
ШАГ 1. Цель QUIK-safe. Приведение индикатора к QUIK-safe стилю, для QUIK это будет означать: 1. никаких глобальных переменных; 2. никаких "висящих" состояний; 3. чёткий lifecycle: "init -> bar -> return"; 4. "индикатор не знает про торговлю, только сигналы".
1.1. Новая контрактация индикатора (правильный подход). "Индикатор = детектор структуры рынка, не стратегии". Он должен: * принимать index, settings, datasourсe; * хранить локальный state; * возвращать расчетные значения (для индикатора только линии).
ШАГ 2. Встраивание в SignalEngine. Теперь "SignalEngine — мозг, индикатор — сенсор".
2.2. Использование данных от индикатора (и это уже по взрослому).
Код
function SignalEngine:updateFromBreakout(buy, sell, support, resistance, volRatio)
local factor = {}
-- Фактор направления
factor.breakout =
buy and 1 or
sell and -1 or
0
-- Фактор структуры
factor.structure =
support and 0.5 or
resistance and -0.5 or
0
-- Фактор волатильности
factor.volatility = clamp((volRatio or 0) - 1, -1, 1)
self.factors.breakout = factor
end
ШАГ 3. Indicator / TradingCore (разделение ответственности). Правило №1. "Индикатор не принимает решений".
local Trader = {}
function Trader:process(signalScore, regime)
if regime == "flat" then return end
if signalScore > 0.7 then
self:buy()
elseif signalScore < -0.7 then
self:sell()
end
end
ШАГ 4. Auto-Regime + фильтры.
4.1. Определение режима.
Код
function SignalEngine:detectRegime(vol, adx)
if vol < 0.7 and adx < 15 then
return "flat"
elseif adx > 25 then
return "trend"
else
return "transition"
end
end
function SignalEngine:filterFactorsByRegime(regime)
if regime == "flat" then
self.factors.breakout.breakout = 0
elseif regime == "transition" then
self.factors.breakout.breakout = 0.5
end
end
-- 4.3. Финальный Score
Код
function SignalEngine:calcScore()
local score = 0
for _, f in pairs(self.factors) do
score = score + (f.breakout or 0) + (f.structure or 0)
end
self.score = clamp(score, -1, 1)
return self.score
end
И ни какой магии, типа машинного обучения или нейросетей! А главное безопасное изменение и замена модулей. Хочется думать так, а жизнь покажет, что опять не учитываю.