TimesFMをDockerで使って、学習ゼロで為替予測する話

前回、TimesFMを使って株価を予測しました。

GitHub - google-research/timesfm: TimesFM (Time Series Foundation Model) is a pretrained time-series foundation model developed by Google Research for time-series forecasting.
TimesFM (Time Series Foundation Model) is a pretrained time-series foundation model developed by Google Research for tim...

うまく予想できてそうな場合もありましたが、結論としては「偶然当たっただけ」ということになりました。

株価には、会社の経営状況や、商品の売れ行き、もしくは他の会社との競争などの要因があります。そのため、株価の未来予測は、根本的に無理があります。

しかし、為替はどうでしょうか?為替は株価ほど大きく変動せず、かつ安定的に保とうとする作用があります。特に日本円なんて、今は円安の状況ですが、二倍の価格変動もしていません。

もしかしたら、短期の為替予測はTimesFMでうまくいくかもしれません。

今回の記事はそれを試していく話となります。

実験 為替USDJPY 日足 短期予測

始めに日足を予測してみたいと思います。

以下jupyter notebookの記録です

まず、timesfmやyfinanceをインポートします。

import timesfm
import yfinance as yf

Appleの株価をダウンロードします。

df = yf.download(tickers='USDJPY=X', interval='1d')
df = df.reset_index().rename(columns={'Date':'ds'})
df['unique_id'] = 'USDJPY'
df

yfinanceが問題なければ、数秒でapple株の株価の記録が取得できます。
1980年からの株価が取れました。

[*********************100%%**********************]  1 of 1 completed
ds	Open	High	Low	Close	Adj Close	Volume	unique_id
0	1996-10-30	114.370003	114.480003	113.610001	114.180000	114.180000	0	USDJPY
1	1996-11-01	113.500000	113.500000	113.500000	113.500000	113.500000	0	USDJPY
2	1996-11-04	113.279999	113.980003	112.949997	113.879997	113.879997	0	USDJPY
3	1996-11-05	113.709999	114.330002	113.449997	114.250000	114.250000	0	USDJPY
4	1996-11-06	114.230003	114.680000	113.650002	113.949997	113.949997	0	USDJPY
...	...	...	...	...	...	...	...	...
7151	2024-05-28	156.845001	156.988998	156.612000	156.845001	156.845001	0	USDJPY
7152	2024-05-29	157.261993	157.639008	157.022003	157.261993	157.261993	0	USDJPY
7153	2024-05-30	157.608002	157.626999	156.427002	157.608002	157.608002	0	USDJPY
7154	2024-05-31	156.953003	157.362000	156.570007	156.953003	156.953003	0	USDJPY
7155	2024-06-02	157.289993	157.289993	157.289993	157.289993	157.289993	0	USDJPY
7156 rows × 8 columns

データが多すぎると感じたため、2015年以前は切り捨てます。また、モデルにいれるのは2024年までのデータとし、それ以降の推移を予測値と実際の値を比較します。

import pandas as pd
df['ds'] = pd.to_datetime(df['ds'])
input_df = df.loc[('2015-01-01' <= df['ds']) & (df['ds'] <= '2023-01-01'), :]
input_df
ds	Open	High	Low	Close	Adj Close	Volume	unique_id
4701	2015-01-01	119.672997	119.672997	119.672997	119.672997	119.672997	0	USDJPY
4702	2015-01-02	119.889999	120.736000	119.835999	119.870003	119.870003	0	USDJPY
4703	2015-01-05	120.389000	120.608002	119.411003	120.433998	120.433998	0	USDJPY
4704	2015-01-06	119.416000	119.497002	118.680000	119.425003	119.425003	0	USDJPY
4705	2015-01-07	118.674004	119.639000	118.674004	118.672997	118.672997	0	USDJPY
...	...	...	...	...	...	...	...	...
7041	2023-12-26	142.229996	142.619995	142.108002	142.229996	142.229996	0	USDJPY
7042	2023-12-27	142.460999	142.832001	141.858002	142.460999	142.460999	0	USDJPY
7043	2023-12-28	141.399002	141.651993	140.289993	141.399002	141.399002	0	USDJPY
7044	2023-12-29	141.429993	141.899002	140.828995	141.429993	141.429993	0	USDJPY
7045	2024-01-01	140.951996	141.024994	140.951996	140.951996	140.951996	0	USDJPY
2345 rows × 8 columns

timesFMのモデルを作ります。入力長は512日、出力長は128日としました

tfm = timesfm.TimesFm(
    context_len=512,
    horizon_len=128,
    input_patch_len=32,
    output_patch_len=128,
    num_layers=20,
    model_dims=1280,
    backend="cpu",
)

huggingfaceからモデルの学習済モデルの重みをロードします

import json
with open('/work/api_keys.json', 'r') as f:
    api_keys = json.load(f)
from huggingface_hub import login
login(token = api_keys['huggingface_hub'])
tfm.load_from_checkpoint(repo_id="google/timesfm-1.0-200m")

timesFMによる予測をします。入力がDataFrameのときは、forecastではなく、forecast_on_dfを利用できます。

forecast_df = tfm.forecast_on_df(
    inputs=input_df,
    freq="D", 
    value_name="Close",
    num_jobs=-1,
)
forecast_df

予測値のDataFrameを見てみます

	unique_id	ds	timesfm	timesfm-q-0.1	timesfm-q-0.2	timesfm-q-0.3	timesfm-q-0.4	timesfm-q-0.5	timesfm-q-0.6	timesfm-q-0.7	timesfm-q-0.8	timesfm-q-0.9
0	USDJPY	2024-01-02	139.699448	138.264938	138.723114	139.118927	139.403473	139.699448	139.914474	140.148087	140.468536	140.901657
1	USDJPY	2024-01-03	139.750626	138.001617	138.596100	139.060806	139.400650	139.750626	140.130905	140.437256	140.855927	141.395813
2	USDJPY	2024-01-04	140.003296	137.883606	138.645615	139.089981	139.559158	140.003296	140.324493	140.706848	141.222366	141.878967
3	USDJPY	2024-01-05	140.207581	137.901703	138.697693	139.260590	139.775192	140.207581	140.630768	141.035553	141.611130	142.399445
4	USDJPY	2024-01-06	140.316437	137.777863	138.693192	139.294937	139.861099	140.316437	140.737320	141.268112	141.832764	142.665268
...	...	...	...	...	...	...	...	...	...	...	...	...
123	USDJPY	2024-05-04	138.750839	122.632362	128.359879	132.266312	135.658493	138.750839	141.742966	145.125763	149.056595	155.137314
124	USDJPY	2024-05-05	138.900269	122.591347	128.296616	132.273300	135.731461	138.900269	141.860809	145.146225	149.249054	155.156143
125	USDJPY	2024-05-06	138.500259	122.193253	127.977875	131.981628	135.343018	138.500259	141.619919	144.953537	149.095169	155.203766
126	USDJPY	2024-05-07	138.503845	121.868309	127.951424	131.876114	135.333908	138.503845	141.590317	144.940369	149.085037	155.336700
127	USDJPY	2024-05-08	138.667587	121.826683	127.838326	131.914093	135.362442	138.667587	141.789581	145.061554	149.211197	155.500488
128 rows × 12 columns

予測結果を描画してみます

import matplotlib.pyplot as plt

plot_df = df.loc[df['ds'] > '2022-01-01']
plt.plot(plot_df['ds'], plot_df['Close'], label='raw')
plt.plot(forecast_df['ds'], forecast_df['timesfm'], label='predict')
plt.fill_between(forecast_df['ds'], forecast_df["timesfm-q-0.4"], forecast_df["timesfm-q-0.6"], color='gray', alpha=0.3, label='predict-bounds')

plt.xticks(rotation=30)
plt.legend()

あまりうまく良くできているとは言えませんでした。

実験 為替USDJPY 日足 長期予測

次に長期の予測をやってみます。timesfmのhorizon_lenを512にして予測させてみます。

tfm = timesfm.TimesFm(
    context_len=1024,
    horizon_len=512,
    input_patch_len=32,
    output_patch_len=128,
    num_layers=20,
    model_dims=1280,
    backend="cpu",
)

tfm.load_from_checkpoint(repo_id="google/timesfm-1.0-200m")

import pandas as pd
df['ds'] = pd.to_datetime(df['ds'])
input_df = df.loc[('2015-01-01' <= df['ds']) & (df['ds'] <= '2023-01-01'), :]
forecast_df = tfm.forecast_on_df(
    inputs=input_df,
    freq="D",
    value_name="Close",
    num_jobs=-1,
)
import matplotlib.pyplot as plt

plot_df = df.loc[df['ds'] > '2022-01-01']
plt.plot(plot_df['ds'], plot_df['Close'], label='raw')
plt.plot(forecast_df['ds'], forecast_df['timesfm'], label='predict')
plt.fill_between(forecast_df['ds'], forecast_df["timesfm-q-0.4"], forecast_df["timesfm-q-0.6"], color='gray', alpha=0.3, label='predict-bounds')

plt.xticks(rotation=30)
plt.legend()

これもあまりいい予測ではありませんでした。予測が外れていることもそうですが、途中ピークを表す波形になってしまいました。

実験 為替USDJPY 1時間足 短期予測

1時間足を使った予測をしてみます。まず1時間足をyfinanceを使って取ってきます。yfinanceの仕様で、1時間足は最大で720daysまでしか取得できないので、periodを2yに設定します。

また、timesFMのforecast_on_df関数が、date型には対応していますが、datetime型には対応していなかったため、forecast関数で予測をします。

df = yf.download(tickers='USDJPY=X', interval='1h', period='2y')
df = df.reset_index().rename(columns={'Datetime':'ds'})
df['unique_id'] = 'USDJPY'
tfm = timesfm.TimesFm(
    context_len=512,
    horizon_len=128,
    input_patch_len=32,
    output_patch_len=128,
    num_layers=20,
    model_dims=1280,
    backend="cpu",
)

tfm.load_from_checkpoint(repo_id="google/timesfm-1.0-200m")
import pandas as pd
forecast_input = [
    df.loc[df['ds'] <= '2023-01-01', 'Close'].to_numpy()
]
frequency_input = [0]

point_forecast, experimental_quantile_forecast = tfm.forecast(
    forecast_input,
    freq=frequency_input,
)
import numpy as np
import matplotlib.pyplot as plt

x1 = np.arange(0, len(df))
plt.plot(x1, df['Close'], label='raw')

y2 = point_forecast[0, :]
x2 = np.arange(len(forecast_input[0]), len(forecast_input[0])+len(y2))
plt.plot(x2, y2, label='predict')
plt.legend(loc='lower left')

plt.xlim([len(forecast_input[0])-100, len(forecast_input[0])+len(y2)+100])

plt.xticks(rotation=30)
plt.legend()

全く予測できていません。また、ほとんど予測値が変動していないため、当てにできません。

実験 為替USDJPY 1時間足 長期予測

長期間の予測はどうでしょうか?

df = yf.download(tickers='USDJPY=X', interval='1h', period='2y')
df = df.reset_index().rename(columns={'Datetime':'ds'})
df['unique_id'] = 'USDJPY'
tfm = timesfm.TimesFm(
    context_len=1024,
    horizon_len=512,
    input_patch_len=32,
    output_patch_len=128,
    num_layers=20,
    model_dims=1280,
    backend="cpu",
)

tfm.load_from_checkpoint(repo_id="google/timesfm-1.0-200m")
import pandas as pd
forecast_input = [
    df.loc[df['ds'] <= '2023-01-01', 'Close'].to_numpy()
]
frequency_input = [0]

point_forecast, experimental_quantile_forecast = tfm.forecast(
    forecast_input,
    freq=frequency_input,
)
import numpy as np
import matplotlib.pyplot as plt

x1 = np.arange(0, len(df))
plt.plot(x1, df['Close'], label='raw')

y2 = point_forecast[0, :]
x2 = np.arange(len(forecast_input[0]), len(forecast_input[0])+len(y2))
plt.plot(x2, y2, label='predict')
plt.legend(loc='lower left')

plt.xlim([len(forecast_input[0])-100, len(forecast_input[0])+len(y2)+100])

plt.xticks(rotation=30)
plt.legend()

予測値は外れていますが、先程よりも変動しており、良くはなりました。

実験 為替USDJPY 1分足 長期予測

短期予測はあまりうまく行きそうにないので、長期予測だけ行いました。

df = yf.download(tickers='USDJPY=X', interval='1m')
df = df.reset_index().rename(columns={'Datetime':'ds'})
df['unique_id'] = 'USDJPY'
tfm = timesfm.TimesFm(
    context_len=1024,
    horizon_len=512,
    input_patch_len=32,
    output_patch_len=128,
    num_layers=20,
    model_dims=1280,
    backend="cpu",
)

tfm.load_from_checkpoint(repo_id="google/timesfm-1.0-200m")
import pandas as pd
forecast_input = [
    df.loc[df['ds'] <= '2024-05-30', 'Close'].to_numpy()
]
frequency_input = [0]

point_forecast, experimental_quantile_forecast = tfm.forecast(
    forecast_input,
    freq=frequency_input,
)
import numpy as np
import matplotlib.pyplot as plt

x1 = np.arange(0, len(df))
plt.plot(x1, df['Close'], label='raw')

y2 = point_forecast[0, :]
x2 = np.arange(len(forecast_input[0]), len(forecast_input[0])+len(y2))
plt.plot(x2, y2, label='predict')
plt.legend(loc='lower left')

plt.xlim([len(forecast_input[0])-100, len(forecast_input[0])+len(y2)+100])

plt.xticks(rotation=30)
plt.legend()

ぜんぜん駄目でした。振れ幅がひどすぎて、予測としては使えそうもありません。

まとめ

すべての実験で、予測はうまくいきませんでした。しかし、統計的に予測結果の良し悪しを算出していないので、もしかしたら、有効的に使える期間があるのかもしれません。

現状では、「明日の為替を知りたい」とか「数分後の為替を知りたい」とった用途では利用で来なさそうです。今後はパラメータなどの検証をやる気が湧いたらやります。

タイトルとURLをコピーしました