English 中文 Español Deutsch 日本語 Português Italiano
Рыночная математика: прибыль, убыток, издержки

Рыночная математика: прибыль, убыток, издержки

MetaTrader 5Примеры | 23 августа 2022, 14:27
3 045 9
Evgeniy Ilin
Evgeniy Ilin

Разделы

Введение

Достаточно долгое время, занимаясь советниками, я старался не обращать внимание на то, что значат те или иные величины при расчетах прибыли или убытка. Главное в этом то, что для того, чтобы писать советники, вовсе не обязательно вникать максимально глубоко в суть вопроса. Да и зачем это делать, когда MQL5 и даже MQL4 содержат весь необходимый функционал для вычисления? Вопрос риторический, на самом деле. Тем не менее после определенного времени и определенного пути, неизбежно возникают вопросы, потому что мы начинаем замечать такие детали, которые раньше нам казались несущественными. И это неизбежно влечет осознание того факта, что я держу кота в мешке. Это одна сторона медали. Второй стороной медали явился тот факт, что после того, как я уже заинтересовался вопросом и начал искать информацию в интернете, если я что-то находил в интернете, то эта информация была, во-первых, рваная и не структурированная, во-вторых, по моему ощущению, содержала не более двадцати процентов той информации, которую мне бы хотелось получить. Глядя на это все я решил исправить данный огрех. После прочтения этой статьи вы получите законченную и работающую математическую модель и научитесь понимать и правильно считать все, что связано с ордерами.

Формулы для вычисления прибыли или убытка ордеров

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

Тут я хочу заметить, что написание советников дисциплинирует ваши мысли и заставляет разбираться что и как считается, и это бесценно. Теперь перейдем к сути. Начать стоит с простейшего представления о том, как считается прибыль ордера. Лично я всегда знал, что расчет прибыли довольно сложен в своей основе, но сконструирован на основе нескольких простых соображений. Для упрощения понимания нужно принять что спреда не существует, как свопа и комиссии. Да и я думаю многие даже в расчет эти величины не берут по началу. Конечно, в языке MQL5 предусмотрены встроенные функции, такие как OrderCalcProfit, возможно и другие, но в данной статье я хочу пройти по основе основ, чтобы все понимали, что и как считается. Подобная дотошность может вызывать недоумение, но это и является фатальной ошибкой, которую допускают многие трейдеры, не обращая внимание на такие величины как спред, комиссию и своп. Каждая из этих величин по-своему влияет на прибыль или убыток. В своих расчетах я буду учитывать все и показывать, насколько подобные мелочи могут вас вооружить. Прибыль и убыток ордеров без учета спредов, комиссии, свопов:

  • PrBuy = Lot * TickValue * [ ( PE - PS )/Point ] — прибыль для ордера на покупку
  • PrSell = Lot * TickValue * [ ( PS - PE )/Point ] — прибыль для ордера на продажу
  • Point — размер минимально возможного изменения цены на выбранном инструменте
  • TickValue — величина прибыли для прибыльной позиции при движении цены на “1” Point
  • PE — цена закрытия трейда (Bid)
  • PS — цена открытия трейда (Bid)

Величины, такие как Point и TickValue, в MQL5 определены на уровне предопределенных переменных либо доступны в рамках встроенного функционала, возвращаемого значения функций типа SymbolInfoDouble и подобных ей. Вообще привыкайте, что я буду так или иначе касаться MQL5 в своих статьях хотя бы потому, что, даже анализируя, как построен MQL5, ну или хотя бы отдельный его функционал, можно докопаться до сути и узнать то, что в конечном итоге поможет вам углубиться в суть вопроса.

Теперь немного расширим понимание данной формулы. Известно, что при открытии ордера Buy, ордер открывается по цене Ask, а для Sell по цене Bid. И соответственно наоборот: при закрытии Buy мы закрываем по Bid, а при закрытии Sell мы закрываем по Ask. Давайте перепишем формулы с учетом новых поправок:

  • PrBuy = Lot * TickValue * [ ( Bid2 – Ask1 )/Point ] — прибыль для ордера на покупку
  • PrSell = Lot * TickValue * [ ( Bid1 – Ask2 )/Point ] — прибыль для ордера на продажу
  • Bid1 — цена открытия трейда Sell
  • Ask1 — цена открытия трейда Buy
  • Bid2 — цена закрытия трейда Buy
  • Ask2 — цена закрытия трейда Sell

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

required data

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

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

В итоге с комиссии и со свопа имеем два, казалось бы, разных метода, но, как мы увидим, эти методы являются просто удобной формой восприятия одного и того же метода “налогообложения”, в том числе и спред. Напишу две формулы для вычисления комиссии:

  1. Comission = Lot * TickValue * ComissionPoints
  2. Comission = Lot * ContractSize * BidAlpha * ComissionPercent/100

Здесь мы видим, что появляется новая величина “ContractSize”, которая также имеет реализацию в MQL5 на уровне вшитого функционала, получающего информацию с торгового сервера. Эта величина является одной из важнейших и присутствует абсолютно во всех расчетах прибыли и убытка, хотя и в неявном виде, для того чтобы упростить программисту вычисления. Я вижу обоснованность подобных упрощений с точки зрения работы программиста. Но в нашем случае мы будем разбираться что, откуда и зачем. Зачем это нужно будет видно ближе к концу статьи. Кроме этого, я ввел дополнительную переменную BidAlpha, смысл которой тоже раскрою чуть ниже. Также появляются соответственно следующие величины, которые указываются в спецификации инструмента:

  • ComissionPoints – комиссия в пунктах
  • ComissionPercent – комиссия в процентах от размера контракта
Множитель BidAlpha нужен для того, чтобы переводить своп в единицах базовой валюты в своп в единицах нашего баланса. Здесь существует четыре сценария:
  1. BidAlpha = 1 (в случае если базовая валюта совпадает с валютой депозита)
  2. BidAlpha = Bid (выбранного инструмента)
  3. BidAlpha = Bid (соответствующего курса, где базовая валюта выбранного инструмента совпадает с базовой валютой переходного инструмента, а вторая валюта совпадает с валютой депозита)
  4. BidAlpha = 1/Ask (соответствующего курса, где базовая валюта выбранного инструмента совпадает со второй валютой переходного инструмента, а базовая валюта совпадает с валютой нашего депозита)

Действительно если размер контракта применяется к паре типа USDCHF, понятно, что базовой валютой выбранной пары является USD. Допустим, у нас депозит в USD, тогда переводная валюта становится USDUSD и, соответственно, ее курс всегда будет равен единице. Второй случай еще проще. Например, у нас пара EURUSD, она же и является переводным курсом, поэтому ее Bid и является искомой величиной. Третий случай может быть такой. Наша валюта, к примеру, EURNZD. Тогда получается, мы должны найти переводной курс с EUR и USD. Такой курс EURUSD, и Bid этого курса и будет являться тем, что нам нужно. В четвертом случае все немного сложнее. Например, мы выбрали инструмент CHFJPY. Понятно, что переводной парой является USDCHF, так как курса CHFUSD нет на форексе. Конечно, мы можем создать свой синтетический инструмент и работать с CHFUSD, тогда мы сможем использовать предыдущий вариант. Но на самом деле нам нужно просто перевернуть этот инструмент, тогда его курс и станет равным “1/Ask” текущего неудобного нам курса. По сути, данным действием мы и создаем этот синтетический инструмент, просто не акцентируя внимание на этом. Если уж я взялся за подобный материал я должен вам разжевать все от начала и до конца. Те же вещи будут справедливы и для свопов. Есть еще вопросы, которыми задается мой мозг. Например, какой курс использовать в переводной валюте, Bid, Ask или все-таки Mid? В рамках подобного подхода это неразрешимый вопрос. Постепенно в ходе развития идеи мы придем к правильному подходу, и все белые пятна будут закрашены. А сейчас давайте хотя бы поверхностно определим каркас для улучшения. Для этого мы должны написать хотя бы первый приближенный вариант общей формулы прибыли и убытков, учитывающий все варианты “налогообложения”, такие как спред, своп, комиссию.

Для вычисления свопов получим аналогичные формулы:

  1. Swap = Lot * TickValue * SwapPoints * SwapCount(StartTime,EndTime)
  2. Swap = Lot * ContractSize * BidAlpha * SwapPercent/100 * SwapCount(StartTime,EndTime)

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

  • SimpleCount = MathFloor( (EndTime -StartTime) / ( 24 * 60 * 60 ) )

Если принять, что EndTime и StartTime имеют тип datetime, то их разница будет равна количеству секунд между точками открытия и закрытия ордера. Своп начисляется раз в сутки, поэтому просто эту величину нужно разделить на количество секунд в одних сутках. Так можно получить первые представления о том, как можно оценивать своп позиции. Но, конечно же, эта формула неверная — строго говоря, но она дает ответ на вопрос о том, что это за функция и что она возвращает, и просто может хотя бы навести на мысли, как же (хотя бы примерно) вычисляется своп. Она возвращает количество начисленных свопов за время существования позиции. Аналогично комиссии в спецификации будет одна из двух возможных величин для свопа, с обязательным указанием метода расчета:

  • SwapPoints – своп за один перенос позиции через ночь в пунктах
  • SwapPercent – своп за один перенос позиции в процентах от размера контракта

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

  • PrBuy = Lot * TickValue * [ ( Bid2 – (Bid1+S1*Point) )/Point ] — прибыль для ордера на покупку
  • PrSell = Lot * TickValue * [ ( Bid1 – (Bid2+S2*Point) )/Point ] — прибыль для ордера на продажу
  • S1 — спред при открытии ордера на покупку
  • S2 — спред при закрытии ордера на продажу

Понятно, что Ask включает в себя и спред, и Bid. Давайте отделим прибыль или убыток ордера, который получился от спреда, как отдельное слагаемое:

  • PrBuy = Lot * TickValue * [ ( Bid2 – Bid1)/Point ] + ( - Lot * TickValue * S1 ) — прибыль для ордера на покупку
  • PrSell = Lot * TickValue * [ ( Bid1 – Bid2)/Point ] + ( - Lot * TickValue * S2 ) — прибыль для ордера на продажу

Видно, что в обеих формулах отделилось некое слагаемое, которое и является той частью, которую с нас берет брокер. Конечно, это не вся сумма, но по крайней мере теперь нагляднее можно увидеть, что мы получаем и что забирает себе брокер. Заметьте тот факт, что в первом случае, наш “налог” на спред зависит только от величины спреда при открытии “Buy” позиции, а во втором — при закрытии “Sell” позиции. Получается, что брокеру мы отдаем часть своей прибыли, представленной в виде спреда всегда ровно в момент покупки. Действительно, если углубиться глубже в процесс торговли на рынке Forex, станет ясно, что открытие позиции Buy позиции и закрытие позиции Sell есть действие эквивалентное, что подтверждается нашими формулами. В данном случае:

  • S1 — спред в пунктах при открытии любой позиции
  • S2 — спред в пунктах при закрытии любой позиции

Данные величины — это ровно те числа, которые вы можете увидеть в окне Market Watch, если захотите отобразить спред. Ровно те же значения возвращает соответствующая встроенная MQL5-функция SymbolInfoInteger с соответствующими входными параметрами, которые я приводить здесь не буду, потому что все это можно посмотреть в справке MQL5. В справке можно найти и примеры, и все что нужно. Моя задача в данном случае составить удобную математическую модель расчета, привязанную к языку MQL5, для того чтобы эти формулы сразу можно было кодировать в любой советник или любой другой полезный MQL5-код. Давайте выпишем наше слагаемое, которое теперь подобно и свопу, и комиссии:

  • SpreadBuy = - Lot * TickValue * S1
  • SpreadSell = - Lot * TickValue * S2

Спред при открытии и закрытии

По классическим соображениям спред считается в точке действия Buy, но я вам сейчас покажу, почему это неверно. Я провел множественные исследования рынка, и самой предсказуемой точкой движения цены оказалась точка “0:00”. Это точка перехода от одних суток к другим. Если вы внимательно понаблюдаете за этой точкой, вы увидите на всех валютных парах примерно одно и то же — скачок в сторону понижения курса. Этот скачок обусловлен повышением спреда в данной точке. После скачка следует равный ему откат. А что такое спред? Спред — это промежуток между Bid и Ask. Этот промежуток в классическом представлении есть следствие стакана цен. Если стакан цен насыщен лимитными ордерами, спред стремится к нулю, а если игроки покидают рынок, спред увеличивается. Можно это назвать распадом стакана. Даже по первым ощущениям можно сказать, что Bid — не главное. Получается, Ask и Bid являются равноправными в своей основе. Это легко понять, представив, что, например из “EURUSD” можно сконструировать зеркальный инструмент USDEUR, и тогда Bid станет Ask и наоборот Ask станет Bid. Проще говоря, мы просто перевернем стакан.

Обычно линия Ask не отображается на графике, а стоило бы:

bid & ask

Видно, что с повышением периода графика Ask и Bid начинают сливаться. Возможно, исходя из этих соображений ни один терминал не отображает обе линии, хотя лично я считаю, что это нужная опция. Тем не менее, знание о наличии этих величин и их разнице графика в целом не так важно, ведь все равно можно использовать эти вещи в советнике. Здесь я не нарисовал Mid, но я думаю все понимают, что эта линия ровно посередине между Bid и Ask. Понятно, что для высоких периодов разница этих величин практически не играет роли, и вроде как не надо даже учитывать наличие Ask, но на самом деле надо. Эти детали очень важны.

Учитывая эти соображения, теперь совершенно точно можно сказать, что середина стакана является инвариантом при подобных преобразованиях. Данная величина может быть вычислена следующим образом:

  • Mid = (Ask + Bid) / 2

Учитывая подобное представление, используя последнюю формулу можно понять, что:

  • Bid = Mid * 2 – Ask
  • Ask = Mid * 2 - Bid

Ну и далее:

  • Bid = Mid * 2 – (Bid + S*Point) = Mid – (S*Point)/2
  • Ask = Mid * 2 – (Ask - S*Point) = Mid + (S*Point)/2

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

  • PrBuy = Lot * TickValue * [ ( (Mid2 – (S2*Point)/2) – (Mid1 + (S1*Point)/2) ) )/Point ]
  • PrSell = Lot * TickValue * [ ( (Mid1 – (S1*Point)/2) – (Mid2 + (S2*Point)/2) ) )/Point ]

После соответствующих преобразований, увидим:

  • PrBuy = Lot * TickValue * [ (Mid2 – Mid1)/Point ] - Lot * TickValue * (  S1/2 + S2/2  )
  • PrSell = Lot * TickValue * [ (Mid1 – Mid2)/Point ] - Lot * TickValue * (  S1/2 + S2/2  )

Учитывая, конечно, что:

  • Bid1 = Mid1 – (S1*Point)/2
  • Bid2 = Mid2 – (S2*Point)/2
  • Ask1 = Mid1 + (S1*Point)/2
  • Ask2 = Mid2 + (S2*Point)/2

Ну и, конечно же, понимая, что:

  • Mid1 — середина стакана при открытии любой позиции
  • Mid2 — середина стакана при закрытии любой позиции

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

  • Spread = -Lot * TickValue * (  (S1*Point)/2 + (S2*Point)/2  )

Ну и соответственно слагаемое, символизирующее прибыль или убыток без учета спреда, свопа комиссии, например так:

  • ProfitIdealBuy = Lot * TickValue * [ (Mid2 – Mid1)/Point ]
  • ProfitIdealSell = Lot * TickValue * [ (Mid1 – Mid2)/Point ]

Вот теперь можно написать удобные формулы с учетом всех убытков от спреда, комиссии и свопов. Начнем с прототипа выражения. За основу возьмем последние формулы для прибыли или убытков ордера только с учетом спреда:

  • TotalProfitBuy = ProfitIdealBuy + (Spread + Comission + Swap)
  • TotalProfitSell= ProfitIdealSell + (Spread + Comission + Swap)

По-хорошему, я должен был написать эту формулу в самом начале, но я думаю, что здесь она более уместна. Тем не менее, практически везде, присутствует некая покрытая мраком величина TickValue. Основной вопрос заключается в том, как она вычисляется и как можно применять одну и ту же величину для расчета в разных временных точках. Под временными точками подразумеваются входы и выходы из позиций. Думаю, многим понятно, что данная величина носит динамический характер, и более того она разная для каждого торгового инструмента. Не разложив данную величину на составляющие, мы будем получать погрешности все большие, чем более дальние у нас "мишени". Давайте перефразируем это более понятным языком. Иначе говоря, полученные формулы являются лишь приближением. Существует абсолютно точная формула, которая лишена этих недостатков. Ее пределом и являются полученные выше соотношения. Сами пределы можно написать так:

  • Lim[ dP -> 0 ] ( PrBuy(Mid1, Mid1+dP… ) ) = TotalProfitBuy(Mid1, Mid1+dP…)
  • Lim[ dP -> 0 ] ( PrSell(Mid1, Mid1+dP… ) ) = TotalProfitSEll(Mid1, Mid1+dP…)
  • Mid1+dP = Mid2 — новая цена получается из предыдущей плюс дельта, которую устремляем к нулю
  • TotalProfitBuy = TotalProfitBuy(P1,P2… ) — как было определено, прибыль или убыток есть функция Mid величин и многих других
  • TotalProfitSell = TotalProfitSell(P1,P2… ) — аналогично

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

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

Точнейший метод расчета прибыли и убытков ордеров

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

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

  • Тот, кто писал этот материал, сам до конца его не понимает, потому необходимо всеми способами убедить вас в обратном (делается это как правило с использованием анти-логичных утверждений)
  • Детали намеренно опускаются для того, чтобы скрыть лишнюю информацию от вас

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

buy & sell

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

Конечно, я уверен, что в специальной литературе есть правильные расчеты, но очевидно, что найти эту информацию сложнее, чем самому додумать то, чего не хватает. Условность эта такова, что якобы, когда мы покупаем, к примеру EURUSD, то мы покупаем EUR и продаем USD. Давайте выпишем это:

  • EUR = Lot * ContractSize
  • USD = - Ask1 * Lot * ContractSize = - (Bid1 + S1*Point) * Lot * ContractSize

В данном случае выходит, что при покупке мы получаем положительное количество базовой валюты и отрицательное количество второй валюты. Думаю, не только мне кажется, что это полная глупость. Я посидел, подумал и пришел к выводу, что это правильные соотношения, но преподносятся они не совсем доступно для нашего восприятия. Лучше так… Для покупки EUR нам необходима другая валюта USD, которую мы должны взять либо у себя с баланса, либо в кредит у брокера, либо смешанным образом. Иначе говоря, мы берем сначала USD из некоего общего хранилища. Эту сумму мы как бы берем взаймы. Получается:

  • USD1 = Ask1 * Lot * ContractSize = (Bid1 + S1*Point) * Lot * ContractSize — это мы заняли
  • EUR1 = Lot * ContractSize — это то, что мы купили за заемные средства по курсу обмена Ask на момент покупки

Минус появится позже, на самом деле его тут нет и быть не может. Минус появится, когда мы будем закрывать позицию. Соответственно, если позиция открыта, то ее нужно закрыть. Получается нужно сделать действие Sell тем же лотом. Если придерживаться классических соображений:

  • EUR2 =  Lot * ContractSize
  • USD2 = Bid2 * Lot * ContractSize

Получается, что мы уже продаем EUR и покупаем USD. Применительно к нашим преобразованиям, получается, что мы берем те EUR, на которые обменяли заемные средства, у себя же и меняем обратно на заемную валюту. Прибыль или убыток получим, вычтя из полученных средств заемные:

  • Profit_EUR = EUR1 – EUR2 = 0
  • Profit_USD = USD2 – USD1 = Bid2 * Lot * ContractSize - (Bid1 + S1*Point) * Lot * ContractSize = Lot * ContractSize * ( Bid2 – Bid1 – S1*Point)

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

  • Profit_USD = Lot * (ContractSize*Point) * [ ( Bid2 – Bid1 – S1*Point) / Point ]

Здесь мы просто делим и потом умножаем правую часть на Point и получаем нашу исходную формулу. Эту же формулу можно получить, если использовать как раз первоначальную систему условностей, что мы продаем и покупаем одновременно при любом действии. В данном случае все заемное получается со знаком минус, символизируя, что мы должны, а покупаемое мы оставляем со знаком плюс. В такой системе условностей нам не нужно учитывать, что на что меняем и откуда. Давайте сделаем то же самое, только используя данный подход:

  • EUR1 = Lot * ContractSize
  • USD1 = - Ask1 * Lot * ContractSize = - (Bid1 + S1*Point) * Lot * ContractSize

Это покупка. Действие первое.

  • EUR2 = - Lot * ContractSize
  • USD2 = Bid1 * Lot * ContractSize

Это продажа, действие второе.

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

  • Profit_EUR = EUR1 + EUR2 = 0
  • Profit_USD = USD1 + USD2 = - (Bid1 + S1*Point) * Lot * ContractSize + Bid2 * Lot * ContractSize = Lot * ContractSize * ( Bid2 – Bid1 – S1*Point)

Получается, что прибыль любого инструмента считается исключительно во второй валюте (не базовой), а базовая валюта всегда исчезает при полном цикле открытие-закрытие. Естественно, что для продаж все зеркально. Давайте напишем все это для полноты картины. Теперь мы продаем EURUSD, а потом закрываем эту позицию, делая “Buy”:

  • EUR1 =  - Lot * ContractSize
  • USD1 = Bid1 * Lot * ContractSize

Это продажа. Действие первое.

  • EUR2 = Lot * ContractSize
  • USD2 = - (Bid2 + S2*Point) * Lot * ContractSize

Это покупка, действие второе.

Теперь давайте точно также сложим все величины:

  • Profit_EUR = EUR1 + EUR2 = 0
  • Profit_USD = USD1 + USD2 = Bid1 * Lot * ContractSize - (Bid2 + S2*Point) * Lot * ContractSize = Lot * ContractSize * ( Bid1 – Bid2 – S2*Point)

Как видно, формула отличается лишь тем, что поменялись местами Bid1 и Bid2. И конечно же, спред берется в точке закрытия позиции, потому что точка закрытия — это точка покупки. Пока все в строгом соответствии с первоначальными формулами. Отдельно стоит отметить, что теперь мы знаем, что такое TickValue, по крайней мере в случае если, вторая валюта (не базовая) нашего инструмента совпадает с валютой нашего депозита. Давайте напишем, чему равна эта величина:

  • TickValue = ContractSize * Point

Однако эта величина годится опять же только для инструментов, где валюта прибыли — валюта нашего депозита. А как быть если мы, например, используем кросс-курс, такой как, например, AUDNZD? Тут главное не сам инструмент, а то, что данная величина всегда вычисляется применительно к валюте нашего депозита, и получаем мы ее с торгового сервера. Но если пользоваться этой формулой применительно к кросс-курсу, то получится, что данная формула, конечно же, работает, но будет выдавать нам ответ не в валюте нашего депозита, а во второй валюте инструмента. Чтобы перевести это в валюту депозита, необходимо домножить данную величину на некий коэффициент, который, по сути, и является переводным курсом, который мы рассматривали в предыдущем блоке.

  • TickValueCross = ContractSize * Point * BidAlphaCross

Переводной курс считается просто:

  1. Смотрим, какая у нас в инструменте вторая валюта (не базовая)
  2. Ищем инструмент, который содержит эту валюту и валюту нашего депозита
  3. Совершаем обмен по соответствующему курсу
  4. Если нужно, преобразуем инструмент (зеркальный курс)

Например, если мы торгуем по EURCHF, а депозит у нас в USD, то первоначальная прибыль будет у нас в CHF, поэтому мы можем взять инструмент USDCHF и его курс. Получается, нужно обменять CHF на USD, тогда выходит, что нам нужно купить USD за CHF. Но так как CHF = PBid * USD, то USD = (1/PAsk) * CHF и соответственно:

  • BidAlphaCross = 1/PAsk

Второй пример: давайте возьмем немного другой. Например, мы торгуем по AUDNZD, и прибыль мы получаем в NZD, тогда можно взять курс NZDUSD и так как USD = PBid * NZD, то в данном случае:

  • BidAlphaCross = PBid

Давайте разбираться. Перевод CHF в USD означает “+USD ; -CHF”, иначе говоря, теряем одну валюту, а приобретаем другую. Это означает покупку USD, продажу по курсу USDCHF, по цене PAsk, что означает на самом деле как раз следующее: “USD = (1/PAsk) * CHF”. Проще это воспринимать так, что при покупке мы должны получить чуть меньше USD, чем могло быть в случае, если брокер не брал абсолютно ничего с нашей операции обмена. А это значит, что если мы делим на большее PAsk, мы получаем меньшее значение, чем 1/P.

Обратная ситуация со вторым случаем. Перевод NZD в USD означает “+USD ; -NZD”, а это означает продажу по курсу NZDUSD по цене PBid. Ну и пишем аналогичное соотношение для обмена “USD = PBid * NZD”. Точно также обмен производится по чуть более худшему курсу, а это и есть “PBid”. Все совпадает, все прозрачно и понятно. Не забывайте, что первичным, идеальным курсом является “PMid”, который мы рассмотрели выше. Учитывая это, несложно понять, что спред — не что иное, как процент, который с нас берет брокер в виде обмениваемой валюты. Поэтому каждый трейд, не важно, открытие это или закрытие позиции, сопровождается налогом брокера на обмен валюты, называемой спредом. Остальная часть этого налога содержится в комиссии и свопе.

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

С учетом наличия новой величины BidAlphaCross перепишем формулы прибыли и убытков ордеров без учета комиссии и свопа:

  • BuyProfit = BidAlphaCross * Lot * ContractSize * ( Bid2 – Bid1 – S1*Point)
  • SellProfit = BidAlphaCross * Lot * ContractSize * ( Bid1 – Bid2 – S2*Point)

Учтя, что:

  • Bid1 = Mid1 – (S1*Point)/2
  • Bid2 = Mid2 – (S2*Point)/2

Давайте перепишем формулы в более наглядном виде, подставив туда соотношения для Mid:

  • BuyProfit = BidAlphaCross * Lot * ContractSize * ( Mid2 – (S2*Point)/2 – Mid1 + (S1*Point)/2 – S1*Point)
  • SellProfit = BidAlphaCross * Lot * ContractSize * ( Mid1 – (S1*Point)/2 – Mid2 + (S2*Point)/2 – S2*Point)

Упростим все это:

  • BuyProfit = Lot * BidAlphaCross * ContractSize * Point * [ ( Mid2 – Mid1 )/ Point  - ( S1/2 + S2/2 ) ]
  • SellProfit = Lot * BidAlphaCross * ContractSize * Point * [ ( Mid1 – Mid2 )/ Point  - ( S1/2 + S2/2 ) ]

Упростим еще:

  • BuyProfit = Lot * TickValueCross * [ ( Mid2 – Mid1 )/ Point ] - Lot * TickValueCross * ( S1/2 + S2/2 )
  • SellProfit = Lot * TickValueCross * [ ( Mid1 – Mid2 )/ Point ] - Lot * TickValueCross * ( S1/2 + S2/2 )

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

Функция точного вычисления свопа

Нам осталось уточнить формулы для свопов. Вспомним формулы, которые мы получили в начале статьи:

  • Swap = Lot * TickValue * SwapPoints * SwapCount(StartTime,EndTime)
  • Swap = Lot * ContractSize * BidAlpha * SwapPercent/100 * SwapCount(StartTime,EndTime)

В прошлом блоке было получено, что TickValue — не однозначная величина и считается по-разному для разных валютных пар. Было определено:

  • TickValue = ContractSize * Point

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

  • TickValueCross = ContractSize * Point * BidAlphaCross

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

  • Swap = Lot * TickValueCross * SwapPoints * SwapCount(StartTime,EndTime)

Но данная формула еще далека от совершенства. Все потому, что в отличие от комиссии или спреда своп может зачисляться сколь угодно большое количество раз, пока ваша позиция открыта. Выходит, что для описания всего суммарного свопа в случае кросс-курсов недостаточно одной величины TickValueCross, ведь получается, что в каждой точке начисления свопа данная величина немного, но отличается, так как меняется величина BidAlphaCross. Давайте напишем полные формулы для вычисления свопов для двух вариантов "налогообложения":

  1. Swap = SUMM(1 … D) { Lot * (SwapPoints * K[i]) * TickValueCross[i] } — сумма всех начисленных свопов в пунктах, за каждую пересеченную точку 0:00
  2. Swap = SUMM(1 … D) { Lot * ContractSize * BidAlpha[i] * (SwapPercent/100 * K[i]) * } — в процентах

Массивы для суммирования:

  • K[i] = 1 или 3 — если коэффициент равен “3”, то это значит, что это был день для начисления тройного свопа
  • TickValueCross[i] — массив размеров тика в точках зачисления свопа
  • BidAlpha[i] — массив корректировочных курсов в точках зачисления свопа

Давайте разберем пример вычисления свопа для произвольного ордера. Для этого я введу короткие обозначения:

  • TickValueCross[i] = T[i]
  • BidAlpha[i] = B[i]
  • K[i] = K[i]

Теперь графически изобразим, как мы будем суммировать свопы:

swap calculation


Мы разобрали все возможные примеры вычисления прибыли и убытков ордеров.

Практическая часть

В данном разделе мы будем проверять нашу математическую модель. В частности, особое внимание я бы уделил вопросам вычисления прибыли или убытка без учета комиссий и свопов. Если помните, выше у меня возник закономерный вопрос, в какой временной точке считать величину TickValueCross в случае, если мы считаем прибыль по кросс-курсу? Этот момент является единственной неопределенностью во всей модели, которую я и собираюсь проверить. Для этого мы должны сначала реализовать весь необходимый функционал для вычисления прибыли или убытка любого ордера с помощью нашей математической модели, провести тестирование в тестере стратегий и после всего этого сравнить наши вычисления с реальными данными ордеров из торговой истории. Конечной целью является проверка нашей математической модели и параллельно сравнение с эталонной функцией MQL5, такой как OrderCalcProfit.

Для того чтобы оценить все это, необходимо ввести четыре величины:

  1. Real — прибыль ордера из истории
  2. BasicCalculated — та же прибыль, только вычисленная в момент открытия ордера с помощью функции OrderCalcProfit
  3. CalculatedStart — прибыль, вычисленная в момент открытия ордера с использованием нашей математической модели
  4. CalculatedEnd — прибыль, вычисленная в момент закрытия ордера с использованием нашей математической модели

В связи с этим вытекают три типа среднего отклонения значения прибыли:

  1. AverageDeviationCalculatedMQL = Summ(0..n-1) [ 100 * MathAbs(BasicCalculated - Real)/MathAbs(Real) ]  / n : относительное отклонение прибыли по коду MQL5
  2. AverageDeviationCalculatedStart = Summ(0.. n-1 ) [  100 * MathAbs(CalculatedStartReal)/MathAbs(Real) ] / n : относительное отклонение прибыли по нашему коду при открытии ордера
  3. AverageDeviationCalculatedEnd =  Summ(0.. n-1 ) [  100 * MathAbs(CalculatedEnd Real)/MathAbs(Real) ] / n : относительное отклонение прибыли по нашему коду при закрытии ордера

Ну и аналогично с этим можно ввести три типа максимального отклонения:

  1. MaxDeviationCalculatedMQL = Max(0.. n-1 ) [ (100 * MathAbs(BasicCalculated - Real)/MathAbs(Real))  ] - относительное отклонение прибыли по коду MQL5
  2. MaxDeviationCalculatedStart =  Max(0.. n-1 ) [  (100 * MathAbs(CalculatedStart Real)/MathAbs(Real)) ]  - относительное отклонение прибыли по нашему коду при открытии ордера
  3. MaxDeviationCalculatedEnd =  Max(0.. n-1 ) [  (100 * MathAbs(CalculatedEnd Real)/MathAbs(Real)) ]  - относительное отклонение прибыли по нашему коду при закрытии ордера

Где:

  • Summ(0..n-1) — сумма всех относительных отклонений всех "n" ордеров
  • Max(0..n-1) — максимальное относительное отклонение из всех "n" ордеров

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

double CalculateProfitTheoretical(string symbol, double lot,double OpenPrice,double ClosePrice,bool bDirection)
   {
   //PrBuy = Lot * TickValueCross * [ ( Bid2 - Ask1 )/Point ]
   //PrSell = Lot * TickValueCross * [ ( Bid1 - Ask2 )/Point ]
   if ( bDirection )
      {
      return lot * TickValueCross(symbol) * ( (ClosePrice-OpenPrice)/SymbolInfoDouble(symbol,SYMBOL_POINT) );
      }
   else
      {
      return lot * TickValueCross(symbol) * ( (OpenPrice-ClosePrice)/SymbolInfoDouble(symbol,SYMBOL_POINT) );
      }   
   }

Здесь две формулы в одной: сразу для покупок и для продаж. За это отвечает маркер "bDirection". Зеленым как раз выделена дополнительная функция, которая считает этот наш размер тика. Ее я реализовал вот так:

double TickValueCross(string symbol,int prefixcount=0)
   {
   if ( SymbolValue(symbol) == SymbolBasic() )
      {
      return TickValue(symbol);
      }
   else
      {
      MqlTick last_tick;
      int total=SymbolsTotal(false);//symbols in Market Watch
      for(int i=0;i<total;i++) Symbols[i]=SymbolName(i,false);
      string crossinstrument=FindCrossInstrument(symbol);
      if ( crossinstrument != "" )
         {
         SymbolInfoTick(crossinstrument,last_tick);
         string firstVAL=StringSubstr(crossinstrument,prefixcount,3);
         string secondVAL=StringSubstr(crossinstrument,prefixcount+3,3);
         if ( secondVAL==SymbolBasic() && firstVAL == SymbolValue(symbol) )
            {
             return TickValue(symbol) * last_tick.bid;
            }
         if ( firstVAL==SymbolBasic() && secondVAL == SymbolValue(symbol) )
            {
            return TickValue(symbol) * 1.0/last_tick.ask;
            }         
         }
      else return TickValue(symbol);  
      }
   return 0.0;   
   }

Внутри также две реализации для случаев:

  1. Валюта прибыли инструмента, совпадает с валютой нашего депозита
  2. Все остальные случаи (ищем переводной курс)

Внутри второго сценария также ветвление на два случая:

  • Валюта депозита находится в верхней части переводного курса
  • Валюта депозита находится в нижней части переводного курса

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

string FindCrossInstrument(string symbol,int prefixcount=0)
   {
   string firstVAL;
   string secondVAL;
   for(int i=0;i<ArraySize(Symbols);i++)
      {
      firstVAL=StringSubstr(Symbols[i],prefixcount,3);
      secondVAL=StringSubstr(Symbols[i],prefixcount+3,3);
      if ( secondVAL==SymbolBasic() && firstVAL == SymbolValue(symbol) )
         {
         return Symbols[i];
         }
      if ( firstVAL==SymbolBasic() && secondVAL == SymbolValue(symbol) )
         {
         return Symbols[i];
         }      
      }
   return "";
   }

Для этого необходимо знать, как "вынуть" базовую валюту из имени инструмента:

string SymbolValue(string symbol,int prefixcount=0)
   {
   return StringSubstr(symbol,prefixcount+3,3);
   }

А также получить валюту прибыли с помощью встроенной функции MQL5:

string SymbolBasic()
   {
   return AccountInfoString(ACCOUNT_CURRENCY);
   }

Ну и сравниваем все это с валютами во всех символах из Market Watch до первого совпадения. Теперь можно использовать этот функционал в момент открытия и закрытия ордеров. Остальную часть кода при желании вы сможете посмотреть в исходнике, который я приложу к статье. Я добавил вычисление отклонений после завершения бектеста, они пишутся в лог терминала. Я протестировал все двадцать восемь основных валютных пар и кросс-курсов и занес результат в таблицу, чтобы мы могли оценить работоспособность нашей математической модели и сравнить с реализацией MQL5. Результаты разделились на три условных блока. Первые два выглядят так:

1 & 2 blocks

Как видно, для первых четырех валютных пар как MQL5 реализация, так и наша отрабатывают идеально без погрешностей, все потому, что валюта прибыли совпадает с валютой депозита, в целом я не удивлен. Далее идет блок из трех валютных пар, у которых базовая валюта совпадает с валютой прибыли. В данном случае лучше всего отрабатывает реализация MQL5, но тем не менее уже видно, что погрешность вычисления при открытии ордера на порядок выше, чем та же погрешность при закрытии, что уже косвенно говорит о том, что действительно вычисление должно производиться в момент закрытия ордера. Посмотрим на остальные валютные пары:

block 3

Здесь, как видно, мой функционал не уступает базовому функционалу MQL5 и еще дополнительно абсолютно везде видно, что вычисления при закрытии позиции гораздо более точные, абсолютно всегда. Единственное, я не понял причину нулей в первой строке второго блока. Причин может быть много, но почему-то мне кажется, что они не в моей модели, хотя я могу и ошибаться. Что касается проверок формул для комиссий и свопов, я не думаю, что это необходимо — я уверен в этих формулах, так как там нет ничего особо хитрого.

Заключение

Итог таков, что составлена математическая модель с абсолютного нуля, руководствуясь лишь обрывками информации. Модель содержит все необходимое для расчета ордеров основных валютных пар и кросс-курсов. Модель проверена в тестере стратегий и готова к немедленному использованию в любом советнике, индикаторе или полезном скрипте. На самом деле, применимость данной модели гораздо шире, чем просто вычисление прибыли, убытков или издержек, но это уже тема для другой статьи. Весь необходимый функционал и примеры его использования вы можете найти в исследовательском советнике, который я использовал для составления таблицы. Советник будет приложен к статье. Можете сами погонять его и сравнить результаты с таблицей. А самое главное, я считаю, что составлена простая, логичная и аргументированная "методичка" в советском стиле, открыв которую, в любой момент можно вспомнить что, куда, откуда и зачем.



Прикрепленные файлы |
Последние комментарии | Перейти к обсуждению на форуме трейдеров (9)
Aleksandr Slavskii
Aleksandr Slavskii | 23 авг. 2022 в 17:30

Автору спасибо.

С кросами постоянно голову ломал, а тут всё разжёванно, ещё и воплощено в код. Бери, пользуйся, не ломай голову)))

fxsaber
fxsaber | 23 авг. 2022 в 17:52

Вроде, в статье Point перепутан с TickSize.

По определению, Point = 10^(-Digits).

Evgeniy Ilin
Evgeniy Ilin | 23 авг. 2022 в 21:03
fxsaber #:

Вроде, в статье Point перепутан с TickSize.

По определению, Point = 10^(-Digits).

Ну нет, Point это _Point у меня все как положено/ TickSize это-по моему немного другое, это размер минимального изменения цены. На некоторых инструментах может быть TickSize = 5*_Point, например. Это просто число от которого отталкиваются все остальные величины из спецификации. Тяжело все нюансы предусмотреть тем более в такой статье, но если это важно нужно поправить конечно. Я старался суть передать в первую очередь. насчет "_Point", он может быть любым, это не суть. Например взять USDJPY, твоя формула будет уже не верной потому как там курс за сотню уходит, тебе придется домножить еще на 100. смотря чему еще Digits равен, тут я не уверен. Если брать пары типо EURUSD USDGBP, то да. Point это свободная величина. Ну конечно не в том плане что надо ее делать равной непонятно чему, но примерно ее уравнивают к среднему движению цены на инструменте скажем за секунду или пять секунд...

Andrey F. Zelinsky
Andrey F. Zelinsky | 23 авг. 2022 в 21:56
Evgeniy Ilin #:

... но если это важно нужно поправить конечно ...

  • PrBuy = Lot * TickValue * [ ( PE - PS )/Point ] — прибыль для ордера на покупку

-- здесь правильно TickSize -- ну, и дальше по тексту

Evgeniy Ilin
Evgeniy Ilin | 26 авг. 2022 в 16:39
Andrey F. Zelinsky #:
  • PrBuy = Lot * TickValue * [ ( PE - PS )/Point ] — прибыль для ордера на покупку

-- здесь правильно TickSize -- ну, и дальше по тексту

У меня все правильно, иначе бы расчеты не сошлись. Я скрипт сделал коротенький. Он не лично вам а всем сделан. Величины эти все- таки разные и следует понимать их разницу. Я подумаю как поправить. Но в данном случае все верно. Зачеркивать не нужно. 

Нейросети — это просто (Часть 26): Обучение с подкреплением Нейросети — это просто (Часть 26): Обучение с подкреплением
Продолжаем изучение методов машинного обучения. Данной статьей мы начинаем еще одну большую тему "Обучение с подкреплением". Данный подход позволяет моделям выстаивать определенные стратегии для решения поставленных задач. И мы рассчитываем, что это свойство обучения с подкреплением откроет перед нами новые горизонты построения торговых стратегий.
Разработка торговой системы на основе индикатора Williams PR Разработка торговой системы на основе индикатора Williams PR
Новая статья из серии, в которой мы учимся создавать торговые системы по показателям самых популярных технических индикаторов. Пишем системы на языке MQL5 для использования в MetaTrader 5. В этой статье мы будем изучать индикатор Процентного диапазона Уильямса (Williams' %R).
Технический индикатор своими руками Технический индикатор своими руками
В этой статье мы рассмотрим алгоритмы, следуя которым можно создать свой собственный технический индикатор. Мы увидим, как с помощью очень простых начальных предположений можно получить довольно сложные и интересные результаты.
Нейросети — это просто (Часть 25): Практикум Transfer Learning Нейросети — это просто (Часть 25): Практикум Transfer Learning
В последних двух статьях мы создали инструмент, позволяющий создавать и редактировать модели нейронных сетей. И теперь пришло время оценить потенциальные возможности использования технологии Transfer Learning на практических примерах.