Pull to refresh

Comments 70

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


У меня ошибка стремится к нулю, а ценность валюты инициализируется от 0 до 1 с равномерным распределением (в каждой итерации симуляции). А конечная стоимость валют тоже получается в этом же диапазоне, ну, может, с другой плотностью конечного решения, не равномерным…
Кратко: без матана никак, реальность не описывается школьной математикой. Хороший пример для тех, кто кричит «нафиг нам это ваше университетское образование».
Чуть более подробно в следующем комменте.
Кратко: без матана никак


Еще бы вы сразу сказали, что именно никак, я не понял какой вопрос адресуется, спасибо.
Теперь детали.
1. У вас тут метод наименьших квадратов, который предполагает нормальное (гауссово) распределение шумов (ошибок в начальных данных). Всем хорошо нормальное распределение, формулы короткие, считать и доказывать теоремки просто, за что его любят преподы математики и авторы учебников. Только вот с реальностью оно дружит в большинстве случаев примерно никак. Гауссово распределение в некотором смысле максимально компактное, у него самые тощие хвосты из всех возможных. Из-за этого любая ошибка во входных или промежуточных данных, которая не укладывается в рамки гауссова распределения (а большинство — не укладываются), вызывает «панику-панику» и сносит ваш алгоритм в неведомые дали. Что вы, собственно, и наблюдаете. Решение: разбираться с распределениями (строить гистограммы шумов, аппроксимировать их разными стат. моделями и считать критерий Колмогорова-Смирнова). А затем подбирать вид штрафной функции исходя из наиболее подходящего распределения.
2. Задача в вашей постановке существенно некорректно поставлена, в том смысле, что любое малое возмущение во входных данных может привести к сколько угодно большой ошибке на выходе. Наивными алгоритмами такое не решается от слова совсем, нужно как минимум гладить (т.е. на каждом шаге домножать вектор текущего решения на некую матрицу B слева и на TB справа). Определитель матрицы В должен быть равен строго 1, по диагонали должны стоять числа чуть меньше 1, а в остальных ячейках должны стоять некие малые числа. Какие конкретно это должны быть числа — нужно смотреть по результатам решения вопроса номер 1.
У вас тут метод наименьших квадратов


Простите, но нет. Я же прямо написал, что линейную алгебру не использую.

У меня решение через аналитический градиент.

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


Предположение о нормальности ошибок не используется совсем здесь — не нужно.

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


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


Простите, но да.
Вот это ваш код?

express <- expression(
(eurusd - eur / usd) ^ 2 +
(gbpusd - gbp / usd) ^ 2 +
(eurchf - eur / chf) ^ 2 +
(eurgbp - eur / gbp) ^ 2 +
(gbpchf - gbp / chf) ^ 2 +
(usdchf - usd / chf) ^ 2
)


Это МНК в незамутнённом первозданном виде.

Мой код.


Тот факт, что я минимизирую сумму квадратов отклонений не делает метод МНК, я ее использую теорию матриц вообще, я делаю то, что делает нейронная сеть...


Попрошу вас немного прояснить ваш аргумент

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

1. Добавьте коэффициенты перед квадратами, т.е. в сумме будет не просто (eurusd — eur/usd)², а типа 1.05*(eurusd...)² + 0.89*(...)² Сами числа возьмите методом научного тыка из неких внешних данных, типа средних объёмов торгов или денежной массы М2. Арифметическое среднее ваших коэффициентов должно быть равно 1. Это сменит «вшитое» в МНК распределение с гауссова на хи-квадрат, которое всё же намного более гибкое в плане шумов.

2. Когда вы берёте начальные значения для тестов из ГСПЧ… Не знаю как сделан ГСПЧ в R, но думаю как везде — с равномерным распределением. Берите вместо просто random() арифметическое среднее от 6 идущих подряд вызовов random(). Эффект должен быть совершенно волшебным.

3. Не знаю, как у вас там дальше всё сделано, но данные всё же нужно гладить на каждом шаге. Это совершенно элементарно делается, только можно загладить насмерть: система будет устойчиво сходиться к единичному вектору, т.е. к состоянию «все валюты равны».
Меня минусуют не оправдано. Это бред. МНК это устоявшийся термин для решения через b = (A * At)-1 * At * Y.
Это называется ordinary least squares, МНК. Если я решаю регрессионную задачу и использую квадраты невязок, через градиентный спуск, бред говорить об МНК, другие расчеты, другая сложность, другие результаты могут быть, в конце концов… Я мог выбрать фитнеса функцию как сумму модулей невязок, например… Понятно, я надеюсь…

1) производные не разностные, в тексте написано ясно (вы читали его?):

2 * (eur/usd^2 * (eurusd — eur/usd)) + 2 * (gbp/usd^2 * (gbpusd —
gbp/usd)) — 2 * (1/chf * (usdchf — usd/chf))


Это производная от дифференцмруемой ф-и. Дальше она умножается на скорость обучения, и все, классика жанра… Я ещё сделал gradient clipping [0;1].

Если вы принципиально не готовы курить матан, сделайте хотя бы следующее


Я решаю задачу БЕЗ линейной алгебры, то есть, без матана. Я исследую результат.

Это сменит «вшитое» в МНК распределение с гауссова на хи-квадрат, которое всё же намного более гибкое в плане шумов.


Это generalized linear model или что? Я вот не понял сейчас почему веса тут нужны, вот от слова совсем не понял…

Я вообще, мне не ясно как свести задачу к МНК, не ясен дизайн матрицы, я бы и по матану попробовал решить, и через GLM…
Гауссово распределение в некотором смысле максимально компактное, у него самые тощие хвосты из всех возможных. Из-за этого любая ошибка во входных или промежуточных данных, которая не укладывается в рамки гауссова распределения (а большинство — не укладываются), вызывает «панику-панику» и сносит ваш алгоритм в неведомые дали. Что вы, собственно, и наблюдаете. Решение: разбираться с распределениями (строить гистограммы шумов, аппроксимировать их разными стат. моделями и считать критерий Колмогорова-Смирнова). А затем подбирать вид штрафной функции исходя из наиболее подходящего распределения.</blockquot

У мня невязки уходят в ноль, задача решается на 100%, я не то делаю, что думаете вы. Тут нет выбросов, смещающих решение. Просто бесконечно много решений. В МНК это может быть возможно, если число независимых переменных намного больше числа наблюдений, не понимаю как у меня это получается.
  1. Вы совершенно бескомпромиссно пишете чушь. Из того, что автор в качестве метрики выбрал квадрат евклидова расстояния, совершенно не следует никакого предположения о нормальности чего-либо. По факту, у него вообще статистики и распределений нет. Статистика и распределения появились бы, если бы автор начал оценивать погрешность своих результатов. Но этого нет, есть просто поиск оптимума некоторой непрерывной и дифференцируемой функции.
  2. Неустойчивость решения надо доказывать. Никаких предпосылок для "малое возмущение во входных данных может привести к сколько угодно большой ошибке на выходе" тут нет.

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

1. Есть измерение каких-либо величин (автор их получил не из неких уравнений, а просто взял срез на какой-то момент времени), есть МНК — этого достаточно, автор невольно и не осознавая этого предположил нормальное распределение шумов. Точка.
2. Нет, это устойчивость решения для абсолютно любого метода надо доказывать. Более того, доказано, что в данном случае решение заведомо неустойчиво. У многомерной поверхности второго порядка примерно всегда более одного экстремума, а соответственно есть седловые точки между ними. Любое сколь угодно малое отклонение сваливает решение или в один экстремум, или в другой, или вообще непонятно куда.
То, что в задаче ещё и недостаточно информации (нужно накладывать ограничения на сумму или произведение координат), делает ситуацию только хуже.
  1. У автора в постановке задачи нет шумов. Поэтому никакие предположения об их распределении тут неуместны. Если же говорить в отрыве от статьи… Когда вы предполагаете нормальное распределение величины и ищите параметры этого распределения, то центр распределения можно найти с помощью МНК. В обратную сторону это не работает никак. Если вы что-то ищете с помощью метода наименьших квадратов, то никаких гарантий каких-либо распределений это не дает. Но, еще раз повторю, у автора нет распределений, поэтому все предположения на этот счет бессмысленны и нерелевантны.
  2. Целевая функция в этой задаче квадратично зависит от начальных данных. В окрестностях решения она дифференцируема и раскладывается в ряд Тейлора, следовательно малым изменениям начальных данных соответствуют малые изменения решения. Или у вас какое-то свое новое определение устойчивости? Напишите его здесь в формальной форме, типа "Решение называется устойчивым, если для любого эпсилон больше нуля и т. д. и т. п.", чтобы можно было предметно обсудить...

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

Чтобы математически ограничить рост решения к минимизируемому функционалу добавляют норму вектора (или ее квадрат, так проще для подсчета производных) решения с каким-нибудь сравнимым с желаемой погрешностью весом (чем больше вес, тем строже будет соблюдаться условие минимизации вектора и тем больше отклонения будут влиять на итоговую погрешность). В вашем случае в expression добавится что-то такое:
… + (usd^2 + eur^2 + chf^2 + grb^2)*0.1
С порядком множителя (0.1) можно смело поиграться
Да, она самая. Для точности в пять знаков он будет где-то таким — 0.00001 / (4^2), я так понял это если хочется каждую из четырех валют в диапазоне (0..1) получить. То есть направляем «часть сходимости» системы на удержание размера вектора вместо погрешности. Ну и кстати — из вычисляемого выражения наверное все же лучше убрать деление (метод градиентного спуска очень любит сваливаться с гипербол в непонятно куда) — домножить все эти скобки на произведение квадратов валют, вроде как раз за счет отдельного слагаемого с регуляризацией получится эквивалентная постановка.
Вот так, с регуляризацией и избавлением от деления? Спасибо.

alpha <- 1e-4

express <- expression(
          (
               (eurusd - eur / usd) ^ 2 +
               (gbpusd - gbp / usd) ^ 2 +
               (eurchf - eur / chf) ^ 2 +
               (eurgbp - eur / gbp) ^ 2 +
               (gbpchf - gbp / chf) ^ 2 +
               (usdchf - usd / chf) ^ 2 +
               (usd^2 + eur^2 + chf^2 + gbp^2) * alpha
          ) * chf^2 * gbp^2 * usd^2 * eur^2
     )


Результат:



Финальные решения похоже стали более равномерно распологаться в [0;1]. Ошибка опускается до 0,05 или выше.
Математика математикой, а экономический смысл подобных изысканий как мне кажется нужно представлять более четко.
Я бы указал на три фактора из области экономики:
1. Объем обмена по каждой из валюты. То есть все эти же характеристики могли бы учитываться с некоторыми весами.
2. Конвертируемость. Есть валюты свободно конвертируемые или твердые. И для таких валют категория курс имеет смысл. И есть валюты не конвертируемые (с разной степенью не конвертируемости) — для них курс носит почти случайный, ситуационный характер.
3. Курс валюты (как одно число), который устанавливается государством по своим правилам является величиной которая используется для различных расчетов но с точки зрения экономики не является ни курсом покупки, ни курсом продажи (это два числа). Этот курс конечно соотносится как правило с курсом покупки/продажи и даже пытается оказывать на него регулирующее влияние. Но иногда расхождения могут быть существенные (напрмер во время кризисов)

Пытаясь выделить своеобразный «первичный» курс в котором можно выразить все другие валюты — система делает все валюты конвертируемыми а это не так. И также уходит разница между курсом покупки/продажи. А это как раз один из важных факторов которые нужно учитывать.

Можно только работать с конвертируемыми валютами. В эксперименте все такие.


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

По результатам rbc.ru смог найти только следующие пары валют,
по которым есть хоть какой-то объём торгов (TOD тикер)
BYN_RUB
CHF_RUB
EUR_RUB
GBP_RUB
HKD_RUB
JPY_RUB
KZT_RUB
TRY_RUB
GBP_USD
KZT_USD
UAH_USD
USD_KZT
Есть мнение что по остальным парам вообще ничего не будет.
Вот и весь экономический смысл.

Foreign Exchange включает намного больше, а на нашей бирже торгуется малая часть кроссов. В основном с рублем.

Кажется, задачу можно свести к игре «камень-ножницы-бумага», решение которой сводится, по сути, к случайному блужданию от нуля.
В данном случае — интуитивно, глядя и на ваши же результаты в том числе. Нет никакой абсолютной стоимости у дегег, в общем случае это функция от всей совокупности информации на рынке. И эта функция ну вообще не линейная, с кучей внешних параметров, следующих из рыночной ситуации. Статистическими методами вы там находите, кажется, только артефакты дискретизации данных, а не какие-то несвободные переменные в самих данных. Грубо говоря, в курсах валют всегда можно найти кучу эквивалентных по предсказательной силе гипотез, лежащих на разных кривых, просто чем длиннее гипотеза, тем вероятнее, что она перестанет предсказывать (станет фальшивой). При устремлении в бесконечность все гипотезы фальшивы (или, что то же самое в данном случае, истинная гипотеза в общем случае равна самим входным данным) :). Ваш метод находит самоподобие кривых на случайно выпущенных гипотезах, удачно пересекающиеся с решёткой дискретизации вычислителя (64 бит). (Простите за такие умозрительные объяснения, построже и математичнее пояснить вряд ли хватит компетенции :))

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

Если есть время и желание чуть дальше «покопать», то можно попробовать взять минутные данные за день, представить каждую минуту некоторым средним значением курса с учетом волатильности за выбранный период инструмента и сгенерировать gif/video по набору результатов, фрейм по каждой минуте. Будет ли заметно некоторое уменьшение всех вариантов решений? Может будет некоторый узкий диапазон решений, которые наиболее часто происходят (устойчивый узкий диапазон)? И что в моменты существования сужения диапазона будет происходить с ценовой волатильностью участвующих инструментов (снижается)? Может какой-то из инструментов будет чаще через волатильность кросса (объемы?) определять вектор движения «абсолютной» цены? И может тогда смотреть за динамикой ценовых волатильностей, есть ли где взаимосвязь (минутные задержки), а не пытаться найти «абсолютную» цену валют?

Если хочется хардкора с поиском внутри минут — можно взять не средние значения цены, а сразу тики. К примеру, здесь: www.truefx.com/?page=downloads. Ну или у иных агрегаторов/брокеров/бирж, в сети при желании это все можно найти.

В целом, я думаю что какого-то «среднего» в соотношении пар стабильно вряд ли возможно найти, так, чтобы на этом где-то получить хорошее матожидание. Слишком сложная система получается, со слабопрогнозируемым и неустойчивым во времени результатом. Возможно где-то внутри коротких промежутков времени (1… 5 минут) и получится что-то разглядеть, но такое больше удел HFTшников и там гораздо сложнее инструментарий.

Торговля/цены сильно завязаны на объем торгов набора инструментов по каждой валюте (кроссы/опционы/фьючерсы/CFD/etc), объемы в свою очередь завязаны на время торгов («сезонность» внутри дня, недели, месяца, внутри периодов действия деривативов, год к году и прочее), периоды торгов со временем также изменяются (летние/зимние времена, праздники и прочее).

При отсутствии данных по совокупным объемам торгов на биржах можно брать в расчет величину ценовой волатильности по инструментам, но такое допущение работает только на ликвидных мировых инструментах и только в некоторые пиковые по объемам часы (не более 1-2 часа в сутки) торгов, когда нет пересечения по кроссам с рынками других регионов (биржи азии/европы/сша) и нет новостного «шторма».

С большой долей вероятности все стандартные подходы наподобие методики из статьи либо уже не работают, либо очень редко работают в определенные моменты (в основном — при росте волатильности, пробой цены при сильном и стабильном росте объемов торгов).
Судя по всему, решений «много», потому что ваша целевая функция F(usd, eur, chf, gbp) инвариантна относительно умножения на скаляр: для любого a>0, F(a*usd, a*eur, a*chf, a*gbp) = F(usd, eur, chf, gbp). Это значит, что если какой-то набор (usd', eur', chf', gbp') минимизирует функцию, то то же можно сказать и про любой набор (a*usd', a*eur', a*chf', a*gbp').
Избежать этого можно положив, например, usd + eur + chf + gbp = 1, но тогда метод оптимизации должен быть другой.
Спасибо, да, тоже заметил такую инвариантность. То есть, если стартовые значения сделать в районе 100, то решений тоже будет куча, но уже тоже в районе 100.

Избежать этого можно положив, например, usd + eur + chf + gbp = 1, но тогда метод оптимизации должен быть другой.


Можно оптимизировать через конечно-разностный метод с ограничениями или, например, вообще gradient-free.

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

Интересно: если взять USD/JPY, курс будет уже на других порядках, так как йена миллионами исчисляется в быту. А если взять белорусский зайчик, там будут еще другие порядки. То есть, валюта зайчик вообще будет иметь ценность 0.000… от основных валют. А например Индекс доллара (взвешенный курс) гуляет около 100…

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


obs_rate = c(
    eurusd = 1.12012,
    gbpusd = 1.30890,
    eurchf = 1.14135,
    eurgbp = 0.85570,
    gbpchf = 1.33373,
    usdchf = 1.01896,
    usdusd = 1
)

loss = function(vec, rate){
    sum((rate - c(
        vec["eur"]/ vec["usd"],
        vec["gbp"]/ vec["usd"],
        vec["eur"]/ vec["chf"],
        vec["eur"]/ vec["gbp"],
        vec["gbp"]/ vec["chf"],
        vec["usd"]/ vec["chf"],
        vec["usd"])
    )^2)
}

initial = c(eur = 1, usd = 1, gbp = 1, chf = 1)
set.seed(123)
res = optim(initial, loss, rate = obs_rate)
res$par
# eur       usd       gbp       chf 
# 1.1200905 0.9999775 1.3089043 0.9814043 

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

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


Это не совсем понял. Судя по вашему коду (который у меня тоже также отработал), вы говорите алгоритму, чтобы доллар был ближе к 1, что и есть вроде как единственное ограничение, да?

Получается, мы хотим, чтобы доллар был единицей всегда, а остальные курсы выражались через эту единицу (что будет выполняться, даже если в паре доллара нет)? И тогда в динамике доллар будет иметь константную ценность? Это разумно?
Получается, мы хотим, чтобы доллар был единицей всегда, а остальные курсы выражались через эту единицу (что будет выполняться, даже если в паре доллара нет)?

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


obs_rate = c(
  eurusd = 1.12012,
  gbpusd = 1.30890,
  eurchf = 1.14135,
  eurgbp = 0.85570,
  gbpchf = 1.33373,
  usdchf = 1.01896
)

loss = function(vec, rate){
  # eur + usd + gbp + chf = 100
  # chf = 100 - eur - usd - gbp
  chf = 100 - vec["eur"] - vec["usd"] - vec["gbp"]
  sum((rate - c(
    vec["eur"]/ vec["usd"],
    vec["gbp"]/ vec["usd"],
    vec["eur"]/ chf,
    vec["eur"]/ vec["gbp"],
    vec["gbp"]/ chf,
    vec["usd"]/ chf
    )
  )^2)
}

initial = c(eur = 1, usd = 1, gbp = 1)
set.seed(123)
res = optim(initial, loss, rate = obs_rate)
res$par
  # eur      usd      gbp 
# 25.39684 22.67382 29.67731

# соотношение с прошлыми результатами для всех пар практически одинаковое
# > 25.39684/1.1200905
# [1] 22.67392
# > 22.67382/0.9999775 
# [1] 22.67433
# > 29.67731/1.3089043 
# [1] 22.6734
# > (100 - 25.39684 - 22.67382 - 29.67731)/0.9814043 
# [1] 22.67366

Видно, что от прошлых курсов отличие в постоянном множителе.

Про множитель я все понял, спасибо!

А вот это

Да, наложено условие, чтобы обменный курс к доллару был максимально близок к единице


все таки не совсем понятно. Мне в коде читается так, что просто доллар = 1. А обменные курсы это пары, они восстанавливаются в оригинальном значении, но такими, чтобы везде usd = 1. То есть, ценность доллара единица, и это априорная гипотеза.

У нас же результат оптимизации — это курс некоей абсолютной валюты к известным валютам. vec["usd"] — это курс доллара к абсолютной валюте. В функции потерь дополнительная компонента (1 - vec["usd"])^2, которая задаёт условие, чтобы этот курс абсолютной валюты к доллару был максимально близок к единице, т. е. желательно, но не обязательно, чтобы они менялись один к одному. Или я что-то неправильно понимаю?

У нас же результат оптимизации — это курс некоей абсолютной валюты к известным валютам.


Можно сказать и так…

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

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

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

Согласен. Но мне не нравится, что usd = 1 = const, не будет меняться с течением времени. Хотелось бы, чтобы был скрытый параметр (Абс.Валюта), не заменяемый существующей валютой, то есть, без вашего ограничения.

Тогда все будет меняться, например, так:



В этом эксперименте оптимизация без ограничений но со слабенькой регуляризацией, а начальные значения валют положены равными 1 (пальцем в небо). Дальше они меняются от своих предыдущих значений. Получается warm-starts метод.

Полный код: github code

Если подключить исторические данные, то в качестве единицы измерения будет доллар в какой-то один момент времени времени. Все последующие курсы доллары уже не равны единице, а будут выражаться, допустим, через доллар на 2018-12-01.

Поглядите мой код, там можно как-то ограничить loss, данные тянутся за 180 дней…

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


express <- expression(
    (eurusd - eur / usd) ^ 2 +
        (gbpusd - gbp / usd) ^ 2 +
        (eurchf - eur / chf) ^ 2 +
        (eurgbp - eur / gbp) ^ 2 +
        (gbpchf - gbp / chf) ^ 2 +
        (usdchf - usd / chf) ^ 2 +

        (cadchf - cad / chf) ^ 2 +
        (gbpcad - gbp / cad) ^ 2 +
        (usdcad - usd / cad) ^ 2 +
        (eurcad - eur / cad) ^ 2 +
        (usd^2 + eur^2 + chf^2 + gbp^2 + cad^2) * alpha +
        (1 - (usd + eur + chf + gbp + cad)/5)^2
)

Мой код тут, я опять использовал готовый оптимизатор: https://gist.github.com/gdemin/2f6e3c51383c66b1789e904185b75f40


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

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

Я немножко помедитировал на условия этой задачи и оказалось, что она легко сводится к обычной линейной регрессии. Рассматривать будем случай из вашей статьи, без динамики. Для одной пары мы имеем: eurusd = eur / usd. Можно взять логарифм от обоих частей, тогда получим log(eurusd) = log(eur) - log(usd). Далее это выражение можно представить таким образом: log(eurusd) = 1*log(eur) - 1*log(usd) + 0*log(gdp) + 0*log(chf). Это уже уравнение обычной линейной регрессии, где в роли коэффициентов выступают наши абсолютные курсы. Тут уже пора перейти к коду:


obs_rates = c(
    eurusd = 1.12012,
    gbpusd = 1.30890,
    eurchf = 1.14135,
    eurgbp = 0.85570,
    gbpchf = 1.33373,
    usdchf = 1.01896
)

l_obs_rates = log(obs_rates)

if(FALSE){
    ## псевдокод - не запускать
    # l_* обозначает логарифм величины
    l_eurusd = 1*l_eur - 1*l_usd + 0*l_gdp + 0*l_chf 
    l_gbpusd = 0*l_eur - 1*l_usd + 1*l_gdp + 0*l_chf 
    l_eurchf = 1*l_eur + 0*l_usd + 0*l_gdp - 1*l_chf 
    l_eurgbp = 1*l_eur + 0*l_usd - 1*l_gdp + 0*l_chf 
    l_gbpchf = 0*l_eur + 0*l_usd + 1*l_gdp - 1*l_chf 
    l_usdchf = 0*l_eur + 1*l_usd + 0*l_gdp - 1*l_chf 
}

coef_matr = rbind(
    c(1, - 1, + 0, + 0),
    c(0, - 1, + 1, + 0),
    c(1, + 0, + 0, - 1),
    c(1, + 0, - 1, + 0),
    c(0, + 0, + 1, - 1),
    c(0, + 1, + 0, - 1)
)
colnames(coef_matr) = c("eur", "usd", "gdp", "chf")

regr_dat = data.frame(l_obs_rates, coef_matr)
# + 0 в уравнении регрессии, так как у нас нет свободного члена
res = lm(l_obs_rates ~ . + 0, data = regr_dat)
summary(res)
exp(coef(res))
# абсолютные курсы
#  eur      usd      gdp      chf 
# 1.141333 1.018961 1.333749  NA 
# погрешность
obs_rates - exp(predict(res))
#   eurusd        gbpusd        eurchf        eurgbp        gbpchf        usdchf 
# 2.540224e-05 -3.079330e-05  1.744148e-05 -3.248295e-05 -1.925139e-05 -8.634867e-07 

Используется классическая регрессия, однако очевидно, что применима вся мощь современных подходов с регуляризацией.
Швейцарский франк у нас выпал, так как является линейной комбинацией других валют. Его абсолютный курс легко рассчитать: chf = eur/eurchf. Интересно, что он близок к единице. Я поэкспериментировал, используя в регрессии переменные в другом порядке и всегда получалось, что выпадающий курс практически равен 1.
Еще некоторые дополнительные замечания:


  1. У нас на четыре переменных шесть кейсов. Это совсем не круто и добавление истории эту проблему не решает. Её решит только добавление новых пар валют — кол-во переменных растет медленнее, чем кол-во пар.
  2. Для динамики все сложнее. Наивный способ, когда на каждом временном интервале своя регрессия, не очень хорош. Тут имеет смысл смотреть на иерархические регрессионные модели (например, пакет lme4).
Отлично. Вот, я тоже хотел свести к линейному решению, но не мог понять как дизайн сделать. Почитаю, позапускаю. Спасибо! И, да, похоже, для динамики нужны что-то типа панельные методы, я их не пробовал, но будет смысл покурить.
Воспроизвел ваш код. Очень интересно. Получается, судя по невязкам, что все таки не 100% фит, это наверное связано с точностью при операциях на матрице (обращение)?

> res$residuals
       eurusd        gbpusd        eurchf        eurgbp        gbpchf        usdchf 
 2.267840e-05 -2.352581e-05  1.528156e-05 -3.795996e-05 -1.443414e-05 -8.474192e-07 

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

Ага, понятно.

Ну, можно вытащить все возможные пары на один момент времени. Их около 90. Значит, будет 14 валют.

Может, пригодится. По списку валют вытягивает все их пары и конструирует матрицу:


currs <- c(
    'usd',
    'eur',
    'chf',
    'gbp',
    'cad',
    'jpy',
    'rub',
    'cny',
    'aud'

)

## download and bind historic rates for pairs --------------

rate.env <- new.env()

for(i in seq_along(currs)) for(j in seq_along(currs)[-seq_len(i)]){
    cat(currs[i], currs[j], "\n")
    quantmod::getFX(
        toupper(
            paste0(currs[i]
                   , '/'
                   , currs[j]
            )
        )
        , from = as.Date("2019-04-29")
        , to = as.Date("2019-04-29")
        , env = rate.env
    )
}

obs_rates = unlist(as.list(rate.env))

l_obs_rates = log(obs_rates)
currs = toupper(currs)
coef_matr = do.call(cbind, lapply(currs, function(currency){
    ifelse(grepl(paste0("^", currency), names(l_obs_rates)), 1,
           ifelse(grepl(paste0(currency, "$"), names(l_obs_rates)), -1, 
                  0
           ))
}))
colnames(coef_matr) = currs

regr_dat = data.frame(l_obs_rates, coef_matr)

res = lm(l_obs_rates ~ . + 0, data = regr_dat)
summary(res)
Ну, это бленд наших кодов. Я его посмотрел, lm построил. Дальше что делать, вопрос. Я хотел поизучать характеристики временных рядов. На первый взгляд они похожи на валютные пары. Длиннохвостные, нестационарные, есть автокорреляция. Но также при этом они между собой по первым разницам коррелируют.

Да, но тут матрица автоматически конструируется, а не в ручную выписывается.

Я и не знал, что Yahoo столько пар может сформировать. Вау.
Решил посмотреть на ошибки попристальнее. Взял 17 валют и золотишко. Золотишко генерит большие отклонения. Попробовал также GLM

# clear environment

rm(list = ls()); gc()


## load libs

library(data.table)
library(ggplot2)
library(magrittr)
library(quantmod)
library(scales)

currs <- c(
     'eur',
     'chf',
     'gbp',
     'cad',
     'jpy',
     'rub',
     'cny',
     'cnh',
     'aud',
     'nok',
     'sek',
     'nzd',
     'try',
     'czk',
     'xau',
     'hkd',
     'mxn',
     'usd'
)

## download and bind historic rates for pairs --------------

rate.env <- new.env()

for(i in seq_along(currs)) for(j in seq_along(currs)[-seq_len(i)]){
     cat(currs[i], currs[j], "\n")
     quantmod::getFX(
          toupper(
               paste0(currs[i]
                      , '/'
                      , currs[j]
               )
          )
          , from = as.Date("2019-04-29")
          , to = as.Date("2019-04-29")
          , env = rate.env
     )
}

obs_rates = unlist(as.list(rate.env))

l_obs_rates = log(obs_rates)

currs = toupper(currs)

coef_matr = do.call(cbind, lapply(currs, function(currency){
     ifelse(grepl(paste0("^", currency), names(l_obs_rates)), 1,
            ifelse(grepl(paste0(currency, "$"), names(l_obs_rates)), -1, 
                   0
            ))
}))

colnames(coef_matr) = currs

regr_dat = data.frame(l_obs_rates, coef_matr)

# lm

lm_mod = lm(l_obs_rates ~ . + 0, data = regr_dat)

summary(lm_mod)

lm_coefs <- exp(coef(lm_mod))

lm_resids <- exp(lm_mod$fitted.values) / obs_rates - 1

lm_resids[which.max(abs(lm_resids))]

head(sort(abs(lm_resids), decreasing = T))

plot(lm_resids)

# glm

glm_mod = glm(l_obs_rates ~ . + 0, data = regr_dat, family = gaussian(link = "identity"))

summary(glm_mod)

exp(coef(lm_mod))

glm_coefs <- exp(coef(glm_mod))

glm_resids <- exp(glm_mod$fitted.values) / obs_rates - 1

glm_resids[which.max(abs(glm_resids))]

plot(glm_resids)
github.com/alexmosc/abs_forex/blob/master/fx_currency_absolute_linear_dynamic.R Посмотрите — ваш подход через линейку в динамике, доллар = 1. Интересные временные ряды получились и корреляции довольно разумные, ИМХО. Например, Евро с Чешкой — 0.91 по первым разностям. Думаю, есть повод для арбитража?..

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

Во всем этом точно что-то скрывается. Надо дальше моделировать, может быть, откроется истина.

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

Швейцарский франк у нас выпал, так как является линейной комбинацией других валют.

Можно подробнее, откуда взялось это утверждение? Оно же противоречит реальности.

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

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


Это и была задача исследования. А конечная цель (у автора оригинальной статьи) — применить метод портфельной оптимизации к абс.валютам.
К сожалению, я случайно опубликовал комментарий, не завершив мысль…
Я бы размышлял следующим образом:
1. С точки зрения экономики абсолютный курс — есть универсальная мера стоимости любого блага, доступного для приобретения. Много экономистов сломали копья на тот счет, что именно может служить такой универсальной мерой. Например, рассматриваются такие эквиваленты стоимости как энергия, человеческий труд и пр. В настоящее время поиски этого экономического грааля продолжаются и существуют интересные теории стоимости с каноническим примером цены бутылки воды, превышающей цену бриллианта в условиях жажды в пустыне.
2. С математической точки зрения абсолютная мера любого товара или труда, да что там – любой величины — могла бы быть выведена как евклидово расстояние в ортогональном пространстве базисных координат. Ортогональность в таком случае будет означать, что энергию нельзя линейно выразить через труд, основные фонды и, например, землепользование.
3. Следуя предложенной идеологии и переходя к курсам валют, следует также определить некий ортогональный базис, но очевидно, что в текущей ситуации базис вырождается до одного измерения в виде американского доллара. Действительно, все базисные товары торгуются в этой валюте, а не какой либо другой. Другими словами все курсы валют коллинеарные единичному вектору в виде доллара. Действительно, взяв три пары валют:
eurusd 1.12012
gbpusd 1.3089
eurgbp 0.8557

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

В целом абсолютные курсы валют гипотетически будут иметь смысл в том случае, когда мировая торговля перейдет на торговлю экспортируемых товаров в локальных валютах. В этом случае каждая валюта может иметь абсолютный курс, который будет выражать меру спроса/предложения на объемы экспортируемых благ страны-экспортера.
У нас просто моделирование. если эти валюты будут полезны для инвестирования значит хорошо, а если нет, то это просто умственное упражнение.
Как я вижу смысл — получить некий аналог курса доллара как такового. Т.е. из бесконечного шума изменения валютных пар вычленить динамику каждой конкретной валюты к некоему абсолюту. Сейчас обыватель может отдельно получить динамику курса какой-либо валюты к доллару, часто воспринимаемому как абсолют + динамику индекса доллара, который тоже не идеален. Благодаря этому посчитанному абсолюту можно оторвать интересующую валюту от эффектов самого доллара и получить ее чистую динамику. Т.е. не делать оговорку «вот здесь курс был низкий, но вы же помните, как тогда взлетел доллар ко всем валютам». Что дальше делать с этой информацией — загадка. Оценивать активы, пассивы, эффект от инвестиций в чем-то кристаллизованном, например.
Если положить на старте все абсолютные курсы = 1 и дальше работать с приращениями, это не поможет избавиться от множества решений?
Sign up to leave a comment.

Articles