Будучи инвестором ПАММ-счёта Absolute Trading, меня заинтересовала глубина текущей просадки, а именно – насколько такой результат согласуется с результатами тестирования на исторических данных используемых стратегий автоматической торговли.
К сожалению (по техническим причинам), результат back-тестирования стратегий, предоставленный управляющим (Henadzi), демонстрирует торговлю фиксированным лотом, в то время как в реальности торговля ведётся долей от средств. Мне, как неискушённому инвестору, сложно соотнести график реальной торговли с графиком тестирования, т.к. визуально графики сильно отличаются.
В связи с этим появилась идея перерасчёта результатов тестирования таким образом, как если бы торговля велась долей от средств, с построением соответствующих графиков, а Геннадий решил мне помочь, предоставив необходимые данные и дав исчерпывающую консультацию по ряду вопросов.
Данная статья представляет собой результат компиляции программы, написанной в стиле Literate Programming.
Update (2013-08-08):
- Работа с CSV вынесена в модуль Report и не используется в новой версии программы.
- Входные данные программа теперь получает из базы данных и результат обработки тоже записывает в базу (используя функции неопубликованного модуля
Database).- Индексы сделок теперь представлены не строками, а целыми числами (и остаются ими после сборки портфеля).
- Мелкий рефакторинг.
– cakeplus
Даны результаты тестирования торговых стратегий (последовательность операций) в виде отчётов 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 |
Figure 1: Результат тестирования портфеля (график из ReportManager).
Каждая из стратегий портфеля совершает сделки фиксированного объёма (Size в таблице). Order – номер сделки. Сделка состоит из операций открытия (buy или sell) и закрытия (close, s/l, t/p, close at stop).
Необходимо пересчитать все сделки так, как если бы торговля велась долей от баланса (именно от баланса, а не от средств, по причине отсутствия необходимых данных). Кроме того, необходимо собрать из стратегий портфель (назначая стратегиям установленные Геннадием весовые коэффициенты).
Пояснение:
Реинвестирование идёт от средств, а не от баланса, то есть в реальной торговле при открытии новой сделки учитывается и незафиксированный профит или лосс. Но программных средств для такого анализа под МТ я не нашёл, только ReportManager этот портфели может формировать, а он по балансу считает. Так что, в любом случае, будет погрешность. Но ей можно и пренебречь, общая картина всё равно будет отражать динамику верно.
– Henadzi
Что представляет из себя торговля долей? – Если при балансе в 10000 сделка открывалась с объёмом 0.24, то при балансе в 20000 объём должен быть удвоенным, т.е. 0.48 лота. Так, с ростом доходности объём открываемых сделок будет увеличиваться, а в просадках – уменьшаться.
Решать задачу будем путём последовательного пересчёта результата всех сделок с одновременным пересчётом баланса.
Первая подзадача – научиться вычислять профит сделки по значениям Size и Price. Алгоритм описан в справке Альпари:
Величина прибыли / убытка рассчитывается по формуле:
для позиции Buy (покупка):
Profit/Loss = (Contract × ClosePrice) - (Contract × OpenPrice);
для позиции Sell (продажа):
Profit/Loss = (Contract × OpenPrice) - (Contract × ClosePrice),
где:
Profit/Loss — величина прибыли / убытка в валюте котировки;
Contract — величина контракта в базовой валюте;
ClosePrice — цена закрытия валютной пары;
OpenPrice — цена открытия валютной пары.
Пример расчёта для первой сделки:
Результат не сходится с профитом из отчёта (получили $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
Значит при тестировании нужно учитывать дополнительные косты, значения которых отличаются для разных торговых инструментов:
Оценка костов должна быть заведомо большей, с запасом, чем в среднем будут их значения при реальной торговле.
Q&A:
Спред, проскальзывание и комиссия – константы, неизменные во времени, воздействие которых на конечный результат определяется только объёмом сделки?
– cakeplus
Да, по сути это константы, и вы правильно сформулировали. Я их назвал переменными по привычке. Во встроенном языке МТ внешние параметры программы объявляются, как переменные, только типа extern, и им можно внутри программы присвоить другие значения, отличные от первоначально заданных. А тестирование средствами МТ4 сводится к перебору значений этих внешних параметров из заданного диапазона с заданным шагом, сортировке полученных результатов по разным критериям, и выбору оптимального набора значений этих внешних параметров. В дальнейшем, при работе торгового алгоритма, они обычно остаются неизменны. Так и здесь. Все косты - спред, своп, и проскальзывания, в процессе формирования портфеля будут неизменны, но свои для каждого торгового инструмента. А вот объём сделки по каждой стратегии можно менять, и затем смотреть, какая кривая доходности будет у портфеля получаться, какие просадки, какая прибыль. Сначала составлять портфель для фиксированного лота, регулируя долю каждой стратегии в общем портфеле. А затем включать реинвестирование.
– Henadzi
Для вычисления профита нам понадобится информация об используемых торговых инструментах, которую можно взять в спецификации контрактов Альпари:
Косты:
За единицу измерения коста берём количество пунктов за лот (пункты/лот), которое нужно будет домножать на 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
Величину сделки рассчитываем по вышеприведённым формулам из документации Альпари. Для вычисления профита одной сделки понадобятся две строки отчёта: операция открытия и операция закрытия. Строка отчёта представлена структурой, описанной в модуле 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
Свопы действительно могут рассчитываться по разному - и для разных типов счетов, и во времени они меняются. Именно в данном случае, при расчёте свопов, заморачиваться и вникать особо не надо - их влияние на результаты торговли в сравнении со спредом и проскальзываниями невелико. Проще всего учитывать их так же, как их учитывает терминал МТ, когда генерирует тестовые отчёты. Логично и брать значение свопов прямо из терминала в момент создания исходных отчётов стратегий, тогда можно будет легко проверять правильность работы продукта - при фиксированном лоте результаты должны сходиться с репортом для этого же портфеля, полученным из тех же отчётов при помощи 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'"
Программа получает на вход отчёт и возвращает отчёт с перерасчитанным полем 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
Мы идём по списку торговых операций (в хронологическом порядке) и обновляем два состояния: 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 })
Операции типа 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
Результат тестирования каждой из стратегий представлен в виде отдельного отчёта. Каждой стратегии ставится в соответствие весовой коэффициент, определяющий размер открываемых стратегией позиций:
(* 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); ]
Figure 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) })
Пробуем воспроизвести торговлю фиксированным лотом (с начальным балансом в $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)
Figure 3: Результат торговли фиксированным лотом.
Получилась картинка, очень похожая на картинку из тестера стратегий. Конечная величина баланса ненамного отличается от баланса из отчёта (361040.23 против 353506.92). Это говорит о том, что вычислительные погрешности не оказали существенного влияния на конечный результат. Также мы убедились в работоспособности программы.
Теперь мы делаем так, чтобы объём сделки увеличивался (или уменьшался) пропорционально изменению баланса:
(* 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
Figure 4: Результат торговли долей.
Нихуя себе! Пугающе огромные числа. На этом графике почти ничего не понятно, пробуем нарисовать логарифмический:
Figure 5: Результат торговли долей (логарифмический график).
Опаньки! Получили копию графика торговли фиксированным лотом, только с доходностью на порядки выше.
В чём подвох? Где ошибка? Жду комментариев.
Update:
Заоблачных итоговых сумм не пугайтесь. Так и должно быть. Но чудес в жизни не бывает, с ростом суммы стратегии будут терять эффективность - им попросту не будет хватать ликвидности для быстрого открытия/закрытия позиций. Речь, правда, идёт об очень больших суммах, начиная с которых эффективность заметно снижается, и нам это пока совсем не грозит. :–) А вот крупные инвестиционные фонды подобных быстрых стратегий позволить себе не могут - именно по причине недостатка ликвидности на рынке для тех средств, которыми они управляют… Так что всё сбалансировано, и всё имеет свой предел - чем больше сумма, тем меньшую на ней можно получить доходность.
– Henadzi
Взглянем на графики доходности по годам (при торговле долей).
Примечание:
На 2011 и 2012 годах производилась настройка параметров стратегий, поэтому и график там ровнее, но на него ориентироваться нельзя, это только при очень большом везении так могло бы всё складываться. Поэтому реальные результаты следует ожидать по динамике и по просадкам примерно такие, как за три года Out-of-Sample, с 2008 по 2010 включительно. Так что самый значимый для анализа и оценки период - это 2010, 2009 и 2008 годы, а 2011 и 2012 заведомо "приукрашены" тестером стратегий, и этого приукрашивания полностью никак не избежать, хоть я и настраиваю стратегии для надёжности довольно грубо.
– Henadzi
Figure 6: Результат торговли долей (2008).
Видим одну резкую и глубокую просадку (более 50% за месяц).
Figure 7: Результат торговли долей (2009).
Видим стремительный ранап в ноябре (300% за полмесяца).
Figure 8: Результат торговли долей (2010).
А тут получилась астрономическая доходность без глубоких просадок, хотя этот год не использовался для оптимизации стратегий.
Figure 9: Результат торговли долей (2011).
Понятно, что на данных этого года стратегии оптимизировались.
Figure 10: Результат торговли долей (2012).
Тоже красивый график.
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)
Figure 12: Результат торговли долей (2013), без стратегий по золоту.
Ну вот, совсем другое дело! Просадка уменьшилась с 60% до гораздо менее пугающих 20%. Впрочем, пиковая доходность тоже уменьшилась, т.к. в предшествующем просадке ранапе золото также сыграло большую роль.
В этом году с золотом произошли аномалии, взявшие врасплох стратегии, тестировавшиеся на данных 2008-2012 годов. За вычетом золота результаты торговли пока согласуются с графиками тестирования стратегий.
Больше никаких выводов сделать не получается, а нам, инвесторам, остаётся ждать и мечтать о таких ранапах, какой показали тесты за ноябрь 2009 года :-)