Всем известен старинный индикатор Пробоя. Этот индикатор для
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()
А объективно, он не лучше и не хуже стандартных скользяшек. Сам по себе не работает, нуждается в дополнительной обвязке, чтобы получить систему.
— Вы совершенно заблуждаетесь с этим выводом…
Ещё в 90 было много вариантов построения этого индикатора и попыток формализовать системы торговли на основе его… Простых решений, позволяющих получить нормальную статистику на выходе нет..
О некоторых вопросах по этому индикатору написано выше…