Rostislav Kudryashov
Rostislav Kudryashov личный блог
18 июля 2021, 09:49

Индикатор Загзаг для Quik'а - лучший демонстратор тренда (и волн Эллиота). Даром

Всем известен старинный индикатор Пробоя. Этот индикатор для Quik'а найдёте на smart-lab.ru/blog/704033.php
Когда-то по нему торговали, покупая на пробое вверх максимума предыдущих N баров и продавая на пробое минимума. Похоже, сегодня эта стратегия вышла из моды.

Возможен другой подход к отслеживанию экстремумов. Регистрировать пробой не на фиксированном числе баров (по ширине графика), но на фиксированном уходе цены от предыдущего экстремума (по высоте графика). Это столь же известный индикатор Зигзаг. Минимальный уход цены от предыдущего экстремума — размах зигзага, необходимый для смены тренда, я именую силой тренда.

Мало кто пытался строить торговую стратегию на основе Зигзага. Самое ходовое возражение против Зигзага — он не предсказывает тренд. Абсурд. Никто и ничто не предсказывает будущего!
Более того. Зигзаг даёт самое объективное и наглядное представление о чередовании трендов заданной силы.

Я некоторое время назад уже предлагал вниманию публики индикатор зигзага для Quik'а.
Сегодня я усматриваю в нём ряд недостатков, которые и намерен восполнить.
1. Вершины и впадины зигзага я строил по Close'м баров. Возможно, уместнее отмечать High и Low.
2. Силу тренда можно задавать абсолютным значением или относительным. При запуске индикатора задано относительное значение 0.001 (0.1%).
3. Дополнительно к экстремумам графика индикатор показывает точки пробоя коридора — точки поворота, где предыдущий тренд меняет направление. По этим точкам можно на глазок оценить прибыльность стратегии.
4. Чтобы оценивать прибыльность не на глазок, в индикатор встроен калькулятор, суммирующий прибыль сделок в точах поворота на левой части графика до момента запуска индикатора.
5. Все сделки в точках поворота на левой части графика выводятся в текстовый файл формата .csv, читаемый Excel'ом. Это позволяет получить график Equity.

Конечно, подход с High и Low для истории котировок не совсем однозначен. Если экстремумы текущего бара поглощают предыдущий, Зигзаг выбирает экстремум текущего бара по точке поворота, даже если этот экстремум гораздо слабее, чем в направлении предыдущего бара.
Это позволяет совершать сделку, не дожидаясь завершения бара.

На некоторых историях котировок индикатор Загзаг сулит самый заманчивый выигрыш, на других — сокрушительный проигрыш. Поставьте индикатор на минутках и развлекайтесь зрелищем.
Settings = {
  Name = "_Zigzag"
  ,Delta = 0.0 -- Абсолютная сила тренда в пунктах
  ,Rate  = 0.001 -- Относительная сила тренда
  ,Log   = "D:\\Zigzag.csv" -- лог-файл сделок
  ,line = {
    {Name = "Zigzag"
    ,Color = RGB(255,255,0) -- Жёлтый
    ,Type = TYPE_LINE
    ,Width = 1}
   ,{Name = "Lwr"
    ,Color = RGB (255,255,0) -- Жёлтый
    ,Type = TYPE_TRIANGLE_DOWN
    ,Width = 1}
   ,{Name = "Upr"
    ,Color = RGB (0,128,255) -- Тёмно-Голубой
    ,Type = TYPE_TRIANGLE_UP
    ,Width = 1}
  } -- BadConditions, CurSize, ClosePrev = nil
}   -- DataSourceInfo, Xtrs, DummyXtr, ScanNo = nil

function Msg()
  return Title() .." - параметры:"
    .."\nDelta - абсолютная сила тренда или 0;"
    .."\nRate - относительная сила тренда или 0."
    .."\nLog - лог-файл сделок на поворотах."
    .."\nСила - минимальный уход от экстремума"
    .."\n  для смены направления тренда."
end

---------
function AddXtr (bar) -- bar > #Xtrs[n].FinIdx
-- Код возврата: 0 - ничего не изменилось;
-- ±2 - замена последнего экстремума более сильным;
-- ±3 - добавление экстремума, противоположного.
  local node = Xtrs[#Xtrs]
  local xtr = node.FinVal
  if Trend() > 0 then
    local mn = Lwr (xtr)
    if L(bar) <= mn then
      node = NewNode (bar, L(bar))
      node.IniVal = math.min (mn, O(bar))
      table.insert (Xtrs, node)
      return -3
    end
    if H(bar) >= node.FinVal then
      node.FinIdx = bar
      node.FinVal = H(bar)
      return 2
    end
  else -- Trend() < 0
    local mx = Upr (xtr)
    if H(bar) >= mx then
      node = NewNode (bar, H(bar))
      node.IniVal = math.max (mx, O(bar))
      table.insert (Xtrs, node)
      return 3
    end
    if L(bar) <= node.FinVal then
      node.FinIdx = bar
      node.FinVal = L(bar)
      return -2
    end
  end -- if Trend() > 0
  return 0
end -- AddXtr()

function DateAndTime (i) -- в баре котировок
  return DataSourceInfo and DataSourceInfo.interval < 0 and
    string.format ("%04d.%02d.%02d", T(i).year, T(i).month, T(i).day) or
    string.format ("%04d.%02d.%02d %02d:%02d"
      ,T(i).year, T(i).month, T(i).day, T(i).hour, T(i).min)
end

function Lwr (val)
  return Settings.Delta > 0 and val - Settings.Delta or
    val * (1 - Settings.Rate)
end

function MakeDummy() -- Вместо нового экстремума или усиления старого
  local dummyXtr = NewNode (  Size(), C(Size()))
  SetValue (Size(), 1, C(Size()))
  local finVal = Xtrs[#Xtrs].FinVal -- IniVal - будущий поворот
  dummyXtr.IniVal = Trend() > 0 and Lwr (finVal) or Upr (finVal)
  SetValue (Size(), Trend() > 0 and 2 or 3, dummyXtr.IniVal)
  return dummyXtr
end      

function NewNode (bar, val)
  return { IniIdx = bar, IniVal = val, FinIdx = bar, FinVal = val }
end

function Title()
  return getDataSourceInfo().sec_code .."[".. Settings.Name .."]"
end

function ToString (no) -- параметры узла Зигзага
  if not no then no = #Xtrs end
  return "Xtrs[".. no .."]"
     .."\nIni ".. DateAndTime (Xtrs[no].IniIdx)
      .." ".. Xtrs[no].IniVal
     .."\nFin ".. DateAndTime (Xtrs[no].FinIdx)
      .." ".. Xtrs[no].FinVal
end

function Trend (idx)
  idx = idx or #Xtrs
  return Xtrs[idx].IniVal > Xtrs[idx-1].FinVal and 1 or -1
end

function Upr (val)
  return Settings.Delta > 0 and val + Settings.Delta or
    val * (1 + Settings.Rate)
end

------------
function Init()
  message (Msg())
  return #Settings.line
end

function OnCalculate (index)
  if BadConditions then return nil end
  if index == 1 then
    ScanNo = not ScanNo and 1 or ScanNo + 1
    if not Sub1() then -- BadConditions, CurSize, Xtrs,
      return nil       -- DummyXtr, DataSourceInfo
    end
  elseif C(index) == ClosePrev then
  elseif index >= CurSize then
    Sub2()
  end
  ClosePrev = C(index)
  return GetValue (index, 1), GetValue (index, 2), GetValue (index, 3)
end -- OnCalculate()

function OnChangeSettings()
  BadConditions, ClosePrev, DataSourceInfo, ScanNo = nil
  if Settings.Delta > 0 and Settings.Rate > 0 or
     Settings.Delta <= 0 and Settings.Rate <= 0 then
    BadConditions = true
    message (Msg())
  end
end

function Sub1()
  BadConditions = true
  CurSize = Size()
  for i = 1, 3 do SetRangeValue (i, 1, CurSize, nil) end
  local validIdx = 1
  while validIdx <= CurSize and not CandleExist (validIdx) do
    validIdx = validIdx + 1
  end
  if validIdx >= CurSize then
    message (Title() ..": мало данных 1.")
    return false
  end
  local cur  = {}
  cur.Hi = H(validIdx);  cur.Lo = L(validIdx)
  cur.Mx = Upr (cur.Hi); cur.Mn = Lwr (cur.Lo)
  local firstRegularIdx
  local bar = validIdx + 1
  while bar <= CurSize do
    while not CandleExist (bar) or
        H(bar) >= cur.Hi and L(bar) <= cur.Lo or
        H(bar) <  cur.Hi and L(bar) >  cur.Lo do
      if CandleExist (bar) then
        cur.Hi = H(bar); cur.Lo = L(bar)
        cur.Mx = math.max (cur.Mx, cur.Hi)
        cur.Mn = math.min (cur.Mn, cur.Lo)
      end
      bar = bar + 1
      if bar > CurSize then
        message (Title() ..": мало данных 2.")
        return false
      end
    end -- while not H(bar) or not L(bar)
    Xtrs = {}
    if H(bar) > cur.Mx then
      table.insert (Xtrs, NewNode (validIdx,  H(validIdx)))
      table.insert (Xtrs, NewNode (bar, H(bar)))
      firstRegularIdx = bar + 1
      break
    elseif L(bar) < cur.Mn then
      table.insert (Xtrs, NewNode (validIdx, L(validIdx)))
      table.insert (Xtrs, NewNode (bar, L(bar)))
      firstRegularIdx = bar + 1
      break
    end
    bar = bar + 1
  end -- while bar <= CurSize
  if not firstRegularIdx then
    message (Title() ..": мало данных 3.")
    return false
  end
  BadConditions = false
  for bar = firstRegularIdx, CurSize do
    if CandleExist (bar) then AddXtr (bar) end
  end
  DataSourceInfo = getDataSourceInfo ()
  local logFile = ScanNo == 1 and io.open (Settings.Log, "w") or nil
  if logFile then
    local fmt = DataSourceInfo and DataSourceInfo.interval < 0 and
      "%-10s; %-10s; %-10s; %-10s; %-8s; %-10s\n" or
      "%-16s; %-10s; %-16s; %-10s; %-10s; %-12s\n"
    logFile:write (string.format (fmt
      ,"nDateTime", "nPrice", "xDateTime", "xPrice", "profit", "equity"))
  end
  local ttl, n, d0, p0, d1, p1 = 0, #Xtrs
  for i = 1, n do
    SetValue (Xtrs[i].FinIdx, 1, Xtrs[i].FinVal)
    if i > 2 then
      d1 = DateAndTime (Xtrs[i].IniIdx)
      if Trend(i) < 0 then
        p1 = Xtrs[i].IniVal;
        SetValue (Xtrs[i].IniIdx, 2, p1)
      else
        p1 = Xtrs[i].IniVal
        SetValue (Xtrs[i].IniIdx, 3, p1); p1 = -p1
      end
      if i > 3 then
        ttl = ttl + p0 + p1
        if logFile then
          logFile:write (
            string.format ("%s; %10.2f; %10s; %10.2f; %10.2f; %12.2f\n"
            ,d0, p0, d1, p1, p0 + p1, ttl))
        end
      end
      d0, p0 = d1, p1
    end
  end -- for i = 1, n
  if logFile then logFile:close() end
  local s = Title() .."\nttl "..
    string.format ("%.2f", ttl) .."\n".. ToString()
  local n1, n2, n3 = Xtrs[#Xtrs-2], Xtrs[#Xtrs-1], Xtrs[#Xtrs]
  Xtrs = {}; Xtrs[1], Xtrs[2], Xtrs[3] = n1, n2, n3
  DummyXtr = CurSize > n3.FinIdx and MakeDummy() or nil
  if ScanNo == 1 then
    message (s .."\nDummyXtr "..
    (DummyXtr and DateAndTime (DummyXtr.FinIdx) or "not-not"))
  end
  return true
end -- Sub1()

function Sub2()
  if DummyXtr then
    SetValue (DummyXtr.FinIdx, 1, nil)
    SetValue (DummyXtr.IniIdx, Trend() > 0 and 2 or 3, nil)
    DummyXtr = nil
  end
  local n = #Xtrs
  SetValue (Xtrs[n].FinIdx, 1, nil)
  SetValue (Xtrs[n].IniIdx, Trend() < 0 and 2 or 3, nil)
  table.remove (Xtrs)
  CurSize = Xtrs[#Xtrs].FinIdx
  while CurSize < Size() do
    CurSize = CurSize + 1
    AddXtr (CurSize)
  end
  for k = n, #Xtrs do
    local node = Xtrs[k]
    SetValue (node.FinIdx, 1, node.FinVal)
    SetValue (node.IniIdx, Trend(k) < 0 and 2 or 3, node.IniVal)
  end    
  if Xtrs[#Xtrs].FinIdx < Size() then
    DummyXtr = MakeDummy()
  end
end -- Sub2()
6 Комментариев
  • Александр Исаев
    18 июля 2021, 09:58
    зигзаг сукко… хитрый
  • SergeyJu
    18 июля 2021, 10:48
    Зигзаг без подглядывания в будущее недостаточно эффективен. На многих активах вообще не работает. Зигзаг с подглядыванием в будущее физически нереализуем. Нелюбовь многих к зигзагу вызвана тем, что во многих стандартных пакетах он рисуется задним числом, то есть торговать его нельзя.  
    А объективно, он не лучше и не хуже стандартных скользяшек. Сам по себе не работает, нуждается в дополнительной обвязке, чтобы получить систему.
    • Чужой
      18 июля 2021, 14:31
      SergeyJu, однозначно
  • igor12
    18 июля 2021, 11:52
    «Мало кто пытался строить торговую стратегию на основе Зигзага.»
    — Вы совершенно заблуждаетесь с этим выводом…
    Ещё в 90 было много вариантов построения этого индикатора и попыток формализовать системы торговли на основе его… Простых решений, позволяющих получить нормальную статистику на выходе нет..
    О некоторых вопросах по этому индикатору написано выше…
  • Skifan
    18 июля 2021, 13:22
    Удачи в торговле 
  • Bogdan
    18 июля 2021, 15:21
    Представьте, что скоро появится искусственный интеллект, как сильно тогда изменится трейдинг

Активные форумы
Что сейчас обсуждают

Старый дизайн
Старый
дизайн