2026-02-24
004 方向予測の強化(done)— 8-9シグナル統合投票方式を導入
005 方向予測パラメータ最適化(done)— Optuna最適化でpred_weight/閾値を調整
007 15M/H精度向上(done)— シグナル重み・バイアスを再最適化
008 方向予測ロジック再設計 ← このissue
30M足 2/23 18:30 +2本先の予測で確認:
予測Open: $66,405
予測Close: $66,303 ← base_priceより下 = 価格は下落予測
方向予測: ▲(上昇)← 矛盾
現在の方向決定ロジック(predict_direction):
1. pred(価格予測)を含む8-9シグナルがそれぞれ buy/sell を投票
2. 加重投票の多数決で方向を決定
3. predは1票に過ぎず、他シグナルに覆される
30M足の設定(007で最適化済み):
シグナル重み:
pred: 2 ← 価格予測(この場合 sell)
daily_trend: 3 ← 最大重み
stoch_level: 2
macd_cross: 2
trend_4h: 1
バイアス: +2(常に買い側に加算)
buy sell
bias +2
pred +2 (価格は下落予測)
daily_trend +3 (日足トレンドが上昇)
─────────────────────────
合計 5 2 → 方向 = ▲(買い)
bias(+2)だけでpredのsell(2)を相殺。daily_trendが1つ買いなら確定で覆る。
現行設計では「価格予測」と「シグナル方向」を1つの ▲▼ に混ぜている。 2つの異なる情報を1つに潰すため矛盾が生じる。
解決: 価格予測とシグナル方向を別の情報として分離し、 それぞれ独立に表示・評価する。
| 情報 | 意味 | 表示 | 評価 |
|---|---|---|---|
| 価格予測 | モデルが予測した OHLC 数値 | $金額 | 乖離率(%) |
| シグナル方向 | テクニカル指標が示す方向感 | ↗ ↘ → | 的中/外れ |
シグナル 実績
+1 (19:00) ↗ 72% ↗ 的中
+2 (19:30) ↗ 39% ↘ 外れ ← 予測Closeは下落だがシグナルは強気
トレーダーの解釈: - 「予測価格は微小下落(-0.15%)だが、テクニカルは強気寄り」 - 「上昇トレンド中の一時的な押し目の可能性」
1. シグナル方向 = テクニカル指標の加重投票で決定(predシグナルを除外)
2. 信頼度 = シグナル一致度 + 変化率の合成スコア(0-100)
3. 価格予測(OHLC)はシグナル方向と独立
4. 的中/外れの判定は「シグナル方向 vs 実績方向」で行う
| ルール | 内容 |
|---|---|
| R1-1 | シグナル方向はテクニカル指標の加重投票で決定する |
| R1-2 | predシグナル(価格予測の方向)は投票から除外する |
| R1-3 | 優勢率 = max(buy, sell) / (buy + sell) で判定 |
| R1-4 | 優勢率 ≥ 65% → 優勢側の方向(↗ or ↘) |
| R1-5 | 優勢率 < 65% → レンジ(→) |
| R1-6 | バイアス補正(direction_bias_*)は廃止する |
| ルール | 内容 |
|---|---|
| R2-1 | 優勢率 < 65% → レンジ(→)(シグナル拮抗) |
| R2-2 | 有効シグナルがゼロ(全て none)→ レンジ(→) |
| R2-3 | レンジ時も信頼度は算出する |
| R2-4 | 65%閾値はTF別にconfig定義(direction_range_threshold_{TF})、Optuna最適化対象 |
30M足の有効シグナル(pred除外):
stoch_level(2), macd_cross(2), trend_4h(1), daily_trend(3)
合計最大 = 8
buy=5, sell=3 → 優勢率 62.5% < 65% → → レンジ
buy=6, sell=2 → 優勢率 75.0% ≥ 65% → ↗ 強気
buy=1, sell=7 → 優勢率 87.5% ≥ 65% → ↘ 弱気
buy=0, sell=0 → シグナルなし → → レンジ
| ルール | 内容 |
|---|---|
| R3-1 | 信頼度 = signal_score + strength_score(上限100) |
| R3-2 | signal_score = agreement_ratio * signal_ratio |
| R3-3 | agreement_ratio = agree / total(predを除外した加重一致率) |
| R3-4 | strength_score = min(strength_ratio, abs(change_pct) * strength_ratio) |
| R3-5 | signal_ratio + strength_ratio = 100(TF別にconfig定義、例: 70 + 30) |
| R3-6 | total=0(有効シグナルなし)の場合: agreement_ratio = 0.5(中立) |
| R3-7 | direction=0(レンジ)の場合: agree=0(どの方向とも一致しない)→ agreement_ratio=0/total |
計算式(現行_calculate_continuous_confidenceを踏襲、pred除外のみ変更):
signal_ratio = config('direction_confidence_signal_ratio', TF, 70)
strength_ratio = 100 - signal_ratio
total = 0; agree = 0
for key, sig in conditions.items():
if key == 'pred': # ← 008で追加
continue # ← 008で追加
if sig['signal'] in ('buy', 'sell'):
weight = signal_weights[key]
total += weight
if (sig['signal'] == 'buy' and direction == 1) or \
(sig['signal'] == 'sell' and direction == -1):
agree += weight
agreement_ratio = agree / total if total > 0 else 0.5
signal_score = agreement_ratio * signal_ratio
strength_score = min(strength_ratio, abs(change_pct) * strength_ratio)
confidence = min(100, signal_score + strength_score)
| ルール | 内容 |
|---|---|
| R4-1 | 既存のシグナル評価関数はそのまま維持する(レベル/クロス/TF固有) |
| R4-2 | シグナル重み(config)もそのまま維持する |
| R4-3 | predシグナルは conditionsに記録するが投票には参加しない(バッジ表示用に残す) |
| R4-4 | conditionsに _summary を追加し、投票集計結果を保存する |
| R4-5 | _summary には buy_count, sell_count, total, dominance, threshold, result を含める |
| R4-6 | これによりconfigなしでJSON単体から優勢率・判定結果を再現できる |
| ルール | 内容 |
|---|---|
| R5-1 | 的中 = シグナル方向と実績方向が一致 |
| R5-2 | 実績方向は determine_direction(actual_OHLC, base_price=actual_open, atr) で判定 |
| R5-3 | シグナル=↗(1)、実績=1(陽線) → 的中 |
| R5-4 | シグナル=↗(1)、実績=-1(陰線) → 外れ |
| R5-5 | シグナル=→(0)、実績=0(横ばい) → 的中 |
| R5-6 | シグナル=→(0)、実績=1 or -1 → 外れ |
determine_direction)def determine_direction(open, high, low, close, base_price=None, atr=None):
"""ローソク足の方向を判定(予測・実績共通)"""
if base_price is None:
base_price = open
# 横ばい判定: レンジが小さく変化率も小さい
if atr is not None and atr > 0:
range_ratio = (high - low) / atr
change_pct = abs(close - base_price) / base_price * 100
if range_ratio < SIDEWAYS_RANGE_RATIO and change_pct < SIDEWAYS_CHANGE_PCT:
return 0 # 横ばい
# 陽線/陰線
if close > base_price: return 1 # 上昇
elif close < base_price: return -1 # 下落
else: return 0 # 変わらず
実績確定時の呼び出し(confirm_predictions.py):
actual_direction = determine_direction(
actual_open, actual_high, actual_low, actual_close,
base_price=actual_open, # 実績は始値基準(ローソク足の陰陽)
atr=atr
)
is_direction_hit = (direction == actual_direction)
# direction = DB保存済みのシグナル方向
変更不要 — 既存の determine_direction + confirm_predictions がそのまま使える。
| ルール | 内容 |
|---|---|
| R6-1 | シグナル予測列のみ ▲▼ → ↗↘→ に変更する |
| R6-2 | 実績方向列は ▲▼ のまま維持する(上昇/下落 = 陽線/陰線) |
| R6-3 | 目付列のアイコンも ▲▼ のまま維持する |
| R6-4 | ヘッダ列名: 「方向予測」→「シグナル」に変更。「方向実績」「目付実績」等は変更しない |
| R6-5 | tooltip凡例のみ更新: 方向予測列「↗=強気 ↘=弱気 →=レンジ」 |
| R6-6 | 色: ↗ = 緑系(text-success)、↘ = 赤系(text-danger)、→ = 黄色(text-warning) |
| ルール | 内容 |
|---|---|
| R7-1 | シグナル方向はstep +1と同じ指標値から算出(実績値は更新されないため) |
| R7-2 | 変化率ボーナスのchange_pctはstep固有の予測値を使う |
| R7-3 | 的中判定のbase_priceは常にlatest_close(予測起点の実績終値) |
BtcPredictionDetail の既存フィールドをそのまま使用:
| フィールド | 型 | 現在の意味 | 008後の意味 |
|---|---|---|---|
direction |
SmallInteger | 価格予測の方向 (1/-1/0) | シグナル方向 (1=↗/-1=↘/0=→) |
direction_confidence |
Decimal | 信頼度 (0-100) | 同じ(算出ロジックのみ変更) |
direction_conditions |
JSON | シグナル詳細 | 同じ(predシグナルも含む) |
is_direction_hit |
Boolean | 方向的中 | シグナル方向 vs 実績方向 |
predicted_target |
Decimal | 目付価格 | 同じ |
{
"pred": {"value": -0.15, "signal": "sell", "desc": "pred -0.15%"},
"rsi_level": {"value": 42.1, "signal": "none", "desc": "RSI=42"},
"stoch_level": {"value": 18.3, "signal": "buy", "desc": "Stoch=18<25"},
"macd_hist": {"value": 0.003, "signal": "buy", "desc": "MACD hist>0"},
"stoch_cross": {"value": 22.1, "signal": "buy", "desc": "Stoch GC"},
"macd_cross": {"value": null, "signal": "none", "desc": ""},
"daily_trend": {"value": 1.2, "signal": "buy", "desc": "Daily uptrend"},
"trend_4h": {"value": -1, "signal": "sell", "desc": "4H downtrend"},
"_summary": {
"buy_count": 5,
"sell_count": 3,
"total": 8,
"dominance": 0.625,
"threshold": 0.65,
"result": "range"
}
}
value, signal, desc)pred: conditionsに記録するが投票には不参加(バッジ表示用)_summary: 投票結果の集約(configなしで優勢率・判定結果を再現可能)buy_count / sell_count: 加重投票の合計dominance: 優勢率 (0.0-1.0)threshold: 使用した閾値result: 判定結果 ("up", "down", "range")predict_ohlc.py
→ predict_direction() ← ロジック変更ここだけ
→ return (direction, confidence, conditions) ← conditionsに_summary含む
→ _save_detail_record(direction, confidence, conditions)
→ BtcPredictionDetail に保存
# 実績方向: actual_close vs actual_open(ローソク足の陰陽)
actual_direction = determine_direction(
actual_open, actual_high, actual_low, actual_close,
base_price=actual_open, atr=atr
)
# 的中 = 保存済みのdirection(=シグナル方向) == actual_direction
is_direction_hit = (direction == actual_direction)
direction の中身がシグナル方向に変わるだけで、比較ロジックは同一。
# models.py — help_textの更新のみ(動作に影響なし)
direction = models.SmallIntegerField(
"シグナル方向", null=True, blank=True,
help_text="1=強気↗, -1=弱気↘, 0=レンジ→"
)
is_direction_hit = models.BooleanField(
"シグナル的中", null=True, blank=True
)
predict_direction() in direction_prediction.pyBefore(pred含む投票 → 方向決定):
# predシグナルも投票に参加
for key, c in conditions.items():
sig = c.get('signal')
weight = signal_weights.get(key, 1)
if sig == 'buy':
buy_count += weight
elif sig == 'sell':
sell_count += weight
# 多数決で方向決定
if buy_count > sell_count:
direction = 1
After(pred除外投票 + 優勢率閾値 → シグナル方向決定):
# predシグナルは投票から除外(conditionsには残す)
for key, c in conditions.items():
if key == 'pred':
continue # 投票に参加しない
sig = c.get('signal')
weight = signal_weights.get(key, 1)
if sig == 'buy':
buy_count += weight
elif sig == 'sell':
sell_count += weight
# 優勢率でシグナル方向を決定
total = buy_count + sell_count
range_threshold = _get_dir_config('direction_range_threshold', timeframe, 0.65)
if total == 0:
direction = 0 # → シグナルなし
elif max(buy_count, sell_count) / total >= range_threshold:
direction = 1 if buy_count > sell_count else -1 # ↗ or ↘
else:
direction = 0 # → レンジ(拮抗)
| 項目 | 理由 |
|---|---|
direction_bias_{TF} (config) |
構造的買い/売りバイアスは除去 |
| 投票のbias初期値加算 | 同上 |
| predシグナルの投票参加 | 価格予測とシグナルを分離するため |
| 項目 | 理由 |
|---|---|
| シグナル評価関数(evaluate*) | 信頼度計算とバッジ表示に引き続き必要 |
| predシグナルのconditions登録 | バッジ表示用(「pred: -0.15%」の表示) |
| シグナル重み(config) | 投票の重み付けに使用(pred以外) |
_calculate_continuous_confidence |
微修正のみ(pred除外した同意率計算) |
calculate_target |
シグナル方向=targetの方向で自然に対応するため変更不要 |
| 横ばい判定の閾値(config) | sideways判定はR2に従い簡略化 |
| ファイル | 変更箇所 | 変更内容 |
|---|---|---|
prediction_ohlc.html |
p{N}_direction セル(方向予測列) |
▲▼ → ↗↘→ |
prediction_ohlc.html |
p{N}_actual_direction セル(方向実績列) |
変更しない(▲▼維持) |
prediction_ohlc.html |
目付列のアイコン | 変更しない(▲▼維持) |
prediction_ohlc.html |
ヘッダ列名「方向予測」 | 「シグナル」に変更(「方向実績」等は維持) |
prediction_ohlc.html |
方向予測列のtooltip | 凡例を「↗=強気 ↘=弱気 →=レンジ」に更新 |
prediction_detail.html |
row.direction セル |
▲▼ → ↗↘→ |
| 項目 | 内容 |
|---|---|
| 的中率 | 変化する。predを除外するため、純粋なテクニカル指標の的中率になる |
| 信頼度 | 意味が明確化:「テクニカル指標の合意度」 |
| 価格予測との矛盾 | 構造的に解消(別情報として扱うため) |
| バッジ表示 | 変更不要(conditions構造は同じ) |
| config.py | 下記4行削除: direction_bias_D(-1), direction_bias_30M(2), direction_bias_H(0), direction_bias_15M(-2)。下記4行追加: direction_range_threshold_D(0.65), _30M(0.65), _H(0.65), _15M(0.65) |
3モデル(XGBoost, LightGBM, CatBoost)の予測ばらつきから算出する一致度スコアの感度を上げる。
| ルール | 内容 |
|---|---|
| R8-1 | ratio^2 → ratio^4 に変更(高止まり防止の強化) |
| R8-2 | ratio = 1 - min(model_std / atr_pct, 1.0) は変更なし |
| R8-3 | score = ratio^4 * 100(0-100にクリップ) |
変更理由: ratio^2 ではスコアが高止まりしやすく、3モデルのばらつきの差が十分に反映されない。4乗にすることで、ばらつきが大きいケースのスコアがより明確に低下する。
感度比較(ratio値 → スコア):
ratio ratio^2 ratio^4
1.00 100 100 ← 完全一致
0.95 90 81
0.90 81 66
0.80 64 41 ← 差が顕著
0.70 49 24
0.50 25 6
0.30 9 1
対象ファイル: crypto/management/commands/predict_ohlc.py L166
274件の実績データから分析した3モデルの特性:
| 特性 | XGBoost | LightGBM | CatBoost |
|---|---|---|---|
| バイアス | +0.0006(中立) | +0.0086(やや強気) | -0.0125(弱気) |
| 方向精度 | 56.2% | 56.9%(最高) | 53.6% |
| 予測振幅(Stdev) | 0.1139% | 0.1225%(最大・大胆) | 0.0858%(最小・保守的) |
| 実績との相関 | +0.028 | +0.025 | -0.084(負の相関) |
| 急変動時MagRatio | 0.078 | 0.081 | 0.063 |
| 不一致時正答率 | 52.5% | 55.9% | 40.7% |
主要な知見: 1. LightGBMが総合的に最も優秀 — 方向精度・急変動追従・不一致時の信頼度すべてでリード 2. CatBoostに問題あり — 実績と負の相関、最低の方向精度、不一致時に間違いやすい 3. 3モデルとも急変動に追従できない — 急変動(上位25%)の6-8%しか捉えられない(平均回帰型予測の限界) 4. 3モデル一致率78.5% — 一致時の方向精度57.2%
TF別の急変動方向精度: - 15M: 47%(ランダム以下)→ 平常時は65% - 30M: 61-63%(バランス良好) - H: LGBM 77.8%(方向は優秀だが振幅をほぼ無視)
tooltip表示案: 一致度カラムのtooltipに「XGB/LGBM/CAT」の各予測値を表示。 CatBoostの負の相関や保守性をユーザーが判断材料にできるようにする。
現行モデルは急変動の「大きさ」を捉えるメカニズムを持たない。 別途アプローチの検討が必要(ボラティリティ予測の分離、急変動フラグ等)。
predict_ohlc -t 30M --backtest --periods 300 --step 1 --save_summaryのtotalにpredの重みが含まれない)_summary がconditions JSONに含まれていることを確認prediction_ohlc.html: シグナル予測列が ↗↘→ で表示されるprediction_ohlc.html: 実績方向列が ▲▼ のままprediction_ohlc.html: 目付列のアイコンが ▲▼ のままprediction_ohlc.html: ヘッダ「方向予測」→「シグナル」に変更されているprediction_ohlc.html: ヘッダ「方向実績」「目付実績」等は変わっていないprediction_ohlc.html: 方向予測列のtooltipが ↗↘→ の凡例になっているprediction_detail.html: シグナル方向が ↗↘→ で表示される| ファイル | 変更内容 |
|---|---|
_predict_ohlc/direction_prediction.py |
pred除外投票、bias廃止 |
_predict_ohlc/config.py |
direction_bias_* 削除、direction_range_threshold 追加(0.65) |
templates/crypto/prediction_ohlc.html |
シグナル予測列のみ ▲▼→↗↘→、tooltip凡例更新 |
templates/crypto/prediction_detail.html |
シグナル予測セルのみ ▲▼→↗↘→ |
crypto/views.py |
direction_label 更新(L388, L740の2箇所。現在テンプレート未使用だが意味を合わせる) |
confirm_predictions.py |
actual_direction の判定ロジック確認 |
tool_optimize_direction.py |
bias/pred_weight削除、range_threshold追加 |