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

Страницы: Пред. 1 ... 10 11 12 13 14
RSS
Система принятия решений и/или Нечеткая логика(FuzzyLogic), Нечеткая логика или Система принятия решений в трейдинге
 
Согласен, картину страшную описали для авто торговли. Думал заменить подход, теперь верну.
Код
----- Получаем цену последней сделки с графика цены 
      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

Каждый слой делает только своё.
Попробую реализовать. Не могу сказать что упростил, но "руки чешутся" по пробовать.
 
Радуйтесь, что в Lua нет понятия интерфейса, как концепции языка. Вот где начинается веселье. Но все же возникает ощущение, что Вы всегда уходите в процесс, вместо результата.
Углубится и написать фреймворк иногда полезно. Но все же, обычно, это процесс долгий. А для получения результата сейчас лучше пойти намного более простым путем.
 
Цитата
Nikolay написал:
А для получения результата сейчас лучше пойти намного более простым путем.
А чем же он проще? Все модули нужны везде?
 
Разделение ответственности, на примере создания индикатора. Единый принцип архитектуры торговой системы в 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
 
Вы забыли данные из других источников. Индикаторы - это настолько малая часть и почти бесполезная часть, что её проще реализовать отдельно. Основное же использование - это скрипты, зачастую без интерфейса, а просто выполняющие расчеты, сохраняющие результаты в файлы. Так банально проще, т.к. анализ данных проще делать в других системах, в том же Python, а не в индикаторах Квика.
Даже если это торгующий скрипт, то не ясно зачем ему график. График нужен человеку, для самоуспокоения, что всё правильно.

Еще важно, что иногда набор данных включает очень далекую историю, которую Квик просто не может предоставить. Например, данные баров выгружались за 5 лет. На основе этих данных строится свой источник данных с ТФ, например, равный 12 минут, 3 часа, 5 часов и т.д. Квик просто не дает такие данные. И уже этот источник поступает на вход алгоритма. Этот источник будет состоять из двух частей - кэшированные, верифицированные данные (что очень важно) и текущие данные реального времени.

Также не редко у брокера бывают проблемы с данными. Например у меня один брокер три дня подряд пропускал данные за прошлый день. День прошел - данные были. Наступает следующий день - данных за прошлый день нет. Переподключения, сбросы - ничего не решает вопрос, т.к. это ошибка на сервере брокера. И так каждый день, возникает скользящая дыра в данных. Можно сказать - зачем такой брокер - ответ прост: я такое наблюдал у многих брокеров.

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

Но давайте по порядку.
Цитата
Nikolay написал:
Вы забыли данные из других источников.
  • Я их умышленно обошел, и задачу свел к получению исторических данных из QUIK,  предполагая что они уже есть, и показу единообразия в подходах. Запись в файл и чтения из него, здесь сводится к единой структуре данных. По сути заданию некоего индекса.
  • Расчет  local breakout = f( index, Settings ) , если не нежны графики? выводим в скрипте f( index, Settings, ds ). По сути сменили источник с сохранением архитектуры, ровно то что Вы описали выше. Давно уже задался задачей сохранения архитектуры при изменяемых модулях.
 
Концептуально, такую архитектуру можно свести к последовательным шагам взросления системы, в стиле боевого 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.1. Контракт SignalEngine (минимальный).
Код
local SignalEngine = {
    factors = {},
    regime = "unknown",
    score = 0
}

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. "Индикатор не принимает решений".

| Слой              | Отвечает за |
| ------------------ | ----------------- |
| Indicator         | данные        |
| SignalEngine  | оценку         |
| Trader            | ордера         |

-- Trading Core (пример концепции)
Код
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

-- 4.2. Режимно-зависимая (Regime-aware) фильтрация.
Код
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
И ни какой магии, типа машинного обучения или нейросетей!  А главное безопасное изменение и замена модулей. Хочется думать так, а жизнь покажет, что опять не учитываю.  :smile:  
Страницы: Пред. 1 ... 10 11 12 13 14
Читают тему
Наверх