Эта статья про Oracle Database, PL/SQL, SQL, MATCH_RECOGNIZE, MODEL clause, aggregate и pipelined functions.

В качестве функциональной области использован Технический анализ (ТА) рынков. Сначала небольшая поверхностная вводная о торговле на рынках, потом — расчёты.

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

image

Разработано много SQL-операторов, процедур и графиков. Часть — ниже, полностью код — на GitHub по ссылке внизу статьи.

Технические индикаторы рынка (ТИР) — дополнительные графики, к графикам цен, формируемые на основе пересчёта значений, содержащихся в базовом графике цены. Обычно это различные виды усреднений (очередная точка графика рассчитывается как усреднённое значение некоторого количества предыдущих значений цены, например, скользящая средняя), отношений (очередная точка является результатом сопоставления некоторого числа предыдущих цен — их разность, производная от изменения за период), или лагов (задержек). Индикаторы наглядно показывают некоторую неочевидную информацию, содержащуюся в статистике изменения цены, и могут формировать рекомендации о торговых приказах — BUY/SELL. Индикаторы имеют минимум один изменяемый параметр, от значения которого будет изменяться результат. Для открытия реальных сделок обычно используется несколько индикаторов в комплексе, плюс дополнительная информация, на усмотрение трейдера.

Один из постулатов Технического анализа — «история повторяется»: Участники рынка в схожих обстоятельствах ведут себя примерно одинаково, формируя похожую динамику изменения цен. И естественно предположить, что поведение рынка в будущем, в основном, повторит наметившиеся в прошлом модели. Следуя этому утверждению, инвестор может выбрать из возможных параметров ТИР те, что наилучшим образом зарекомендовали себя в прошлые периоды.

В этой статье строятся график результативности торговли при использовании каждого индикатора от значения параметра ТИР, это в двумерном представлении. Но фактически я буду строить в трёхмерном представлении, чтобы оценить также и влияние запаздывания исполнения приказа на результат, поэтому параметр будет ось X (абсцисса), задержка — ось Y (ордината), результат — ось Z (аппликата). Это некоторая попытка оценить влияние «проскальзываний» (“slippage”), которые, к сожалению, всегда случаются. Вместо реального «проскальзывания» по оси Y я откладываю исполнение приказа BUY/SELL на срок от 1 до 5 периодов.

Если на этом графике выделится глобальный максимум и сам график будет похож на шляпу (вообще, это называется «нормальное распределение», но оно предполагает строгую симметрию относительно вертикальной оси), конус, пирамида, тоже подходят — значит для этого ТИР можно подобрать определённое значение параметра, который покажет самый высокий результат, и с этим индикатором можно попробовать торговать. Если же график результативности в зависимости от параметра будет напоминать «частокол» — подобрать оптимальное значение параметра невозможно и нет смысла использовать этот ТИР в торговле.

Можно сказать, что в этой статье я рассчитываю лишь «процент попадания» индикаторов. Задача, оценить, сколько можно «заработать», мною не решалась.

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

Для информации… В противоположность Техническому анализу, Фундаментальный анализ (ФА) — термин для обозначения методов прогнозирования рыночной (биржевой) стоимости инструментов, основанных на анализе финансовых и производственных показателей деятельности.
«Внутренняя стоимость» по ФА в большинстве случаев не совпадает с ценой акций компании, которая определяется соотношением спроса и предложения на фондовом рынке. Инвесторов, использующих в своей деятельности ФА, интересуют в первую очередь ситуации, когда «внутренняя стоимость» акций компании превышает цену акций на бирже. Такие акции считаются недооцененными, значит их цена будет расти, и они являются потенциальными объектами инвестиций.

Один из наиболее известных инвесторов, использующих Фундаментальный анализ — Уоррен Баффетт.

Посмотрите отрывок из фильма: https://www.youtube.com/watch?v=SqE8fnvmV1Y

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

И целей этой статьи две…

Кроме описанных выше рассуждений о ТА и ФА я хотел исследовать и показать возможности Oracle Database по выполнению расчётов Технических индикаторов рынка.
Эти возможности я и представляю на суд читателей.

Если вы решите повторить мои расчёты — скачайте код с GitHub по ссылке внизу статьи. Код проверен на версии 12.2.0.1.

Сначала создайте общие объекты. Первыми идут таблицы и view. В это же файле далее — пакет моделирования сделок.

Вторым шагом — создание функций, выполняющих расчёт ТИР.

Третий шаг — расчёты.

Все функции расчёта всех ТИР будут возвращать курсор со следующими полями: STOCK_NAME, ADATE, ACLOSE (цена закрытия дня), AACTION (приказ продавать/покупать)

Пакет содержит три табличные функции моделирования, которые принимают на вход курсор из описанных выше функций расчёта ТИР, смещение (задержку исполнения приказа, лаг), и начальный капитал, по умолчанию равен 1000 USD. Функции пакета вызываются так:

select * from HABR_TRADEMODELLING_P.TRADE_LOG (cursor (select STOCK_NAME, ADATE, ACLOSE, AACTION from table (HABR_MARKETINDEXES_XXXXXXXX_F_CALC (10))), p_lag => 1) order by 1, 2;
 
select * from HABR_TRADEMODELLING_P.CALC_ACTIONS (cursor (select STOCK_NAME, ADATE, ACLOSE, AACTION from table (HABR_MARKETINDEXES_XXXXXXXX_F_CALC (10))), p_lag => 1);
 
select * from HABR_TRADEMODELLING_P.CALC_ACTIONS_TOTALS (cursor (select STOCK_NAME, ADATE, ACLOSE, AACTION from table (HABR_MARKETINDEXES_XXXXXXXX_F_CALC (10))), p_lag => 1);

где XXXXXXXX — название ТИР.

Все функции рассчитывают результат торговли при условии полного реинвестирования прибыли, без учета расходов на транзакции и налоги, и без учёта инфляции и дисконтирования.

Первая функция TRADE_LOG формирует упрощённый журнал сделок трейдера. Функция позволяет проследить всю цепочку сделок. Если на конец периода инвестор «в бумагах», для расчёта баланса в валюте (финансового результата) функция имитирует продажу всех бумаг по последней цене закрытия и формирует соответствующую пометку в поле IN_STOCK.

Вторая функция CALC_ACTIONS возвращает те-же колонки, что и вызванный курсор расчёта ТИР, плюс добавляет следующие колонки: AACTION_LAG (приказ со смещением), BALANCE_CURRENCY (баланс счёта трейдера в наличной валюте), BALANCE_STOCK (количество инструментов в открытых позициях). В зависимости от значения поля AACTION_LAG имитируется покупка или продажа по цене закрытия, и соответственно изменяются баланс в валюте и сумма открытых позиций.

В последней строке каждого инструмента можно увидеть итог торговли по каждому инструменту, также, как и в предыдущей функции, если инвестор «в бумагах», имитируется продажа для расчёта баланса в валюте

Третья функция CALC_ACTIONS_TOTALS делает то же самое, что и вторая, но возвращает только последнюю строку — итог торговли по каждому инструменту. Она и будет использована в моделировании.

Загрузите данные


Расчёты приведены для таких рынков и индексов: S&P500, NYSE, Brent, BTCUSD, EURUSD.
Первые 4 загружены с сайта Yahoo Finance, последний — из другого источника. Результаты расчётов для курсов из иных источников могут отличаться.

Обратите внимание, что периоды курсов каждого инструмента различаются, а именно:

  • S&P500 — 03 /01/1950…29/01/2019, 69 лет;
  • NYSE — 31/12/1965…22/03/2019, 54 года;
  • Brent — 17/05/1991…06/02/2019, 28 лет;
  • BTCUSD — 16/07/2010…29/01/2019, 9 лет;
  • EURUSD — 16/02/2001…27/05/2019, 18 лет.

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

Файл для загрузки (SQL*Loader) можно тоже взять с GitHub по ссылке внизу статьи.

Скользящие средние


Есть по крайней мере три основных вида скользящих средних:

  • линейная (Simple Мoving Average, SMA),
  • экспоненциальная (Exponential Moving Average, EMA) и
  • линейно-взвешенная (Weighted Moving Average, WMA).

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

Линейную скользящую среднюю вычислить проще всего. В Oracle это функция avg (VALUE) over (partition by STOCK_NAME order by ADATE rows between 9 preceding and current row) — скользящая средняя с величиной окна усреднения равным 10 значениям.

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

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

В расчётах ТИР используются все три вида скользящих средних. Для экспоненциальной и линейно взвешенной в этой статье разработаны агрегатные функции EMA и WMA, которые используются в аналитической форме. Кроме того, эти скользящие средние можно вычислить рекурсией или моделькой (фраза MODEL).

Расчёт EMA и WMA, без рекурсии или модельки, одной лишь аналитикой, в Oracle Database, судя по всему, невозможен.

Но ещё оговорки касательно скользящих средних:

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

Технические индикаторы рынка


Для почти каждого индикатора будут приведены несколько методов расчёта: CALC — расчёт с помощью PL/SQL кода, SIMP — расчёт одним оператором или RECU — расчёт рекурсией, AGRF — расчёт с использованием агрегатной функции, MODE — расчёт моделькой.

Для разработки нескольких методов есть свои причины. Во-первых, рассчитав ТИР несколькими методами и сравнив показатели, если показатели совпадают, можно быть уверенным, что расчёты проведены верно (учитывая разные методы округления и обработки значений NULL и «0»). В этой статье я буду сравнивать хэши выборок различных методов, поэтому гарантируется совпадение до бита и одинаковая обработка всеми алгоритмами.

Полагаю, в Oracle начинать разработку ТИР нужно с метода SIMP — расчёт одним оператором. Когда это сделано, у «ораклоида» складывается в голове план и алгоритм расчёта, и его можно легко переложить на PL/SQL или на другой процедурный язык.

Замечу, что метод расчёта CALC на PL/SQL здесь оказывается быстрее метода SIMP (одним оператором) в том случае, если весь расчёт можно выполнить за один проход по курсору. А вот если для расчёта придётся формировать временные таблицы или коллекции, или более, чем 1 проход по курсору — полагаю, метод «одним оператором» окажется и быстрее, и менее ресурсоёмким.

Для всех методов, включая метод SIMP («одним оператором») я буду помещать операторы в функции, для того, чтобы расчёт можно было вызвать с параметром для подбора оптимального значения.

Расчёты приведены для семи ТИР: Пересечение скользящей средней (EMASIMPLE), «Золотой» и «Смертельный кресты (CROSSES), Балансовый объём (OBV), Канал Кельтнера (KELTNER), Тренд цены и объёма (PVT), Индикатор лёгкости движения Армса (EMV), Индекс товарного канала (CCI).

Для семи индикаторов этих двух статей будет один параметр «величина окна усреднения», и второй параметр — сдвиг (lag). Сдвиг говорит, на сколько баров нужно отложить исполнение приказа BUY/SELL — на сколько баров сдвинуть цену закрытия (все приказы совершаются по цене закрытия бара). Это подобно «проскальзыванию» (“slippage”), но это не совсем «проскальзывание». Проскальзывание обычно оказывается не в пользу клиента, а наш лаг, может оказаться как не в пользу клиента, так и в пользу. Тем не менее, использование моделирования с лагом от 1 до 5 баров для некоторых индикаторов показывает, что проскальзывание оказывает значительный эффект на результат. А для некоторых индикаторов лаг и проскальзывание не так важны.

В методах SIMP («одним оператором») и RECU («рекурсия») активно используется фраза MATCH_RECOGNIZE, для формирования торгового сигнала BUY/SELL на основании вхождения/покидания рассчитанного ТИР в заданный диапазон или его поведения относительно своего скользящего среднего.

Подробные описания всех ТИР можно посмотреть в Википедии или в книге Роберта Колби «Энциклопедия технических индикаторов рынка».

Пересечение экспоненциальной скользящей средней


Метод пересечения экспоненциальных скользящих средних является простейшим ТИР.

Метод предполагает: покупку (открытие длинной позиции), если значение цены пересекает снизу-вверх свою экспоненциальную скользящую среднюю (ЭСС); продажу (закрытие длинной позиции), если цена пересекает свою ЭСС сверху вниз.

Короткие позиции, как и маржинальная торговля в целом, в этой статье не рассматриваются.

В дальнейшем, именно этот индикатор можно использовать как эталон для сравнения с другими. Сравнение с этим ТИР лучше, чем сравнение со стратегией «Купи и держи», потому что эта стратегия не выгодна на падающих рынках.

Единственный параметр ТИР — длина скользящей средней.

Расчёт с помощью PL/SQL

create or replace function HABR_MARKETINDEXES_EMASIMPLE_F_CALC (p_averaging_window_width integer)

return HABR_MARKETINDEXES_RESULT_LIST_T pipelined is

    l_result HABR_MARKETINDEXES_RESULT_LIST_T;

    EMA number;

    prev_EMA number;

    prev_TYPICAL_PRICE number;

    retval HABR_MARKETINDEXES_RESULT_T := HABR_MARKETINDEXES_RESULT_T (null, null, null, null, null, null, null, null, null);
    prev_STOCK_NAME varchar2(256);
    l_alpha number;
begin

    l_alpha := 2 / (p_averaging_window_width + 1);

    for c1 in (select STOCK_NAME, ADATE, TYPICAL_PRICE, ACLOSE from LOAD_YAHOO_V order by 1, 2)
    loop
   
        retval.ADATE        := c1.ADATE;
        retval.ACLOSE       := c1.ACLOSE;

        if prev_STOCK_NAME is null or prev_STOCK_NAME <> c1.STOCK_NAME
        then
            retval.STOCK_NAME   := c1.STOCK_NAME;
            EMA                 := c1.TYPICAL_PRICE;
            prev_EMA            := null;
        else
            EMA := round (c1.TYPICAL_PRICE * l_alpha + EMA * (1 - l_alpha), 20);
        end if;
       
        if    prev_TYPICAL_PRICE < prev_EMA and c1.TYPICAL_PRICE > EMA then retval.AACTION := 'BUY';
        elsif prev_TYPICAL_PRICE > prev_EMA and c1.TYPICAL_PRICE < EMA then retval.AACTION := 'SELL';
        else  retval.AACTION := null;
        end if;

        retval.IND_VALUE  := EMA;
       
        pipe row (retval);
       
        prev_STOCK_NAME := c1.STOCK_NAME;
        prev_EMA := EMA;
        prev_TYPICAL_PRICE := c1.TYPICAL_PRICE;
       
    end loop;
end;


Расчёт одним рекурсивным оператором

create or replace function HABR_MARKETINDEXES_EMASIMPLE_F_RECU (p_averaging_window_width integer)

return HABR_MARKETINDEXES_RESULT_LIST_T is

    l_result HABR_MARKETINDEXES_RESULT_LIST_T;

begin

    with
      T1 (STOCK_NAME, ADATE, TYPICAL_PRICE, EMA, ACLOSE, RN) as
           (select STOCK_NAME, ADATE, TYPICAL_PRICE, round (TYPICAL_PRICE, 20), ACLOSE, RN from LOAD_YAHOO_V where RN = 1
            union all
            select b.STOCK_NAME
                 , b.ADATE
                 , b.TYPICAL_PRICE
                 , round (b.TYPICAL_PRICE * 2 / (p_averaging_window_width + 1) + a.EMA * (1 - 2 / (p_averaging_window_width + 1)), 20)
                 , b.ACLOSE
                 , b.RN
            from T1 a, LOAD_YAHOO_V b
            where b.RN = a.RN + 1 and b.STOCK_NAME = a.STOCK_NAME)
    select HABR_MARKETINDEXES_RESULT_T (STOCK_NAME, ADATE, ACLOSE, EMA, null, null, null, null, AACTION)
    bulk collect into l_result
    from T1 match_recognize (partition by STOCK_NAME order by ADATE
                             measures classifier() as AACTION
                             all rows per match with unmatched rows
                             pattern (BUY+ | SELL+)
                             define BUY  as (prev (TYPICAL_PRICE) < prev (EMA) and TYPICAL_PRICE > EMA)
                                  , SELL as (prev (TYPICAL_PRICE) > prev (EMA) and TYPICAL_PRICE < EMA)
                             ) MR;

    return l_result;
end;


Расчёт с помощью фразы MODEL
Текст функции HABR_MARKETINDEXES_EMASIMPLE_F_MODE
create or replace function HABR_MARKETINDEXES_EMASIMPLE_F_MODE (p_averaging_window_width integer)
return HABR_MARKETINDEXES_RESULT_LIST_T is
    l_result HABR_MARKETINDEXES_RESULT_LIST_T;
begin

    with
      T1 as (select * from LOAD_YAHOO_V
             model dimension by (STOCK_NAME, RN) measures (ADATE, TYPICAL_PRICE, ACLOSE, to_number(null) as EMA)
             rules (EMA[any, any] = round (TYPICAL_PRICE [cv(), cv()] * 2 / (p_averaging_window_width     + 1) + nvl(EMA [cv(), cv() - 1], TYPICAL_PRICE [cv(), cv()]) * (1 - 2 / (p_averaging_window_width     + 1)), 20)))
    , T2 as (select STOCK_NAME, ADATE, ACLOSE
                  , TYPICAL_PRICE, LAG (TYPICAL_PRICE) over (partition by STOCK_NAME order by ADATE) as PREV_TYPICAL_PRICE
                  , EMA, lag (EMA) over (partition by STOCK_NAME order by ADATE) as PREV_EMA
             from T1)
    select HABR_MARKETINDEXES_RESULT_T (STOCK_NAME, ADATE, ACLOSE, EMA, null, null, null, null
                                      , case when prev_TYPICAL_PRICE < prev_EMA and TYPICAL_PRICE > EMA then 'BUY'
                                             when prev_TYPICAL_PRICE > prev_EMA and TYPICAL_PRICE < EMA then 'SELL' end)
    bulk collect into l_result
    from T2 order by STOCK_NAME, ADATE;
    
    return l_result;

end;



Расчёт с помощью агрегатной функции
Текст агрегатной функции EMA и функции HABR_MARKETINDEXES_EMASIMPLE_F_AGRF
create or replace type EMA_DATA_T as object (AVALUE number, AVERAGING_WINDOW integer);

create or replace type EMA_IMPL_T as object
(
    l_window_width integer,
    l_ema  number,
    static function ODCIAggregateInitialize   (sctx in out EMA_IMPL_T) return number,
    member function ODCIAggregateIterate      (self in out EMA_IMPL_T, value in EMA_DATA_T) return number,
    member function ODCIAggregateMerge        (self in out EMA_IMPL_T, ctx2 in EMA_IMPL_T) return number,
    member function ODCIAggregateTerminate    (self in EMA_IMPL_T, returnValue out number, flags in number) return number
);


create or replace type body EMA_IMPL_T is
 
static function ODCIAggregateInitialize (sctx in out EMA_IMPL_T) return number is
begin
    sctx := EMA_IMPL_T (null, null);
    return ODCIConst.Success;
end;

member function ODCIAggregateIterate (self in out EMA_IMPL_T, value in EMA_DATA_T) return number is
begin

    if value.AVALUE is not null
    then

        if l_window_width is null
        then
            l_window_width := value.AVERAGING_WINDOW;
            self.l_ema := value.AVALUE;
        else
            self.l_ema := round (value.AVALUE * 2 / (l_window_width  + 1) + self.l_ema * (1 - 2 / (l_window_width  + 1)), 20);
        end if;
    end if;
    
    return ODCIConst.Success;
end;

member function ODCIAggregateMerge(self in out EMA_IMPL_T, ctx2 in EMA_IMPL_T) return number is
begin
    return ODCIConst.Error;
end;

member function ODCIAggregateTerminate(self in EMA_IMPL_T, returnValue out number, flags in number) return number is
begin
    returnValue := self.l_ema;
    return ODCIConst.Success;
end;

end;

create or replace function EMA (input EMA_DATA_T) return number aggregate using EMA_IMPL_T;



create or replace function HABR_MARKETINDEXES_EMASIMPLE_F_AGRF (p_averaging_window_width integer)
return HABR_MARKETINDEXES_RESULT_LIST_T is
    l_result HABR_MARKETINDEXES_RESULT_LIST_T;
begin

    with
      T1 as (select STOCK_NAME, ADATE, TYPICAL_PRICE, ACLOSE
                  , round (EMA (EMA_DATA_T (TYPICAL_PRICE, p_averaging_window_width)) over (partition by STOCK_NAME order by ADATE), 20) as EMA
             from LOAD_YAHOO_V)
    select HABR_MARKETINDEXES_RESULT_T (STOCK_NAME, ADATE, ACLOSE, EMA, null, null, null, null, AACTION)
    bulk collect into l_result
    from T1 match_recognize (partition by STOCK_NAME order by ADATE
                             measures classifier() as AACTION
                             all rows per match with unmatched rows
                             pattern (BUY+ | SELL+)
                             define BUY  as (prev (TYPICAL_PRICE) < prev (EMA) and TYPICAL_PRICE > EMA)
                                  , SELL as (prev (TYPICAL_PRICE) > prev (EMA) and TYPICAL_PRICE < EMA)
                             ) MR;

    return l_result;
end;



Сравним результаты расчёта с одним параметром:

select COLUMN_VALUE as ALG, dbms_sqlhash.gethash (COLUMN_VALUE, 2) as RECORDSET_HASH
from table (sys.odcivarchar2list ('select * from table (HABR_MARKETINDEXES_EMASIMPLE_F_CALC (15)) order by 1, 2'
                                , 'select * from table (HABR_MARKETINDEXES_EMASIMPLE_F_RECU (15)) order by 1, 2'
                                , 'select * from table (HABR_MARKETINDEXES_EMASIMPLE_F_MODE (15)) order by 1, 2'
                                , 'select * from table (HABR_MARKETINDEXES_EMASIMPLE_F_AGRF (15)) order by 1, 2'));

Все хэши должны совпадать для всех четырёх методов.

Если хэши не сошлись, разобраться, где именно образовалось расхождение, можно таким оператором (подставьте имена функций, которые надо сравнить):

select coalesce (a.STOCK_NAME, b.STOCK_NAME) as STOCK_NAME, coalesce (a.ADATE, b.ADATE) as ADATE
     , a.ACLOSE as CALC_ACLOSE, b.ACLOSE as AGRF_CLOSE
     , a.IND_VALUE as CALC_EMA, b.IND_VALUE as AGRF_EMA
     , a.AACTION as CALC_AACTION, b.AACTION as AGRF_AACTION
from table (HABR_MARKETINDEXES_EMASIMPLE_F_CALC (15)) a
full outer join table (HABR_MARKETINDEXES_EMASIMPLE_F_AGRF (15)) b on a.STOCK_NAME = b.STOCK_NAME and a.ADATE = b.ADATE
--where sys_op_map_nonnull (a.ACLOSE) <> sys_op_map_nonnull (b.ACLOSE)
--   or sys_op_map_nonnull (a.IND_VALUE) <> sys_op_map_nonnull (b.IND_VALUE)
--   or sys_op_map_nonnull (a.AACTION) <> sys_op_map_nonnull (b.AACTION)
order by 1, 2;
;

Подбор параметров

Сам ТИР использует один параметр, для этого и для всех других ТИР я изменяю его в диапазоне от 1 до 200, но для расчёта трёхмерной картинки для зависимости и от лага тоже, мы введём второй параметр, который будет изменяться от 1 до 5.

Оператор открывает 200 * 5 = 1000 курсоров, поэтому может потребоваться изменение параметра Oracle OPEN_CURSORS

Запрос ниже выполняет декартово произведение таблицы с числами от 1 до 200, с таблицей с числами от 1 до 5, и декартово умножает всё это на вызов табличной функции HABR_TRADEMODELLING_P.CALC_ACTIONS_TOTALS.

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

rollback;

delete HABR_MARKETINDEXES_PARMSEL_RESULTS where INDICATOR_NAME = 'HABR_MARKETINDEXES_EMASIMPLE_F_CALC';
commit;

insert into HABR_MARKETINDEXES_PARMSEL_RESULTS (INDICATOR_NAME, PARM1, PARM2, STOCK_NAME, ADATE_MIN, ADATE_MAX, DEALS_COUNT, BALANCE_RESULT, DEALS_PROFIT_AMOUNT, DEALS_LOSS_AMOUNT, DEALS_PROFIT_COUNT, DEALS_LOSS_COUNT, IN_STOCK)
with
  TP1 as (select rownum as PARM1 from dual connect by level <= &&AVERAGING_INTERVAL)
, TP2 as (select rownum as PARM2 from dual connect by level <= &&LAG_MODELLING_DEPTH)
select --+ parallel(8)
       'HABR_MARKETINDEXES_EMASIMPLE_F_CALC', PARM1, PARM2, STOCK_NAME, ADATE_MIN, ADATE_MAX, DEALS_COUNT, BALANCE_RESULT, DEALS_PROFIT_AMOUNT, DEALS_LOSS_AMOUNT, DEALS_PROFIT_COUNT, DEALS_LOSS_COUNT, IN_STOCK
from TP1
cross join TP2
cross join table (HABR_TRADEMODELLING_P.CALC_ACTIONS_TOTALS (cursor (select STOCK_NAME, ADATE, ACLOSE, AACTION from table (HABR_MARKETINDEXES_EMASIMPLE_F_CALC (PARM1))), PARM2))
;
commit;

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

Я разработал и более быстрый метод моделирования с одним курсором, без открытия 1000 курсоров, но он настолько объёмный, что занял бы половину статьи. Поэтому я его здесь приводить не буду.

Итог моделирования пересечения скользящей средней (все графики кликабельны):



Вторая строка графиков — это то-же, что и первая строка, но графики немного повёрнуты, графики с различным лагом располагаются один за другим. Это позволяет оценить влияние лага на результат

В целом, индикатор не очень чувствителен к лагам. Для рынков S&P500 и NYSE параметр ТИР нужно выбирать чем больше, тем лучше. Для рынка Brent — в районе 25. На остальных двух рынках зависимости между прибыльностью и параметром нет.

Смертельный и Золотой кресты


В реализации на Oracle этот индикатор очень похож на предшествующий, только здесь используются две скользящие средние, а не одна. Поэтому я приведу только один вариант расчёта.

В Вики описан «Индикатор Ишимоку». Это сложный индикатор. «Кресты» являются одной из составных частей. Но индикатор описан на Вики плохо, в частности, обратите внимание, что линии Tenkan и Kijun описаны совершенно разными словами, хотя по сути это одно и то-же, но с разными периодами.

В книге Роберта Колби этот индикатор тоже не описан.

Японские аналитики называют пересечение средних, когда краткосрочная средняя пересекает долгосрочную снизу вверх, Золотой крест (Golden Cross), а обратную ситуацию, когда краткосрочная скользящая средняя пересекает долгосрочную сверху вниз — Мёртвый крест (Dead Cross).

Автор обратил вниманием, что этот индикатор описан в статье «Фьючерсы на американскую нефть сформировали «смертельный крест»» и стал гуглить его описание и использование.

Этот индикатор считается серьёзным на фондовом рынке, отчасти потому, что он подаёт сигналы редко.

Наиболее часто используются 50-ти и 200-периодные скользящие средние.

При моделировании результативности ниже мы примем период длинной скользящей средней равным учетверённому периоду короткой скользящей средней, и смоделируем длину короткой от 1 до 200 дней (получится от 4 до 800 дней для длинной).

Код для расчёта на GitHub.



Индикатор не очень чувствителен к лагам. Для рынка S&P500 есть максимумы на 48 (192 для длинной СС) и 98 дней (392 для длинной СС). Заметьте, первый максимум очень близко к числам 50х200. Можно предположить, что если этот параметр выбрать на 1 и 2 меньше, чем другие участники рынка, можно попробовать их обыграть на одном этом индикаторе.

На NYSE 4 максимума. На рынках Brent и BTCUSD индикатор не работает.
Для рынка EURUSD тоже параметр надо выбирать чуть меньше, чем 50 для короткой СС. Но вот прибыли на этом рынке индикатор не даёт. Его можно использовать только как дополнительный индикатор.

Балансовый объём, Равновесный объём, On-Balance Volume (OBV)


Индикатор OBV представляет собой кумулятивную скользящую среднюю объёма торгов, взятого со знаком плюс в случае растущего рынка и со знаком минус в случае падающего.

Здесь будет использован такой способ толкования значения индикаторов: купить, когда OBV пересечёт свою скользящую среднюю снизу-вверх, продать, когда OBV пересечёт свою скользящую среднюю сверху-вниз.

Подробнее на Вики или у Колби



На рынке S&P500 индикатор очень чувствителен к задержкам (лагам), но можно попробовать выбирать параметр по принципу «чем больше, тем лучше», некоторая прибыльность достигается. На рынке NYSE прибыльности достигнуть не удастся. На рынке Brent можно выбирать значение параметра от 20 до 100. На рынке BTCUSD чёткой линейной зависимости нет, но выбирать значение параметра меньше 40 нецелесообразно. Для рынка EURUSD значение параметра нужно выбирать «чем больше, тем лучше», но прибыльности достигнуть не удастся.

Канал Кельтнера, Keltner Channel


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

Средняя линии индикатора является простой скользящей средней от типичной цены.

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

В наших расчётах сигнал ТИР будет формироваться так: Покупай, если цена пересекает верхнюю линию, Продавай, если цена пересекает нижнюю.



Для рынков S&P500 и NYSE есть максимумы. На рынке Brent зависимости результата от параметра нет, и вся торговля идёт в убыток. На рынке BTCUSD максимумы есть, но узкие, и в них трудно попасть. На рынке EURUSD зависимость результата от параметра есть.

Тренд цены и объёма, Тенденция цены и объёма, Price–Volume Trend, PVT


Значение индикатора PVT представляет собой кумулятивную сумму произведения текущего объёма торгов на приведённый рост цены относительно предыдущего периода.

Формировать торговые сигналы здесь мы будем следующим образом: Покупай, когда PVT пересечёт свою скользящую среднюю снизу-вверх, Продавай, когда PVT пересечёт свою скользящую среднюю сверху-вниз.

Подробнее на Вики. У Колби не нашёл.



Для рынка S&P500 значение параметра «чем больше, тем лучше», на NYSE почти нет зависимости, для Brent есть максимум в районе 50, на BTCUSD индикатор эффективен только при минимальных лагах, 1-2, и с минимальными значениями параметра, до 50-ти, для EURUSD зависимость возрастающая.

Лёгкость движения, Индикатор лёгкости движения Армса, Arms’ Ease of Movement Value, EMV


ТИР EMV — это численное выражение того, насколько легко изменяется цена на рынке. Чем сильнее изменение цены и чем ниже при этом оборот, тем легче растет или падает рынок.

Торговые приказы здесь будем формировать так: Покупай, когда скользящая средняя EMV поднимается выше нуля, Продавай, когда скользящая средняя EMV опускается ниже нуля.

Подробнее на Вики или у Колби.



На рынках S&P, NYSE есть максимумы, на рынке Brent индикатор эффективен в широком диапазоне значений параметров. На рынках BTCUSD и EURUSD индикатор неэффективен.

Индекс товарного канала, Сommodity channel index, CCI


ТИР CCI — индикатор, основанный на анализе текущего изменения отклонения цены от её среднего значения за определённый период и среднестатистического абсолютного значения этого параметра. ТИР применим к любым финансовым рынкам.

Торговые приказы будем формировать так: Покупай, когда CCI поднимается выше 100, Продавай, когда CCI опускается ниже 100.

Подробнее на Вики или у Колби.



На рынках S&P500, NYSE, Brent нет зависимости результата торговли от значения параметра. На рынке BTCUSD зависимость есть (два чётких максимума), и в реальной торговле её можно попробовать поймать. На рынке EURUSD индикатор результата не принесёт.

Исходники доступны на GitHub
Создайте общие объекты, загрузите данные, либо мои (как я говорил раньше, все рынки кроме последнего взяты с Yahoo Finance), либо свои, создайте функции для расчёта, рассчитайте данные результативности торгов (как я говорил раньше, расчёт каждого индикатора до 20 минут).

Общий вывод относительно ТИР: использование одного только индикатора для извлечения прибыли невозможно. Разные индикаторы по разному эффективны на разных рынках. Возможность получения прибыли при использовании нескольких хорошо настроенных индикаторов плюс дополнительной внешней информации не исключена.

Дополнительно:

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

Выполнять расчёты и сложное моделирование ТИР и финансовой деятельности вообще на Oracle Database весьма удобно.

Комментарии (1)


  1. yleo
    07.06.2019 12:45

    Хороший пример как правильно сделать vendor lock-in.