何をやったのか
為替の日足について、窓を開けた場合、その日の足がトレンド方向に向かって伸びるのか逆になるのかを、窓の方向ごとに確率を割り出してその差をt検定してみた。
補足
為替の世界では、金曜日にマーケットが閉まり、月曜日に開くので基本的に金曜日の最後のレートで来週は始まります。しかし優遇制度などによって一部の顧客は土日でも注文を通らしてもらえますので、その影響によって金曜日の最終レートと月曜日の最初のレートに乖離が生じます。これを窓と呼びます。
通常、窓が上に開いた場合(金曜日ドル円最終レート108.02円→月曜日最初のレート108.24円)だった場合、円を売る要因が合ったのかと市場がざわついて一旦短期間ですが、窓の方向にレートが動きやすいってのがセオリーです。
なので本来は、窓を開けた日の始値から5時間後ぐらいのところで、レートが窓の方向に進んだかを判断してみたいのですが、如何せん1時間足の大量の学習データを用意することができず、18年分の日足データしかすぐには手に入りませんでしたので、止むを得ず、窓を開けた日が、上の方向に開けたのならその日足が陽線になったのか、下の方向に開けたのなら陰線になったのか、それぞれの確率を調べ、どちらの方がトレンド方向へ進みやすいのか統計検定をしてみました。
コード
import pandas as pd
import time
import scipy.stats as stats
import math
old_df = pd.read_csv("FX/usd_jpy_18y.csv")
old_df.head()

処理しやすいように、英語名にカラムを変え、不要な列を削除します。
df = old_df.rename(columns={ "日付け": "time", "終値": "close", "始値": "open", "高値": "high", "安値": "low" })
df.drop("前日比%", axis=1, inplace=True)
df.head()

ロジックの組み方
- 直前に窓を開けた1日ロウソク足を探す
- 窓を開けた方向のカラムも入れる
- その日足の終値が窓を開けた方向へ動いているかの確率を出す。(この段階ではグループに分けなくて良い)
- 方向ごとにグループ分けして確率を出す
- それらの差が有意かどうかをt検定
続き
今回はあまり関係ないと思いますが、df.iterrowsをすると処理が重いらしいので軽いnp.array型でindexも見つけやすく軽いロジックを心がけてみました。
open = df["open"].values
close = df["close"].values
mado = []
start = time.time()
for index in range(df.shape[0]):
if index < 1 or index == df.shape[0] - 1:
# 両端を端折る
continue
if open[index] < close[index + 1]:
# 窓が下に開く
boolean = 1 if open[index - 1] < open[index] else 0 if open[index - 1] > open[index] else -1
# 同じ方向へ差が開くと1
# 今と価格が同じなら-1
# 窓の方向と反対方向へ行ったら0
mado.append({ "side": 0, "isTrend": boolean })
elif open[index] > close[index + 1]:
# 窓が上に開く
boolean = 1 if open[index - 1] > open[index] else 0 if open[index - 1] < open[index] else -1
mado.append({ "side": 1, "isTrend": boolean })
print(time.time() - start)
# 処理時間 0.011049985885620117
mado_df = pd.DataFrame(mado)
mado_df.head()

sideは1が、上方向に窓が開いとことを、
0が下方向へ窓が開いたことを表します。
isTrendは、上方向へ窓が開いたなら日足が陽線だった場合に1、陰線だった場合に0を。
下方向ならその逆バージョンです。
mado_df.mean()
# side 0.320220
# isTrend 0.465081
temp = mado_df.groupby("side")
temp.count()

過去18年分で、下方向に窓を開いた回数は2229回
上方向への窓は1050回ですね。
やはり土日に注文を出すほどの心理状態にある投資家は、ヤバイ情報を掴んで早めに売り飛ばして決済させたいか、暴落を引き起こしたいゲームメーカー精神があるのでしょうか。
temp.mean()

う〜ん。あまりトレンド方向へ進む確率に両者差はなさそうですね。
しかもどちらもトレンドと逆方向へ進んでいるような気がします。
まずは両者の確率が有意かどうかを調べ、次はデータ数の多いside=0(下に窓を開けたグループ)で、この確率が本来50%だがたまたま47%になったのではないか?ということを検証していきたいと思います。
両者の割合の差をt検定
まず両グループの分散が同じか調べます。
temp.std()

ほぼ差はないようです。本来は不偏分散求めてf値出して、分散が等しいか検定しないといけないのですが、今回はサボります。
2群間の平均値が独立であり(データに対応がない)、2群間に等分散性が仮定できるので、ステューデン検定を使います。
long = mado_df[mado_df["side"] == 1]["isTrend"].values
short = mado_df[mado_df["side"] == 0]["isTrend"].values
stats.ttest_ind(long, short)
# Ttest_indResult(statistic=-1.764819614603135, pvalue=0.0776872217562626)
惜しい!有意水準5%で両グループに有意な差はないという帰無仮説を棄却できず、このトレンドに進む確率の3%の差はたまたまだったということになります。
そもそもトレンドへ進む確率47.6%は意味のある偏りなのかをz検定で検証する
続いて下方向へ窓を開けた場合のトレンドに進む確率が47.6%でしたが、これだけ見ると、窓の方向と逆張りすればワンチャンあるんちゃう?となりそうですが、たまたまなのでしょうか?
データが2000個以上あるし割合の検定なので普通にz検定しちゃいます。
恥ずかしながらライブラリを見つけられなかったので手動で...
p = 0.475998
# 割合の標準誤差を出します。
se = math.sqrt((p * (1 - p)) / 2229)
# 0.010578254271604748
標準化してz値を出します。
z = (p - 0.5) / se
# 2.2689944279774683
z値が、2.27ということで有意水準5%で帰無仮説を棄却、トレンドとは逆方向へなりやすいこの確率には意味がありました。
ちなみに95%信頼空間は
0.4788434914567905 < μ < 0.521156508543209
この間にある割合は、母平均(母集団のトレンドとは逆方向へ動く割合)の推定として棄却できないわけですが、今回0.2%ほどこの信頼空間から飛び出た値を取りました。
正直この程度の差じゃ何とも言えないですね。
やはり1時足を使って4時間後、5時間後レベルでの動きを見てやりたいです。
あとはそれに加えて、窓の開いた大きさによってもグループ分けして重回帰分析で数時間後の方向を次回予想できればと思っています。