Tuesday, November 29, 2011

Дивиденды rusquant 0.3.3

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

library(rusquant)
divs <-  getAllDividends()
Все дивиденды, можно получить командой getAllDividends(allYears = TRUE)
Возвращаемый объект содержит следующие данные:

Symbol - id акции на бирже
Name - полное название эмитента
BoardDate - дата собрания совета директоров
RegistryDate - дата закрытия реестра
RecommendedDivs - рекомендуемые дивиденды
Divs - выплаченные дивиденды

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

result <- NULL
    for(i in (1:length(divs[,1]))){
       d <- divs[i,]
       if (d$Divs>0){
          try({
              quotes <- getSymbols(d$Symbol, src="Finam", from="2010-01-01", auto.assign=FALSE)
             if (!is.nan(quotes)){ 
                 price <- Cl(quotes[d$RegistryDate])
                 if (length(price)>0){
                      dd <- d$Divs
                      result <- rbind(result, data.frame(d$Symbol, d$Name, d$RegistryDate, as.numeric(dd)/as.numeric(price), stringsAsFactors=FALSE))
                 }
             }
          }, silent=TRUE)
       }
    }
colnames(result) <- c('Symbol', 'Name', 'RegistryDate', 'Divs')
result[with(result, order(Divs, decreasing=TRUE)),c('Symbol','RegistryDate','Divs')]


Аналогично можно построить статистику для прошлых лет.

Monday, November 28, 2011

Регуляризация линейной регрессии с примерами на R

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

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

"gen" <- function(x){
    return(3*x^2 + 2*x + 2 + rnorm(length(x))*0.5)
}

#Генерируем тренировочные данные
X <- runif(6)
Y <- gen(X)
#Данные для кросс-валидации
Xcv <- runif(50)
Ycv <- gen(Xcv)
Тут все просто, данные распределены согласно некой выдуманной формуле 3*x2+2*x+2 плюс некоторая погрешность, случайным образом распределенная. Тренировочных данных было умышленно взято очень мало, для наглядности (в реальных задачах их все равно, как правило, не хватает, так что это ничуть не мешает обобщению результатов).

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

#Линейная регрессия по полиномиальному набору данных
train <- data.frame(Y, X, X^2, X^3, X^4, X^5)
colnames(train) <- c('Y', 'X', 'X2', 'X3', 'X4', 'X5')
simple <- lm(Y ~ X+X2+X3+X4+X5, train)
error <- sum((predict(simple, train)-Y)^2)/length(Y)
cat("Train error: ",error,"\n")

cv <- data.frame(Xcv, Xcv^2, Xcv^3, Xcv^4, Xcv^5)
colnames(cv) <- c('X', 'X2', 'X3', 'X4', 'X5')
error <- sum((predict(simple, cv)-Ycv)^2)/length(Ycv)
cat("Cross-validation error: ",error,"\n")

#Построим кривую, описывающую наше решение
x <- (1:100)/100
test = data.frame(x, x^2, x^3, x^4, x^5)
names(test) <- c('X', 'X2', 'X3', 'X4', 'X5')
y0 <- predict(simple, test)

plot(X, Y, ylim=range(y0), xlim=c(0,1))
lines(x,y0, col='red')
В результате, ошибка на тренировочных данных близка к нулю, а на данных для кросс-валидации очень велика.
Train error:  4.951098e-13 
Cross-validation error:  6.751967 
Это, так называемый, overfitting. И он абсолютно ожидаемый, ведь через 6 точек на плоскости всегда можно провести полином 5-ой степени.

Тут, читатель может заметить, а к чему, собственно, нам полином 5-ой степени, при таком количестве точек разумней было бы использовать меньше степеней. Согласен, случай выдуманный, но аналогичная проблема возникает в случае сильно-многомерных данных. Например, при обработке текстов. Где переменная X запросто может быть вектором с 10000 элементов.

Что делать?

Для начала, посмотрим на формулу, которой мы пытаемся описать наши данные:
yt = a0 + a1*x + a2*x + a3*x + a4*x + a5*x
Мы пытаемся подобрать эти коэффициенты ai таким образом, чтобы минимизировать ошибку т.е. минимизировать выражение:
L = Σ(yt-y)2
А что если в это выражение добавить еще какой-нибудь член, чтобы как-то уменьшить величины коэффициентов ai? Например, вместо L можно было бы минимизировать выражение:

L1 = Σ(yt-y)2 + λ*Σ|ai|

или другой вариант

L2 = Σ(yt-y)2 + λ*Σ(ai)2

Что мы здесь сделали? Мы добавили в наше выражение штраф за большие значения ai. И величина этого штрафа пропорциональна величине параметра λ, с помощью которого мы теперь можем настраивать наш алгоритм.

Первый вариант носит название L1-регуляризация (в английской литературе LASSO regression), второй вариант L2-регуляризация или регуляризация Тихонова (в английской литературе ridge regression)

Оба варианта реализованы в R. К сожалению, реализация немного не последовательна в используемых входных параметрах и возвращаемых значениях. Тем не менее, мы можем посмотреть её использование чтобы сравнить результат работы.

L1-регуляризация или Lasso regression

L1 регуляризация реализована в пакете lars.

#Lasso regression
#Требуется установить пакет lars если его нет
#install.packages('lars')
library(lars)

train <- cbind(X, X^2, X^3, X^4, X^5)
colnames(train) <- c('X', 'X2', 'X3', 'X4', 'X5')
lasso <- lars(train, Y, type='lasso')

cv <- cbind(Xcv, Xcv^2, Xcv^3, Xcv^4, Xcv^5)
colnames(cv) <- c('X', 'X2', 'X3', 'X4', 'X5')

x <- (1:100)*(max(X)*1.1)/100
test <- cbind(x, x^2, x^3, x^4, x^5)
colnames(test) <- c('X', 'X2', 'X3', 'X4', 'X5')


stats <- NULL
lambda <- 0
plot(X, Y, ylim=c(-5, 10), pch=20, col='red')
points(Xcv, Ycv, pch=20, col='blue')
ls <- NULL
cs <- NULL
for(i in 1:15){
    Yp <- predict(lasso, train, s=lambda, type='fit', mode='lambda')
    trainError <- sum((Yp$fit-Y)^2)/length(Y)

    Yp <- predict(lasso, cv, s=lambda, type='fit', mode='lambda')
    cvError <- sum((Yp$fit-Ycv)^2)/length(Ycv)

    stats <- rbind(stats, c(lambda, trainError, cvError))
    if (i%%5==1){
        #Построим кривую, описывающую наше решение
        y0 <-  predict(lasso, test, lambda, type='fit', mode='lambda')
        lines(x,y0$fit, col=i)
        ls <- c(ls, paste("lambda=",lambda,sep=''))
        cs <- c(cs, i)
    }

    lambda <- ifelse(lambda==0,0.00000001, lambda*10)
}
legend("topleft", c("Train", "Cross-validation"), pch=20, col=c('red', 'blue'))
legend("bottomright",inset=0.05, legend=ls, pch=20, col=cs, text.col=cs, bty="n")



plot(stats[,2], ylim=range(stats[,2:3]), type='l', col='red', ylab="error", xlab="log(lambda)")
lines(stats[,3], col='blue')

#Оптимальное значение lamda=1
coef(lasso, s=1, mode='lambda')

На графике показано несколько решений, для разных значений λ. При λ=0 получается обычная линейная регрессия, которую я описал выше. При очень больших значениях λ получается горизонтальная линия, т.е. решение при котором коэффициенты ai=0, для всех i>0. Оба крайних случая очевидно не являются оптимальными решениями. Как же найти оптимальное? Для этого построим график ошибки.

Здесь, на графике, красным цветом отмечена ошибка для тренировочного множества, а синим цветом ошибка для cross-validation множества. Видно, что ошибка тренировочного множества сначала очень мала (overfitting), но постепенно возрастает (underfitting). В то же время ошибка cross-validation имеет выраженный минимум, который для нас и будет оптимальным значением λ В нашем случае, это оптимальное значение оказалось равным 10.

У L1 регуляризации есть одна полезная особенность. Многие коэффициенты оказываются в точности равны 0. Например, в нашем случае:
X        X2        X3        X4        X5 
0.2989565 0.0000000 0.0000000 0.0000000 5.2679886 
Т.е. L1 регуляризация может быть использована еще и как алгоритм feature selection.

Регуляризация Тихонова или Ridge regression

Этот алгоритм реализован в пакете MASS. К сожалению, реализация не совсем стандартная, например, пакет MASS не предоставляет нам функции predict которой так привычно пользоваться тем, кто знаком с R. Тем не менее, не представляет никакой сложности самостоятельная реализация этой функции через умножение матриц.

#Ridge regression
#Требуется установить пакет MASS если его нет
#install.packages('MASS')
library(MASS)

train <- data.frame(Y, X, X^2, X^3, X^4, X^5)
colnames(train) <- c('Y', 'X', 'X2', 'X3', 'X4', 'X5')

stats <- NULL
lambda <- 0
x <- (1:100)/100
test <- as.matrix(cbind(1, x, x^2, x^3, x^4, x^5))

plot(X, Y, ylim=c(-5,15), pch=20, col='red')
points(Xcv, Ycv, pch=20, col='blue')
ls <- NULL
cs <- NULL
for(i in (1:15)) {

    ridge <- lm.ridge(Y ~ X+X2+X3+X4+X5, train, lambda=lambda)
    Yp <- as.matrix(cbind(1, X, X^2, X^3, X^4, X^5)) %*% as.matrix(coef(ridge))
    trainError <- sum((Yp - Y)^2)/length(Y)

    cv <- as.matrix(cbind(1, Xcv, Xcv^2, Xcv^3, Xcv^4, Xcv^5))
    Yp <- cv %*% as.matrix(coef(ridge))
    cvError <- sum((Yp-Ycv)^2)/lentgh(Ycv)

    stats <- rbind(stats, c(lambda, trainError, cvError))
    if (i%%5==1){
        #Построим кривую, описывающую наше решение
        y0 <- test %*% as.matrix(coef(ridge))
        lines(x,y0, col=i)
        ls <- c(ls, paste("lambda=",lambda,sep=''))
        cs <- c(cs, i)
    }
    lambda <- ifelse(lambda==0,0.00000001, lambda*10)

}
legend("topleft", c("Train", "Cross-validation"), pch=20, col=c('red', 'blue'))
legend("bottomright",inset=0.05, legend=ls, pch=20, col=cs, text.col=cs, bty="n")


#Рисуем изменение ошибки CV
plot(stats[,2], ylim=range(stats[,2:3]), type='l', col='red', ylab="error", xlab="log(lambda)")
lines(stats[,3], col='blue')

#Оптимальное значение lamda=10
ridge <- lm.ridge(Y ~ X+X2+X3+X4+X5, train, lambda=10)
coef(ridge)

Аналогично случаю L1 регрессии, здесь мы тоже можем определить оптимальные значения параметра λ

Оптимальное значение λ в данном случае оказалось равным 1. И соответствующие коэффициенты, как видим, совсем не равны 0.
X        X2        X3        X4        X5 
3.0257674 0.7951164 0.7637522 0.8631815 1.0352908 1.2643954 

Естественно методы регуляризации не ограничиваются L1 и L2-регуляризациями. Существует еще несколько популярных подходов, например алгоритм elastic net, включающий в себя как L1 так и L2.

Friday, September 30, 2011

[Text mining] Индекс настроений фондовых рынков

В прошлом году я уже писал о нашумевшей работе американцев, которые умудрились предсказывать индекс S&P500 по сообщениям в twitter. Это было лишь начало, сейчас в интернете можно найти множество статей посвященных анализу текстовых данных для предсказания котировок на бирже:

"Trading Strategies to Exploit Blog and News Sentiment" (Zhang, Skiena 2010)
"The Predictive Power of Financial Blogs" (Frisbee 2010)
"An analysis of verbs in financial news articles and their impact on stock price" (Schumaker 2010)

Но это все там, за океаном. А что же у нас, в России?

Искать правду в твиттере для российского рынка, мне показалось, бесполезным. Во-первых твиттером у нас пользуются по-прежнему единицы, а во-вторых у американцев, так или иначе, в фондовый рынок вовлечено почти все население, у нас же это развлечение для избранных. Зато в России есть раскрученная социальная сеть трейдеров comon.ru. На этом ресурсе публикуется несколько десятков статей ежедневно и, что самое главное, они по теме. Что может быть лучше для задачи анализа текста?

Итак, сказано - сделано. Я решил проверить связано ли как-то настроение блогеров comon.ru с движениями на рынке и, самое интересное, могут ли эти данные быть использованы для предсказания движений рынка. Первым делом, было выкачано около 10 тыс. статей от 100 авторов с comon.ru, вместе с датой публикации и именем автора. Потом текст статей был обработан: удалены все элементы HTML-разметки и стоп-слова а у всех оставшихся слов была выделена основа.

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

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

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

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

Далее, после усреднения данных по дням, получился некоторый индекс, назовем его индексом настроений comon.ru. Это число, которое характеризует среднее настроение блогеров comon.ru в этот день. Интересным оказался график изменения индекса настроения:

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

Следующим шагом, я проверил а сколько можно было бы заработать если покупать вечером того дня когда индекс настроений больше нуля и закрывать позицию на следующий день. И аналогично для отрицательных значений индекса настроений. Оказалось, что среднее дневное изменение индекса ММВБ в день, следующий за днем с позитивным настроением составляет: 0.13%. А в дни следующие за днями с негативным настроением -0.34%. Результат мне показался вдохновляющим. И было решено проверить математически гипотезу о том, что индекс настроения комона предсказывает значения ММВБ. Для этого, я воспользовался методом описанным в оригинальной работе, а именно, причинным тестом Грейнджера:

Model 1: MICEX ~ Lags(MICEX, 1:1) + Lags(SIGNAL, 1:1)
Model 2: MICEX ~ Lags(MICEX, 1:1)
  Res.Df Df      F  Pr(>F)  
1    178                    
2    179 -1 2.9539 0.08741 .

P-value теста составило 0.087, что меньше 0.1. Значит наша гипотеза о том что индекс настроений комона предсказывает изменения индекса ММВБ на следующий день верна с вероятностью 90%. Что является неплохим результатом, вполне сравнимым с результатом в оригинальной работе с твиттером.

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

Что использовалось для данной работы?
1. Граббер текстов с comon.ru и предварительная обработка были написан на Java
2. Вся математика, анализ и построение графиков выполнялись на R с использованием библиотек lmtest и rusquant

Какие варианты развития?
1. В отличии от твиттера, количество авторов комона которые пишут регулярно не велико. Можно ввести что-то вроде предсказательного рейтинга автора, тем самым улучшив общий прогноз.
2. Учитывать оценки других пользователей и комментарии к каждой статье.
3. Добавить анализ морфологии русского языка, учитывать время в котором написаны отдельные предложения статьи.



Tuesday, September 20, 2011

Этот безумный R


Сначала мне казалось, что это простой и примитивный язык, для небольших скриптов, с уклоном в статистику. Потом мне стало казаться, что это самый обычный язык программирования, с уклоном в статистику. Сейчас мне кажется, что это гремучая смесь классических императивных языков вроде C++ или Java и матерой функциональщины вроде Haskell или Lisp. И всё это R.

На самом деле, действительно, R многое заимствовал из Lisp. Функции в R являются полноценными сущностями. Их можно не только вызывать и определять но и передавать в качестве параметров или составлять из них сложные выражения. В R доступны замыкания и отложенные вычисления. При этом, в отличии, например, от Haskell, R не страдает от функциональной чистоты и допускает функции с побочными эффектами. Я уже не говорю о том, что в R еще и объектно-ориентированный язык. Правда, признаюсь честно, реализация объектно-ориентированного программирования в R больше похожа на помойку. В R есть несколько ООП-систем и каждая живет своей жизнью. Все это многообразие, делает работу сложней и интересней. Нередко, разобравшись в концепции, начинаешь понимать, как тот или иной код можно написать короче и эффективней. Некоторыми такими концепциями я бы и хотел сегодня поделиться.

Monday, September 19, 2011

Тиковые данные rusquant v0.3

Очередное обновление rusquant. Теперь стали доступны тиковые данные.

Как обычно, обновиться, тем у кого уже есть, или установить тем, у кого еще нет, можно командой в консоли R:
install.packages("rusquant", repos="http://R-Forge.R-project.org")

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

Для получения тиковых данных можно воспользоваться знакомой функцией getSymbols()

> library(rusquant)
>  getSymbols("SBER", from=Sys.Date()-1, src="Finam", period="tick")
[1] "SBER"
>  SBER[1:5]
                    SBER.Close SBER.Volume
2011-09-14 09:59:59      79.24          30
2011-09-14 09:59:59      79.24          30
2011-09-14 09:59:59      79.24          10
2011-09-14 09:59:59      79.24          20
2011-09-14 09:59:59      79.24         500

Сразу отмечу, что вызов выполняется достаточно долго, т.к. объем получаемых данных очень большой. Для удобства, сохранена система именования OHLC данных. Т.е. цены можно получить всё той же функций Cl, а объем Vo.

Если же есть необходимость преобразовать тиковые данные в OHLC, то нет ничего проще, в модуле xts для этого есть ряд удобных функций типа to.minutes, to.hourly, to.minutes10 и более общая функция to.period

> to.period(SBER, "mins", k=5)
                    SBER.Open SBER.High SBER.Low SBER.Close SBER.Volume
2011-09-14 09:59:59     79.24     79.24    79.24      79.24      287520
2011-09-14 10:04:58     79.47     79.68    79.11      79.52     4042460
2011-09-14 10:09:59     79.52     79.66    79.34      79.35     3095280

Доступные значения периодов: “secs”, “seconds”, “mins”, “minutes”, “hours”, “days”, “weeks”, “months”, “quarters”, и “years”. Хотя, я сомневаюсь, что последними кто-то будет пользоваться. k=5 - показывает какое количество периодов объединять в группу. В данном примере мы получили пятиминутные данные.

Tuesday, September 13, 2011

Геомагнитные бури на фондовом рынке


Сегодняшняя тема немного несерьезная. А может быть наоборот очень серьезная. Так сложилось, что во времена моей прыщавой юности, когда рухнул совок и было не во что больше верить, люди начали слушать всяких Чумаков, Кашпировских и прочих телепузиков. Кажется именно тогда особой популярностью пользовались геомагнитные бури и бабушки у подъезда с радостью списывали очередной приступ остеохондроза на повышенную геомагнитную активность. Наверное, именно такое отношение и привело в последствии к предвзятому взгляду на прогнозы геомагнитной активности (я то её влияние никогда не чувствовал). Тем не менее, явление то существует и объективно регистрируется.

Есть масса исследований, утверждающих, что геомагнитные бури влияют на настроение людей. Люди склонны пессимистичней оценивать происходящее вокруг них во время и после бури, нежели в спокойные дни. Теперь подумаем, если люди глядят на вещи с пессимизом, причем это люди по всей земле. Значит, это должно непременно сказываться на фондовом рынке. Логично предположить, что магнитные бури должны приводить к сокращению вложений в рискованные активы, такие как акции. А раз так, то этим можно было бы воспользоваться для построения собственной торговой стратегии. И вот, в статье "Playing the Field: Geomagnetic Storms and the Stock Market" такое исследование уже проводилось и результаты мне показались достаточно интересными чтобы проверить их на практике.

Tuesday, September 6, 2011

Обновление rusquant 0.2

Небольшая библиотека для работы с Российскими фондовыми рынками в R пополнилась новыми возможностями. Теперь, помимо доступа к данным с Финама, прямо из R вы можете получить доступ к котировкам фьючерсов и опционов Forts, доступным на сайте rts.ru

Итак, для тех что еще не установил себе rusquant - ставим, для тех, кто уже установил - обновляемся командой:

install.packages("rusquant", repos="http://R-Forge.R-project.org")

Что новенького?

1. Стала доступной доска опционов:

library(rusquant)
rts <- getOptionChain("RTS-9.11", "2011-09-15", src="Forts", session = "MAIN")
В качестве параметров передается название базового инструмента, дата экспирации и источник данных src="Forts", кроме того, можно указать сессию session = "MAIN" - основную или session = "EVENING" - вечернюю. Для примера, построим улыбку волатильности. Замечу, что ожидаемая волатильность в данном примере считается биржей и точной методики расчета мне найти не удалось.
plot(cbind(rts$calls[,'Strike'], rts$calls[,'IV']), type='l', main="Implied Volatility", ylab="Volatility", xlab="Strike")
Графики открытого интереса:
plot(cbind(rts$calls[,'Strike'], rts$calls[,'OI']), type='l', col='blue', main="Open Interest", ylab="R", xlab="Strike")
lines(cbind(rts$puts[,'Strike'], rts$puts[,'OI']), type='l', col='red')
2. Стали доступны дневные исторические данные по опционам:
getSymbols("RTS-9.11M150911CA 185000", from="2011-03-21", src="Forts")
chartSeries(RTS911M150911CA185000)


Планы на будущее: добавить дивиденды, статистику и тиковые данные.

Thursday, September 1, 2011

И снова о коинтеграции временных рядов

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

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

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

Рассмотрим пример на языке программирования R:
data <- rnorm(50)
plot(data, type='l')

Monday, August 29, 2011

Сравнение временных рядов

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

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

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

Thursday, August 25, 2011

Куплю будущее, недорого


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

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

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

А важны нам сейчас фьючерсы. О них и поговорим.

Thursday, August 18, 2011

Feature selection с примерами на R

В серии постов о data mining я уже описал несколько различных методов и алгоритмов. Они все разные, но у них есть одна неизбежная общая черта: на вход подается некоторый набор "входных параметров" (в англоязычной литературе используется слово feature) и уже дальше начинаются все чудеса и преобразования. В случае с финансовыми данными, этими параметрами могут быть цены, объемы торгов, день недели, технические индикаторы и много чего еще. Но откуда берутся эти параметры и почему они именно такие? До сих пор, этот вопрос я не рассматривал.

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

Monday, August 8, 2011

Исследуем Российский рынок ценных бумаг с помощью R

В прошлых статьях, посвященных среде разработки R, я многократно упоминал о расширении quantmod, пожалуй, одном из самых полезных расширений для желающих строить математические модели финансовых рынков в R. Единственный недостаток этого расширения - отсутствие поддержки российских рынков, и этот недостаток я решил исправить. Для этого, было написано маленькое расширеньице, оно позволяет автоматически выкачивать данные для российских площадок с сайта Finam. По мере возможности, постараюсь добавить еще данные с сайта РТС.

Установить это расширение просто. Если у вас R последней версии (2.13 на момент написания текста) то достаточно запустить его и выполнить команду:

install.packages("rusquant", repos="http://R-Forge.R-project.org")

Если R более ранней версии, но вы используете Windows, то можно поступить так:

install.packages("rusquant", contriburl="http://r-forge.r-project.org/bin/windows/contrib/latest/")

И третий вариант установки. Можно зайти на http://r-forge.r-project.org/projects/rusquant/ и вручную скачать либо исходники либо готовую версию.

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

Например, можно построить дневной график фьючерса на индекс РТС:

library(rusquant)
getSymbols("SPFB.RTS", from="2011-01-01", src="Finam")
chartSeries(SPFB.RTS)


Кроме того, в отличии от оригинального quantmod, здесь есть маленький бонус, а именно, нам доступны не только дневные но и внутри-дневные данные. Например, можно посмотреть часовик фьючерса на золото:

library(rusquant)
getSymbols("SPFB.GOLD", from=Sys.Date()-5, src="Finam", period="hour")
chartSeries(SPFB.GOLD)


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

library(rusquant)
getSymbols("SPFB.GOLD", from=Sys.Date()-5, src="Finam", period="5min")
chartSeries(SPFB.GOLD)


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

Всего доступны для загрузки 9 временных интервалов: 1min, 5min, 10min, 15min, 30min, hour, day, week, month. Доступные инструменты и их коды можно смотреть на сайте источника в поле имя контракта.

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

Friday, July 29, 2011

Topcoder. String compression.

В прошлый раз, я писал о своем желании провести детальный разбор решений лучших участников топкодера с целью постичь их тайное дао и примкнуть к их рядам. Итак отчитываюсь.
Первым делом я решил разобрать решение победителя последнего онлайн тура Topcoder Open 2011, участника из Китая, с ником ACRush. Его решение было написано на C++ и состояло из более чем 1800 строк кода. Для того чтобы это переварить, я решил переписать его решение на Java.

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

1: abc2a3sd4
2: qwelwesdfdf
3: erwe2rew4dfds
4: qweqwe2dferw

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

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

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

Более точно условие задачи можно прочитать тут.

Wednesday, July 13, 2011

Немного вечерней философии...

Сегодня для меня завершился очередной Topcoder Open, не буду скрывать, в глубине души я все-таки надеялся попасть в финал. Но, надежды надеждами, а результаты так себе. И вроде бы с одной стороны можно успокаивать себя прогрессом, два года назад я вообще не прошел в третий тур, год назад я был на 66-ом месте, а в этом году предварительно на 52-ом, но уж больно медленный этот прогресс. И особенно обидно осознавать, читая описания решений лидеров, что идея то решения была правильной. Как всегда, не хватило каких-то деталей реализации, каких-то мелочей, из которых, конечно же, в нашем деле состоит всё.

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

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

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

Tuesday, July 12, 2011

Метод опорных векторов с примерами на R


Друзья, после продолжительного затишься, я продолжаю тему "data mining с примерами на R". И эта статья посвящается методу опорных векторов (в английской литературе support vector machines или, сокращенно, svm). В интернете масса информации по этому методу и, как всегда, она изобилует сложными математическими формулами, за которыми теряется общая идея. Поэтому я решил написать эту статью без единой формулы, в конце концов, если кому-то нужны формулы, он всегда может обратиться в википедию, скриншот из которой вы можете увидеть слева.


Итак, в чем же состоит идея метода опорных векторов? Давайте, сначала рассмотрим очень простой случай. Предположим, что у нас есть множество точек на плоскости, часть которых относится к классу A, а другая часть к классу B и есть точки класс которых нужно определить. Задачу такого рода, по-научному можно определить как задачу классификации, причем, для её решения, нужен алгоритм обучения "с учителем". Метод опорных векторов как раз подходит именно для таких задач.

Thursday, June 16, 2011

Фильтр Калмана, руководство полного идиота


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

Итак, что же такое фильтр Калмана. Предположим у вас есть некая величина X изменяющаяся во времени. У вас есть некий прибор, измеряющий величину X с некоторой погрешностью. Вы хотите оценить, чему на самом деле равна величина X. Например, в случае акций Xk может быть истинной ценой акции в момент времени k (она определяется доходами компании, курсом валют, ожиданиями инвесторов итп). У нас есть только один способ измерить эту величину - пойти на биржу и посмотреть котировки. Но котировки на бирже подвержены влиянию массы факторов, цены колеблются в широком диапазоне и измеренная величина Yk может отличаться от Xx. Алгоритм Калмана позволяет нам оценить истинное значение Xk на основе истории изменения измеряемой величины Yk. При этом, естественно, делается ряд предположений о связи между Y и X и характере изменения X.

Friday, May 20, 2011

Пока Дойче. Привет Одноклассники!

Как некоторым уже стало известно, затишье в моем блоге последние пару недель было вызвано сменой работы. Да, отныне я более не сотрудник Deutsche Bank, а работаю в компании Одноклассники.ру Это, однако, отнюдь не значит что финансовые рынки мне более не интересны. Скорее наоборот, теперь я освобожден от ограничений, накладываемых контрактом инвестиционного банка и могу свободно спекулировать на бирже. Так что, рассчитываю в ближайшее время опробовать на практике множество идей, о которых пока мог только теоретизировать. Надеюсь так же, что и для графоманской деятельности вскоре найдется время и мой блог пополнится множеством новых замечательных статей.


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


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

Tuesday, May 3, 2011

Кластерный анализ с примерами на R

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

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

А что если у нас есть просто данные и мы ничего не знаем об их структуре? Но зато, у каждого элемента данных есть набор характеристик (например, если речь идет о людях: возраст, пол, образование итп). Так вот, задача кластерного анализа состоит в том, чтобы разбить объекты на группы (кластеры) так чтобы объекты в каждой группе были некоторым образом похожи. Тем самым раскрывается внутренняя структура данных. При этом, нам не требуются тренировочные данные. Такой подход носит название обучения без учителя.

Thursday, April 14, 2011

Метод поиска ближайшего соседа с примерами на R

Продолжаю серию про методы и алгоритмы data mining. Предыдущая статья была посвящена линейной регрессии. Эта статья посвящается методу ближайшего соседа. Идея метода ближайшего соседа очень проста на интуитивном уровне. Давайте рассмотрим простой пример.

Простой пример метода ближайшего соседа

Рассмотрим зарплаты людей, в зависимости от их места жительства. Предположим, что вы живете в центре Москвы и что рядом с вами живут люди с зарплатой 150 тыс рублей. Тогда, можно предположить, что и ваша зарплата близка к этой величине. Если же вы проживаете в поселке Новотагилка Челябинской области, и зарплаты ваших соседей около 3 тыс рублей, то скорее всего и ваша зарплата близка к 3 тыс рублей. Естественно, что здесь нет 100% точной зависимости, кто-то может жить в центре Москвы на пенсию в 6 тыс, а кто-то в Новотагилке зарабатывать 50. Но если единственная информация, которой мы владеем о человеке является его место жительства, то мы можем воспользоваться приведенным подходом для оценки его доходов.

Аналогично работает и метод ближайшего соседа

Tuesday, April 12, 2011

Линейная регрессия с примерами на R

Меня давно преследует идея пройтись по ключевым алгоритмам data mining, систематизировать имеющиеся знания и составить некий обзор с примерами. Собственно, этой статьей и ознаменую начало данной серии. Для примеров будет использоваться программная среда R, о которой я уже писал в одном из прошлых постов.

Линейная регрессия

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

Friday, April 1, 2011

Зачем Goldman Sachs крысы или генетические алгоритмы и нейронные сети на службе в инвестиционных банках

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

Monday, March 21, 2011

[К черту мифы] Тестирование стратегии RSI на R.

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

Одной из самых известных стратегий является стратегия торговли основанная на RSI (Relative Strength Index, индекс относительной силы). Она популярна, в первую очередь, в силу своей простоты. Хотя, существует множество вариантов этой стратегии, все они примерно сходятся в одном: покупаем, когда RSI ниже либо пробивает снизу определенное значение и продаем, когда RSI выше определенного значения либо пробивает его сверху. Кроме того, в этой стратегии учаcтвует такой параметр как период, это количество временных интервалов, на которых расчитывается RSI. Моей целью стала проверка прибыльности этой стратегии для различных значений параметров.

Thursday, March 17, 2011

Язык программирования R для биржевого спекулянта


Эта статья посвящена азам работы с R. Если Вы уже знакомы с R она вряд ли Вам будет интересна, если же Вы смутно себе представляете, что это такое и хотите узнать побольше welcome.
Итак, R это де-факто стандарт в области статистической обработки данных. Это одновременно язык программирования и программная среда вычислений с расширенными графическими возможностями. Для многих задач, этот инструмент успешно заменит Excel и Mathcad. И самое важное, R распространяется свободно и бесплатно.

Monday, March 14, 2011

Поиск границ на цветных изображениях

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

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


Friday, March 11, 2011

Можно ли победить случайное блуждание?

Наткнулся на статью (eng) В громком заголовке автор утверждает, что его простая стратегия способна приносить прибыль, если котировки движутся по законам случайного блуждания. И в доказательство приводит красивый пример, написанный на R.


Monday, February 21, 2011

Распознавание образов на Topcoder, мой подход

Topcoder NASA Tournament Lab Marathon Match подошел к концу и ваш покорный слуга, все-таки, принял в нем участие. О чем ничуть не жалею. Напомню, что задача состояла в том, чтобы определить есть ли на данном изображении какое-либо транспортное средство. Изображения представляли собой нарезанные кусочки аэро-фотоснимков, максимального размера 150х150 пикселей. Под транспортным средством авторы могли подразумевать все что угодно, от газонокосилки до фуры. На одной из картинок, как мне кажется, я нашел даже детскую коляску. Иногда на фотографии торчал лишь кусочек капота автомобиля. Кроме того, качество изображения оставляло желать лучшего, далеко не всегда даже человек мог определить, есть ли на этом изображении транспортное средство или нет. Например тут, я до сих пор не уверен, что это

Tuesday, February 15, 2011

Вопросы с собеседований Google, часть 5

Доброго времени суток, уважаемый читатель.
В связи с проходящим, в данный момент, NASA Tournament Lab Marathon Match я немного забросил свой блог. Но это временно и, чтобы как-то прервать затянувшееся молчание, публикую очередную серию вопроссов с собеседований.

Итак, вопросы:

21) Вам дан 1 миллион целых чисел, как их эффективно отсортировать?
22) В чем разница между static, final и const в Java? (Если вы не знаете Java, аналогичный вопрос будет задан по другому языку программирования)
23) Расскажите о каком-нибудь вашем университетском или рабочем проекте. Как бы вы могли его сделать более эффективным с точки зрения алгоритмов.

Thursday, February 3, 2011

Распознавание образов на topcoder

На топкодере стартовал очередной марафон матч. В этот раз под эгидой NASA и с призовым фондом в $10k. Этот матч показался мне интересным сам по себе, во-первых, потому, что я давно мечтал увидеть на марафоне задачу из реального мира. А во-вторых, потому что это задача из области искуственного интеллекта.

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

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

Monday, January 24, 2011

Причинный тест Грейнджера на Java

В своей прошлой заметке о причинном тесте Грейнджера я рассказал о его применении к данным о производстве яиц и количестве куриц-несушек в США. Я не рассказал, однако, как же этот тест провести самостоятельно и что значат все эти числа в таблицах. Рассказываю.

Вероятностный тест Грейнджера поддерживается многими статистическими пакетами, такими как Stata или R, но гораздо интересней написать реализацию самому. Заодно это поможет разобраться в тонкостях и деталях. Будем реализовывать его на Java. Для этого нам понадобиться Apache Commons Math в ней мы найдем реализации F-статистики и линейной регрессии.

Thursday, January 20, 2011

Курицы, яйца, причинный тест Грейнджера или кто был первым?

Что было раньше, яйцо или курица?

Несмотря на продолжающиеся и по сей день дебаты, ответ на этот вопрос был дан математиками Вальтером Турманом и Марком Фишером в 1988 году. В своей работе "Chickens, Eggs, and Causality, or Which Came First?" используя аппарат математической статистики, они доказали, что яйца были раньше. Однако с тех пор прошло больше 20 лет, курицы и яйца могли измениться...

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

Friday, January 14, 2011

Потокобезопасный DateFormat


"Является ли класс java.text.DateFormat потокобезопасным?". Этот вопрос очень любят задавать на собеседовании по вакансии Java-программиста. Ответ на него – нет, не является. Вот только собеседующие редко спрашивают, а как же тогда быть, если все-таки надо его использовать в многопоточном окружении? Меж тем в реальных задачах мало просто знать, что DateFormat не потокобезопасен, было бы неплохо еще и знать способ решения проблемы. Сравнению возможных вариантов решения и посвящен данный пост.

Элементарная логика и гугл родили на свет пять вариантов:
  1. Вариант решения "в лоб". Если DateFormat не потокобезопасный, давайте синхронизируем доступ. А чтобы не писать каждый раз блокировки вручную, воспользуемся паттерном декоратор. В точности, как это реализовано в Collections.synchronizedList() итп.
  2. Другой, не менее очевидный вариант, давайте создавать новый экземпляр класса на каждый вызов.
  3. Воспользуемся ThreadLocal для хранения одного экземпляра DateFormat на поток.
  4. Воспользуемся классом из Apache Commons Lang FastDateFormat. Пожалуй основной недостаток этого варианта – он не поддерживает парсинг даты. Поддерживается только печать.
  5. И последний, но отнюдь не маловажный вариант – использовать библиотечку joda-time. К сожалению, эта библиотека поддерживает не все паттерны стандартного DateFormat поэтому, не во всех случаях будет возможно на нее переключиться.

Что ж, пусть тест производительности решит вопрос и расставит наконец точки над i. В данном тесте проверялась скорость выполнения 100000 операций парсинга и форматирования в каждом из 20 потоков работающих одновременно.Тест запускался дважды, первый раз с ключиком JVM -server, второй раз с ключем -client.

Полный исходный код теста можно скачать здесь. Для запуска, так же, понадобятся последние версии библиотек Apache Commons Lang и Joda Time.

Результаты получились следующие:
Время парсинга (мс)Время форматирования (мс)
Synchronized java.text.DateFormat12110 / 137513484 / 2766
New instance per call java.text.DateFormat4954 / 102343750 / 7546
ThreadLocal java.text.DateFormat1703 / 2687766 / 656
Apache FastDateFormatНе поддерживается1500 / 2297
Joda Time1062 / 1828500 / 1047
SimpleDateFormat*1697 / 2656758 / 652
* - это референсное время, в данном случае мы эффективно используем DateFormat в однопоточном режиме. Создав для каждого из 20 потоков собственный экземпляр класса SimpleDateFormat.

Первое время - это время выполнения для серверной JVM, второе время - для клиентской JVM.


В случае серверной версии, с впечатляющим отрывом лидирует Joda  Time,  обгоняя даже референсную версию от SimpleDateFormat.

В случае форматирования при клиентской версии JVM в отрыв уходит версия с ThreadLocal.

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