Торгуем долей

June 16, 2013

Содержание

Будучи инвестором ПАММ-счёта Absolute Trading, меня заинтересовала глубина текущей просадки, а именно – насколько такой результат согласуется с результатами тестирования на исторических данных используемых стратегий автоматической торговли.

К сожалению (по техническим причинам), результат back-тестирования стратегий, предоставленный управляющим (Henadzi), демонстрирует торговлю фиксированным лотом, в то время как в реальности торговля ведётся долей от средств. Мне, как неискушённому инвестору, сложно соотнести график реальной торговли с графиком тестирования, т.к. визуально графики сильно отличаются.

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

Данная статья представляет собой результат компиляции программы, написанной в стиле Literate Programming.

Update (2013-08-08):

– cakeplus

1 Задача

Даны результаты тестирования торговых стратегий (последовательность операций) в виде отчётов ReportManager (по отдельному отчёту на каждую из стратегий):

# Time Type Order Size Price S/L T/P Profit Balance
1 2008.01.02 14:05 buy 1 0.24 1.46980 - -    
2 2008.01.03 14:35 close 1 0.24 1.47080 - - 22.34 10022.34
3 2008.01.03 14:35 sell 2 0.24 1.47080 - -    
4 2008.01.04 04:05 close 2 0.24 1.47490 - - -98.38 9923.96

2008-2013_big.png

Figure 1: Результат тестирования портфеля (график из ReportManager).

Каждая из стратегий портфеля совершает сделки фиксированного объёма (Size в таблице). Order – номер сделки. Сделка состоит из операций открытия (buy или sell) и закрытия (close, s/l, t/p, close at stop).

Необходимо пересчитать все сделки так, как если бы торговля велась долей от баланса (именно от баланса, а не от средств, по причине отсутствия необходимых данных). Кроме того, необходимо собрать из стратегий портфель (назначая стратегиям установленные Геннадием весовые коэффициенты).

Пояснение:

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

– Henadzi

Что представляет из себя торговля долей? – Если при балансе в 10000 сделка открывалась с объёмом 0.24, то при балансе в 20000 объём должен быть удвоенным, т.е. 0.48 лота. Так, с ростом доходности объём открываемых сделок будет увеличиваться, а в просадках – уменьшаться.

Решать задачу будем путём последовательного пересчёта результата всех сделок с одновременным пересчётом баланса.

2 Вычисление профита

Первая подзадача – научиться вычислять профит сделки по значениям Size и Price. Алгоритм описан в справке Альпари:

  Величина прибыли / убытка рассчитывается по формуле:

  для позиции Buy (покупка):
    Profit/Loss = (Contract × ClosePrice) - (Contract × OpenPrice);

  для позиции Sell (продажа):
    Profit/Loss = (Contract × OpenPrice) - (Contract × ClosePrice),

  где:
    Profit/Loss — величина прибыли / убытка в валюте котировки;
    Contract — величина контракта в базовой валюте;
    ClosePrice — цена закрытия валютной пары;
    OpenPrice — цена открытия валютной пары.

Пример расчёта для первой сделки:

  1. Купили 0.24 лота EURUSD по 1.46980 (24000 * 1.46980 = 35275.2).
  2. Продали 0.24 лота EURUSD по 1.47080 (24000 * 1.47080 = 35299.2).
  3. Профит в USD составил 35299.2 - 35275.2 = 24.

Результат не сходится с профитом из отчёта (получили $24, а должно было получиться $22.34). Куда же подевались $1.66?

Ликбез про свопы:

Обратили внимание на рекламную акцию Альпари "СВОП, Давай досвидания"? Своп взимается/начисляется при переносе позиций через ночь. Вот этот самый своп $1.66 и откусил, поскольку позиция переносилась через ночь. Он может быть как отрицательным, так и положительным, всё время меняется, и зависит от таких факторов, например, как разница в процентных ставках по разным валютам.

Не заморачивайтесь на свопах, это лишнее. Они могут существенно повлиять разве только на особо "быструю" торговлю финских и эстонских трейдеров, когда позиции в среднем держатся месяцами. :–) Вот ссылка на небольшой ликбез по свопам, первое, что попалось: http://www.alpari.com/ru/trading/faq/#faq-20

Но для ясности, данную сделку разберём подробнее. Стратегии тестировались со значением свопа для EURUSD - минус 2.3 пятизначных пункта для длинных позиций (покупок), и плюс 0.1 пункта для коротких позиций (продаж). В рассматриваемой сделке, минус 2.3 пункта - это 24000*0.000023=$0.552. Кроме того, по данной сделке своп был вычтен тройной, поскольку размер взимаемого/начисляемого свопа ещё и от дня недели зависит, а в ночь со среды на четверг по EURUSD свопы взимаются в тройном размере. Вот и получается $0.552*3=~$1.66.

– Henadzi

О розовых очках:

Основное. Допускается, если расчёты и графики будут представлять в несколько худшем свете потенциальные результаты работы стратегий. Но надо пытаться избегать того, чтобы тестовые результаты приукрашивали эффективность, одевали на нас розовые очки. Иначе риски могут быть оценены неверно, и расчёт торгового лота (сайза) получится завышенным. А при торговле долей средств, близкой к оптимальной, это опасно. Поэтому все косты (то, что уходит брокеру и рынку с каждой сделки, то есть плата за сделку) надо учитывать.

– Henadzi

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

  1. Спред.
  2. Проскальзывание.
  3. Комиссия (для счетов типа ECN).
  4. Своп.

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

Q&A:

Спред, проскальзывание и комиссия – константы, неизменные во времени, воздействие которых на конечный результат определяется только объёмом сделки?

– cakeplus

Да, по сути это константы, и вы правильно сформулировали. Я их назвал переменными по привычке. Во встроенном языке МТ внешние параметры программы объявляются, как переменные, только типа extern, и им можно внутри программы присвоить другие значения, отличные от первоначально заданных. А тестирование средствами МТ4 сводится к перебору значений этих внешних параметров из заданного диапазона с заданным шагом, сортировке полученных результатов по разным критериям, и выбору оптимального набора значений этих внешних параметров. В дальнейшем, при работе торгового алгоритма, они обычно остаются неизменны. Так и здесь. Все косты - спред, своп, и проскальзывания, в процессе формирования портфеля будут неизменны, но свои для каждого торгового инструмента. А вот объём сделки по каждой стратегии можно менять, и затем смотреть, какая кривая доходности будет у портфеля получаться, какие просадки, какая прибыль. Сначала составлять портфель для фиксированного лота, регулируя долю каждой стратегии в общем портфеле. А затем включать реинвестирование.

– Henadzi

2.1 Информация об инструментах

Для вычисления профита нам понадобится информация об используемых торговых инструментах, которую можно взять в спецификации контрактов Альпари:

  1. Величина лота. Лот – это такая фиговина, доля которой, участвующая в сделке, обозначается значением Size в отчёте.
  2. Стоимость пункта на лот в валютной котировке.

Косты:

  1. Спред ("Typical Spreads" в спецификации).
  2. Своп ("Swap Short" и "Swap Long").
  3. Комиссия (пока не учитываем).
  4. Проскальзывание. Придумываем значение сами.

За единицу измерения коста берём количество пунктов за лот (пункты/лот), которое нужно будет домножать на size и стоимость пункта при вычислении профита.

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

  (* file: instruments.ml *)

  module type Instrument = sig
    val lot_size: float
    val pip_size: float
    val swap_long: float
    val swap_short: float
    val slippage: float
    val spread: float
    val commission: float
  end

  (* Дефолтные параметры инструмента (нулевые косты) *)
  module Default = struct
    let swap_long = 0.0
    let swap_short = 0.0
    let slippage = 0.0
    let spread = 0.0
    let commission = 0.0
  end

  <<instruments>>
  <<instruments_nocosts>>

Набор инструментов с учётом костов:

  (* ref: instruments *)

  module EURUSD: Instrument = struct
    include Default
    let lot_size = 100_000.0
    let pip_size = 10.0
    let spread = 3.0
    let slippage = 0.0
    let swap_long = -0.39
    let swap_short = -0.10
  end

  module AUDUSD: Instrument = struct
    include Default
    let lot_size = 100_000.0
    let pip_size = 10.0
    let spread = 4.0
    let slippage = 0.0
    let swap_long = 0.47
    let swap_short = -0.66
  end

  module GBPUSD: Instrument = struct
    include Default
    let lot_size = 100_000.0
    let pip_size = 10.0
    let spread = 3.0
    let slippage = 0.0
    let swap_long = 0.02
    let swap_short = -0.23
  end

  module XAUUSD: Instrument = struct
    include Default
    let lot_size = 100.0
    let pip_size = 1.0
    let spread = 50.0
    let slippage = 0.0
    let swap_long = -4.37
    let swap_short = -0.97
  end

По поводу оценки проскальзываний:

Для евро и для фунта я поставил проскальзывания такими, чтобы сумма спред+проскальзывание равнялась 2 большим пунктам. На самом деле, это завышенное значение костов, ведь мои стратегии открывают позиции только маркетами, и закрывают тоже часто маркет приказами.

– Henadzi

Для золота проскальзывание 10 пунктов - это реальная цифра. avp555 озвучил свою статистику, у него получилось 13 пунктов, но это при закрытии позиций, то есть для отложенных ордеров. А мы считаем вместе для любых ордеров - и для маркетов, и для отложенных, поэтому можно смело брать значение не 13, а 10.

– Henadzi

Набор инструментов без учёта костов (пригодится для сравнений):

  (* ref: instruments_nocosts *)

  module NoCosts = struct
    module type Instrument = Instrument

    module EURUSD: Instrument = struct
      include Default
      let lot_size = 100_000.0
      let pip_size = 10.0
    end

    module AUDUSD: Instrument = struct
      include Default
      let lot_size = 100_000.0
      let pip_size = 10.0
    end

    module GBPUSD: Instrument = struct
      include Default
      let lot_size = 100_000.0
      let pip_size = 10.0
    end

    module XAUUSD: Instrument = struct
      include Default
      let lot_size = 100.0
      let pip_size = 1.0
    end

  end

2.2 Прибыль/убыток

Величину сделки рассчитываем по вышеприведённым формулам из документации Альпари. Для вычисления профита одной сделки понадобятся две строки отчёта: операция открытия и операция закрытия. Строка отчёта представлена структурой, описанной в модуле Report.

Воздействие (penalty) на результат сделки костов (costs):

costs = спред + проскальзывание + комиссия + своп <br> penalty = стоимость пункта * size * costs

  (* ref: calculating_profit *)

  module ProfitCalculator(I: Instrument): sig

    val calc_profit: Report.row -> Report.row -> float

  end = struct

    <<swap_calculation>>

    let calc_profit open_order close_order =
      let penalty =
        let swap = calc_swap open_order close_order in
        let costs = I.spread +. I.slippage +. I.commission +. swap in
        I.pip_size *. open_order.size *. costs
      in
      let contract' = open_order.size *. I.lot_size in
      match open_order.op with
        | `Buy ->
            contract' *. (close_order.price -. open_order.price) -. penalty
        | `Sell ->
            contract' *. (open_order.price -. close_order.price) -. penalty
        | _ ->
            failwith "calc_profit: order type must be either 'buy' or 'sell'"

   end

2.3 Своп

Свопы действительно могут рассчитываться по разному - и для разных типов счетов, и во времени они меняются. Именно в данном случае, при расчёте свопов, заморачиваться и вникать особо не надо - их влияние на результаты торговли в сравнении со спредом и проскальзываниями невелико. Проще всего учитывать их так же, как их учитывает терминал МТ, когда генерирует тестовые отчёты. Логично и брать значение свопов прямо из терминала в момент создания исходных отчётов стратегий, тогда можно будет легко проверять правильность работы продукта - при фиксированном лоте результаты должны сходиться с репортом для этого же портфеля, полученным из тех же отчётов при помощи ReportManager-а. Алгоритм расчёта по дням недели простой - в ночь со среды на четверг взимается тройной размер свопа, а в остальные ночи - одинарный. Значения свопа из терминала на текущий момент: для EURUSD ( -2.5 / -0.1 ), для GBPUSD ( +0.1 / -3.7 ), и для XAUUSD ( -4.69 / -1.09 ). Своп указан в пунктах, первое значение в скобках - для длинных (покупок), а второе - для коротких (продаж) позиций. То есть формула его учёта в долларах такова:

Результат сделки со свопом = Результат сделки без свопа + своп х 0.00001 х size х (количество переходов позиции через ночь кроме ночи со среды на четверг) + 3 х своп х 0.00001 х size х (количество переходов позиции через ночь со среды на четверг).

– Henadzi

Количество переносов позиции через сутки считаем путём подсчёта дней недели во временном интервале между открытием и закрытием сделки (выкидывая последний день). Выходные дни не считаются, а среда считается трижды.

Таким образом, если сделка была закрыта в день открытия, то количество переносов будет равным нулю. Если сделка открыта в пятницу, а закрыта в понедельник, то посчитается один своп.

  (* ref: swap_counting *)

  let swaps_per_day d =
    match Calendar.Date.day_of_week d with
      | Mon -> 1
      | Tue -> 1
      | Wed -> 3
      | Thu -> 1
      | Fri -> 1
      | Sat -> 0
      | Sun -> 0

  let count_swaps open_time close_time : int =
    let d1 = Calendar.to_date open_time in
    let d2 = Calendar.to_date close_time in
    Enum.seq d1
      (fun d -> Calendar.Date.next d `Day)
      (fun d -> not (Calendar.Date.equal d d2))
    |> map swaps_per_day
    |> Enum.sum

Косты мы считаем в пунктах на лот, поэтому используем формулу:

своп = своп в пунктах * количество переносов позиции

  (* ref: swap_calculation *)

  <<swap_counting>>

  let calc_swap open_order close_order : float =
    let swap_ct = count_swaps open_order.time close_order.time in
    match open_order.op with
      | `Buy -> I.swap_long  *. float_of_int (swap_ct)
      | `Sell -> I.swap_short *. float_of_int (swap_ct)
      | _ ->
         failwith "calc_swap: order type must be either 'buy' or 'sell'"

3 Перевычисление баланса

Программа получает на вход отчёт и возвращает отчёт с перерасчитанным полем balance:

  (* ref: order_processing_sig *)

  module type Processor = sig

    val process_report: Report.t -> Report.t

  end

Отчёт представлен структурой, описанной в модуле Report.

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

Чем торговля долей отличается от торговли фиксированным лотом? – Тем, как вычисляется объём сделки в зависимости от текущего баланса. Значит механизм наш будет с "дыркой" – функцей calc_size, пересчитывающей объём сделки в зависимости от состояния баланса.

Ещё одной "дыркой" можно сделать начальную величину баланса: пригодится.

  (* ref: order_processing *)

  <<order_processing_sig>>

  module MakeProcessor (P: sig

    val start_balance: float
    val calc_size: size: float -> balance: float -> float

  end): Processor = struct

    <<calculating_profit>>
    <<dataset_processing>>

  end

3.1 Обработка массива данных

Мы идём по списку торговых операций (в хронологическом порядке) и обновляем два состояния: orders – информацию об открытых сделках (используется при вычислении профита сделки при закрытии) и balance – текущее состояние баланса, которое инициализируется константой P.start_balance. Этот же баланс выводим в результирующий отчёт.

  (* ref: dataset_processing *)

  <<row_processing>>

  let process_report report =
    let orders = Hashtbl.create 10000 in
    let balance = ref P.start_balance in
    report |> List.map (fun row ->
      balance := process_row !balance orders row;
      { row with balance = Some !balance })

3.2 Моделирование выполнения торговых операций

Операции типа buy и sell добавляют в контекст orders информацию об открытых сделках. Закрывающие операции изменяют баланс (используя для вычисления профита ранее описанные функции).

  (* ref: row_processing *)

  let parse_instrument = function
    | "XAUUSD" -> (module XAUUSD: Instrument)
    | "EURUSD" -> (module EURUSD: Instrument)
    | "GBPUSD" -> (module GBPUSD: Instrument)
    | "AUDUSD" -> (module AUDUSD: Instrument)
    | _ ->
        failwith "parse_instrument: Invalid symbol"

  ' contract (balance > 0.)
  let process_row balance orders row =
    match row.op with
      | `Buy ->
          Hashtbl.add orders row.order_id
            { row with size = P.calc_size ~size:row.size ~balance };
          balance

      | `Sell ->
          Hashtbl.add orders row.order_id
            { row with size = P.calc_size ~size:row.size ~balance };
          balance

      | `Close | `SL | `TP | `CloseAtStop ->
          begin match Hashtbl.find_option orders row.order_id with
            | Some open_order ->
                let module I = (val (parse_instrument row.asset): Instrument) in
                let module P = ProfitCalculator (I) in
                balance +. P.calc_profit open_order row
            | None ->
                balance
          end

      | `Modify -> balance

4 Сборка портфеля

4.1 Стратегии

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

  (* ref: strategies *)

  let strategy_list =
    [
      ("gradus_aud_1", 0.01);
      ("gradus_eur_1", 0.01);
      ("gradus_eur_2", 0.01);
      ("gradus_eur_3", 0.01);
      ("gradus_gbp_1", 0.01);
      ("gradus_xau_1", 0.01);
    ]

absolute_trading_strategies.png

Figure 2: Результаты тестирования по стратегиям.

4.2 Объединение отчётов

Каждый из входных отчётов подвергается препроцессингу, после чего выполняется объединение. Препроцессинг отчёта заключается в замене size в сделках на весовой коэффициент, соответствующий стратегии. В объединённый отчёт попадают все строки входных отчётов, отсортированные по дате.

Важный момент: для всех сделок выполняется перенумерация (в хронологическом порядке открытия сделок в объединённом отчёте). Перенумерацию необходимо выполнять затем, чтобы не возникало конфликтов между сделками с одинаковыми номерами из разных отчётов.

  (* ref: building_portfolio *)

  let build_portfolio (list: (Report.t * float) list) : Report.t =
    let gen_id =
      let ids = Hashtbl.create 1000 in
      let cur = ref 0 in
      fun key ->
        match Hashtbl.find_option ids key with
          | Some id -> id
          | None ->
              incr cur;
              Hashtbl.add ids key !cur;
              !cur
    in
    list
    |> List.map (fun (report, size') ->
         report |> List.map (fun row -> { row with size = size' }))
    |> List.mapi (fun id -> List.map (fun row -> (id, row)))
    |> List.concat
    |> List.sort (fun (id1, row1) (id2, row2) ->
         Calendar.compare row1.time row2.time)
    |> List.map (fun (id, row) ->
         { row with order_id = gen_id (id, row.order_id) })

5 Торговля фиксированным лотом

Пробуем воспроизвести торговлю фиксированным лотом (с начальным балансом в $10000). Функция вычисления объёма сделки оставляет Size неизменным:

  (* ref: fixed_processor *)

  <<order_processing>>

  module FixedLotProcessor = MakeProcessor (struct
      let start_balance = 500.0
      let calc_size ~size ~balance = size
    end)

Собираем программу:

  open Batteries
  open CalendarLib
  open Report
  open Instruments

  <<strategies>>
  <<building_portfolio>>
  <<fixed_processor>>

  let () =
    let db = PGOCaml.connect () in
    strategy_list
    |> List.map (fun (name, size) ->
         (Database.get_report db ~name, size))
    |> build_portfolio
    |> FixedLotProcessor.process_report
    |> Database.add_report db ~rewrite:true ~name:Sys.argv.(1)

Без учёта костов (для сравнения):

  open Batteries
  open CalendarLib
  open Report
  open Instruments.NoCosts

  <<strategies>>
  <<building_portfolio>>
  <<fixed_processor>>

  let () =
    let db = PGOCaml.connect () in
    strategy_list
    |> List.map (fun (name, size) ->
         (Database.get_report db ~name, size))
    |> build_portfolio
    |> FixedLotProcessor.process_report
    |> Database.add_report db ~rewrite:true ~name:Sys.argv.(1)

absolute_trading_fixed.png

Figure 3: Результат торговли фиксированным лотом.

Получилась картинка, очень похожая на картинку из тестера стратегий. Конечная величина баланса ненамного отличается от баланса из отчёта (361040.23 против 353506.92). Это говорит о том, что вычислительные погрешности не оказали существенного влияния на конечный результат. Также мы убедились в работоспособности программы.

6 Торговля долей

Теперь мы делаем так, чтобы объём сделки увеличивался (или уменьшался) пропорционально изменению баланса:

  (* ref: frac_processor *)

  <<order_processing>>

  module FracLotProcessor = MakeProcessor (struct
      let start_balance = 500.0
      let calc_size ~size ~balance =
        size *. balance /. start_balance
    end)

Собираем программу:

  open Batteries
  open CalendarLib
  open Report
  open Instruments

  <<strategies>>
  <<building_portfolio>>
  <<frac_processor>>

  let () =
    let db = PGOCaml.connect () in
    strategy_list
    |> List.map (fun (name, size) ->
         (Database.get_report db ~name, size))
    |> build_portfolio
    |> FracLotProcessor.process_report
    |> Database.add_report db ~rewrite:true ~name:Sys.argv.(1)

Q&A:

Алгоритм я вижу примерно таким - идём последовательно по датам и реинвестируем прибыль, либо убытки, после изменения баланса не менее, чем на 10% (можно и 5%, но ещё меньше шаг брать не надо).

– Henadzi

Я прибыль сразу реинвестирую (изменения баланса учитываются при расчёте объёма каждой сделки). Как это может навредить? Зачем нужен порог в 5-10 процентов?

– cakeplus

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

–Henadzi

absolute_trading_fraction.png

Figure 4: Результат торговли долей.

Нихуя себе! Пугающе огромные числа. На этом графике почти ничего не понятно, пробуем нарисовать логарифмический:

absolute_trading_fraction_log.png

Figure 5: Результат торговли долей (логарифмический график).

Опаньки! Получили копию графика торговли фиксированным лотом, только с доходностью на порядки выше.

В чём подвох? Где ошибка? Жду комментариев.

Update:

Заоблачных итоговых сумм не пугайтесь. Так и должно быть. Но чудес в жизни не бывает, с ростом суммы стратегии будут терять эффективность - им попросту не будет хватать ликвидности для быстрого открытия/закрытия позиций. Речь, правда, идёт об очень больших суммах, начиная с которых эффективность заметно снижается, и нам это пока совсем не грозит. :–) А вот крупные инвестиционные фонды подобных быстрых стратегий позволить себе не могут - именно по причине недостатка ликвидности на рынке для тех средств, которыми они управляют… Так что всё сбалансировано, и всё имеет свой предел - чем больше сумма, тем меньшую на ней можно получить доходность.

– Henadzi

Взглянем на графики доходности по годам (при торговле долей).

Примечание:

На 2011 и 2012 годах производилась настройка параметров стратегий, поэтому и график там ровнее, но на него ориентироваться нельзя, это только при очень большом везении так могло бы всё складываться. Поэтому реальные результаты следует ожидать по динамике и по просадкам примерно такие, как за три года Out-of-Sample, с 2008 по 2010 включительно. Так что самый значимый для анализа и оценки период - это 2010, 2009 и 2008 годы, а 2011 и 2012 заведомо "приукрашены" тестером стратегий, и этого приукрашивания полностью никак не избежать, хоть я и настраиваю стратегии для надёжности довольно грубо.

– Henadzi

absolute_trading_fraction_percent_2008.png

Figure 6: Результат торговли долей (2008).

Видим одну резкую и глубокую просадку (более 50% за месяц).

absolute_trading_fraction_percent_2009.png

Figure 7: Результат торговли долей (2009).

Видим стремительный ранап в ноябре (300% за полмесяца).

absolute_trading_fraction_percent_2010.png

Figure 8: Результат торговли долей (2010).

А тут получилась астрономическая доходность без глубоких просадок, хотя этот год не использовался для оптимизации стратегий.

absolute_trading_fraction_percent_2011.png

Figure 9: Результат торговли долей (2011).

Понятно, что на данных этого года стратегии оптимизировались.

absolute_trading_fraction_percent_2012.png

Figure 10: Результат торговли долей (2012).

Тоже красивый график.

absolute_trading_fraction_percent_2013.png

Figure 11: Результат торговли долей (2013).

А вот это уже бектест по данным того периода, в котором велась реальная торговля. График несколько отличается от графика ПАММа: пиковая доходность в 200% вместо 100%, и ниже нуля график ни разу не опускается.

Интересно, почему?

Видимо, потому, что в просадке Геннадий вносил изменения в портфель. И всё равно график мало похож на результаты тестирования за предыдущие годы. Случился крайне неблагоприятный для стратегий период? По графикам отдельных стратегий видно, что значительный вклад в текущую просадку внесли стратегии, торгующие на золоте; при этом портфель несколько смягчил конечный убыток.

Попробуем убрать из портфеля стратегии, торгующие золотом:

  open Batteries
  open CalendarLib
  open Report
  open Instruments

  <<strategies>>
  <<building_portfolio>>
  <<frac_processor>>

  let () =
    let db = PGOCaml.connect () in
    strategy_list
    |> List.filter (fun (name, size) ->
         not (String.exists name "xau"))
    |> List.map (fun (name, size) ->
         (Database.get_report db ~name, size))
    |> build_portfolio
    |> FracLotProcessor.process_report
    |> Database.add_report db ~rewrite:true ~name:Sys.argv.(1)
  open Batteries
  open CalendarLib
  open Report
  open Instruments

  <<strategies>>
  <<building_portfolio>>
  <<fixed_processor>>

  let () =
    let db = PGOCaml.connect () in
    strategy_list
    |> List.map (fun (name, size) ->
         (name, Database.get_report db ~name))
    |> List.map (fun (name, report) ->
         (name ^ Sys.argv.(1), FixedLotProcessor.process_report report))
    |> List.iter (fun (name, report) ->
         Database.add_report db ~rewrite:true ~name report)

absolute_trading_fraction_percent_2013_noxau.png

Figure 12: Результат торговли долей (2013), без стратегий по золоту.

Ну вот, совсем другое дело! Просадка уменьшилась с 60% до гораздо менее пугающих 20%. Впрочем, пиковая доходность тоже уменьшилась, т.к. в предшествующем просадке ранапе золото также сыграло большую роль.

7 Выводы

В этом году с золотом произошли аномалии, взявшие врасплох стратегии, тестировавшиеся на данных 2008-2012 годов. За вычетом золота результаты торговли пока согласуются с графиками тестирования стратегий.

Больше никаких выводов сделать не получается, а нам, инвесторам, остаётся ждать и мечтать о таких ранапах, какой показали тесты за ноябрь 2009 года :-)

comments powered by Disqus