mcpField
E
evolutionary-metric-ranking

evolutionary-metric-ranking

github.com/terrylicaデータ・データベース2120 回インストール1 日前に更新
リポジトリ

evolutionary-metric-ranking について

evolutionary-metric-rankingは、複数の評価指標で設定を絞り込み、最適化する手法です。パーセンタイル閾値の交差判定と進化的探索を組み合わせ、定量取引の事例も扱います。

主な機能

  1. 01指標ごとのパーセンタイル順位付け
  2. 02指標別に独立した閾値を設定
  3. 03指標の積集合で候補を選別
  4. 04TPEまたはNSGA-IIで閾値を最適化
  5. 05制約指標と選別結果を分析

活用シーン

  1. 01複数指標で戦略やモデルを選別
  2. 02目標候補数に合わせた閾値の探索
  3. 03候補を制約する指標の特定

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/terrylica/cc-skills --skill evolutionary-metric-ranking

インストール後、Claude Code で/evolutionary-metric-rankingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
case-study-rangebar-ranking.md
evolution-log.md
metric-design-guide.md
objective-functions.md

詳細

進化的メトリクスランキング

複数の評価メトリクスにわたって高品質な構成を体系的に絞り込むための方法論です。メトリクスごとのパーセンタイルカットオフ、共通部分に基づくフィルタリング、進化的最適化を使用します。特定の分野に依存しない原則と、定量取引の事例を紹介します。

関連スキル:rangebar-eval-metrics(メトリクスの定義) | adaptive-wfo-epoch(WFO 統合) | backtesting-py-oracle(SQL 検証)


自己進化型スキル:このスキルは使用を通じて改善されます。説明に誤りがある場合、パラメーターがずれている場合、または回避策が必要だった場合は、先送りせず、直ちにこのファイルを修正してください。実際に再現可能な問題に対してのみ更新してください。

このスキルを使用する場面

次の場合にこのスキルを使用してください。

  • 複数の品質メトリクスにわたって構成/戦略/モデルをランキングし、フィルタリングする
  • 最適なサブセットを選ぶ、メトリクスごとのしきい値を探す
  • どのメトリクスが制約として作用し、どのメトリクスが実質的に作用していないかを特定する
  • フィルターパラメーターに対して多目的最適化(Optuna TPE / NSGA-II)を実行する
  • 最適化結果のフォレンジック分析(普遍的な最良候補、特徴の傾向)を実施する
  • プラグイン可能な評価システム向けにメトリクスレジストリを設計する

基本原則

P1 - 生の値ではなくパーセンタイル順位を使用する

生のメトリクス値は互換性のない尺度で表されます(Kelly は [-1,1]、取引回数は [50, 5000]、Omega は [0.8, 2.0])。パーセンタイルランキングを使うと、すべてのメトリクスが [0, 100] に正規化され、メトリクス間の比較が意味のあるものになります。

Rule: scipy.stats.rankdata(method='average') scaled to [0, 100]
      None/NaN/Inf -> percentile 0 (worst)
      "Lower is better" metrics -> negate before ranking (100 = best)

同順位の平均値を使用する理由:同じ値には、その値が占める順位の平均が割り当てられます。これにより、実際に同一の値を持つものが不当に区別されるのを防ぎます。

P2 - メトリクスごとに独立したカットオフを設定する

各メトリクスには、それぞれ独立して調整可能なカットオフを設定します。cutoff=20 は「このフィルターを通過するのは上位 20%の構成のみ」という意味です。これにより、各軸が 1 つの品質次元を制御する、12 次元(または N 次元)の探索空間ができます。

cutoff=100 -> no filter (everything passes)
cutoff=50  -> top 50% survives
cutoff=10  -> top 10% survives (stringent)
cutoff=0   -> nothing passes

一律ではなく独立させる理由:メトリクスによって識別力が異なります。すべてのメトリクスを同じカットオフで一律に厳しくすると、実質的に作用していない次元にフィルタリングの枠を浪費する一方、制約として作用する条件のフィルタリングが不十分になります。

P3 - 共通部分によって複数のメトリクスで優れた結果を実現する

構成が最終フィルターを通過するには、メトリクスごとのカットオフをすべて同時に満たす必要があります。この共通部分の判定により、他の項目でひどい成績を出している単一メトリクスの最良候補が紛れ込むのを防ぎます。

survivors = metric_1_pass AND metric_2_pass AND ... AND metric_N_pass

スコアリングではなく共通部分を使う理由:加重和によるスコアリングでは、メトリクスの不振が見えにくくなります。シャープレシオが 99 パーセンタイルでも、規則性が 1 パーセンタイルの構成は、加重和では高いスコアを得る一方、明らかに不十分です。共通部分を使うことで、すべての次元で最低品質を確保できます。

P4 - フィルターなしで始め、進化的に絞り込む

すべてのカットオフはデフォルトで 100%(フィルターなし)です。オプティマイザーは選択した目的を最もよく満たす組み合わせを見つけるため、カットオフを段階的に厳しくします。これは、最初は厳しく設定してから緩めていく方法とは逆です。

Initial state:  All cutoffs = 100 (1008 configs survive)
After search:   Each cutoff independently tuned (11 configs survive)

フィルターなしで始める理由:最初から厳しく設定すると、別のカットオフの組み合わせなら残ったはずの構成をすぐに除外し、全体最適を見逃すおそれがあります。広い範囲から狭めていく探索は、グローバル最適化の特徴です。

P5 - 複数の目的関数から異なる真実を明らかにする

「品質」を捉えられる単一の目的関数はありません。複数の目的関数を実行し、選択された構成の集合を比較してください。すべての目的関数で選択される構成が、最も堅牢です。

目的関数問い明らかになること
max_survivors_min_cutoff最も厳しいカットオフで、構成数が最大になるか?数量と厳格さの効率的フロンティア
quality_at_target_n上位 N 件の中で品質が最も高いのは?目標とするポートフォリオ規模に最適なカットオフ
tightest_nonempty生存者が 1 件以上残る中で、最も厳しい条件は?唯一の生存者となる、あらゆる指標で最優秀の構成
pareto_efficiency生存者数と厳格さのトレードオフは?パレートフロント全体(NSGA-II)
diversity_rewardカットオフに重複がないか?独立した情報を提供する指標

目的関数間の一貫性:すべての目的関数で選択された構成が、最も妥当性の高い選択です。1 つの目的関数でしか選択されない構成は、その目的関数の偏りによる結果である可能性が高いです。

P6 - 制約となる指標の特定

最適化後に、100% まで緩和すると共通部分が増える制約指標を特定します。制約とならない指標は、すでに十分緩いか、制約指標と完全に相関しています。

For each metric with cutoff < 100:
    Relax this metric to 100, keep others fixed
    If intersection grows: this metric IS binding
    If intersection unchanged: this metric is redundant at current cutoffs

重要な理由:制約指標は、品質フロンティアを実際に制約している要因です。構成の改善には、制約となる側面に注力する必要があります。

P7 - 非活性次元の検出

ある指標が母集団をまったく区別しない場合、その指標は非活性です。最適化前にこれを検出し、次元を削減してください。

If max(metric) == min(metric) across all configs: INERT
If percentile spread < 5 points: NEAR-INERT

対応:非活性指標を探索空間から削除するか、カットオフを恒久的に 100 に設定してください。これらを含めると、最適化の予算が無駄になります。

P8 - フォレンジック事後分析

最適化後にフォレンジック分析を実施し、実行可能な知見を抽出します。

  1. あらゆる指標で最優秀の構成 - すべての目的関数で生存する構成
  2. 特徴の出現頻度 - 生存者に最も多く見られる特徴
  3. 指標が制約となる順序 - カットオフを厳しくするにつれ、指標が制約となる順序
  4. 厳格化曲線 - 共通部分の件数と一律カットオフ(100% -> 5%)の関係
  5. 指標の識別力 - 厳格化の各段階で、最も多くの構成を除外する指標

アーキテクチャパターン

Metric JSONL files (pre-computed)
        |
        v
MetricSpec Registry  <-- Defines name, direction, source, cutoff var
        |
        v
Percentile Ranker    <-- scipy.stats.rankdata, None->0, flip lower-is-better
        |
        v
Per-Metric Cutoff    <-- Each metric independently filtered
        |
        v
Intersection         <-- Configs passing ALL cutoffs
        |
        v
Evolutionary Search  <-- Optuna TPE/NSGA-II tunes cutoffs
        |
        v
Forensic Analysis    <-- Cross-objective consistency, binding metrics
MetricSpec レジストリ

レジストリは、指標定義の唯一の信頼できる情報源です。各エントリは凍結されたデータクラスです。

@dataclass(frozen=True)
class MetricSpec:
    name: str              # Internal key (e.g., "tamrs")
    label: str             # Display label (e.g., "TAMRS")
    higher_is_better: bool # Direction for percentile ranking
    default_cutoff: int    # Default percentile cutoff (100 = no filter)
    source_file: str       # JSONL filename containing raw values
    source_field: str      # Field name in JSONL records

設計原則:新しい指標の追加は、MetricSpec エントリを 1 つ追加するだけです。それ以外のコード変更は不要です。ランキング、カットオフ、共通部分、最適化の仕組みはすべて汎用的です。

環境変数の規約

各指標のカットオフは、名前空間付きの環境変数で制御します。

RBP_RANK_CUT_{METRIC_NAME_UPPER} = integer [0, 100]

これにより、次のことが可能になります。

  • コードを変更せずにシェルレベルで上書き
  • 最適化の出力を次の実行にそのままコピーして貼り付け
  • 環境設定による CI/CD との統合
  • タスクごとの環境変数マップによるタスクランナーとの統合(例:moon タスクの env:)

進化的最適化の設計

サンプラーの選択
シナリオサンプラー理由
単一目的TPE(Tree-Parzen Estimator)ベイズ最適化で、整数値やカテゴリ値を扱え、10~20 次元に適しています
多目的(2 つ以上)NSGA-IIパレートフロンティアの探索に適した、集団ベースの手法

再現性:サンプラーには必ずシードを設定してください(seed=42)。最適化結果は再現可能である必要があります。

探索空間の設計
def suggest_cutoffs(trial):
    cutoffs = {}
    for spec in metric_registry:
        cutoffs[spec.name] = trial.suggest_int(spec.name, 5, 100, step=5)
    return cutoffs

step=5 にする理由:十分な粒度を維持しながら、探索空間を20分の1に縮小できます(各メトリクスの値が100個ではなく20個になります)。12個のメトリクスの場合、20^12 = 4 x 10^15 となり、100^12 = 10^24 より小さくなります。

下限を5にする理由:cutoff=0 では必ず空の共通部分になります。5未満の値は厳しすぎるため、実用上役に立ちません。

データの事前ロード(重要なパフォーマンスパターン)
# Load metric data ONCE, share across all trials
metric_data = load_metric_data(results_dir, metric_registry)

def objective(trial):
    cutoffs = suggest_cutoffs(trial)
    # Pass pre-loaded data - avoids disk I/O per trial
    result = run_ranking_with_cutoffs(cutoffs, metric_data=metric_data)
    return obj_fn(result, cutoffs)

理由:データを事前ロードすると、各試行は約6msで評価されます(NumPy/set のみを使用する処理)。事前ロードしない場合、各試行で約50msのディスクI/Oが発生します。10,000回の試行では、60秒と500秒の差になります。

目的関数のパターン
パターン1 - 比率の最適化
def obj_max_survivors_min_cutoff(result, cutoffs):
    n = result["n_intersection"]
    if n == 0:
        return 0.0
    mean_cutoff = sum(cutoffs.values()) / len(cutoffs)
    return n / mean_cutoff  # More survivors per unit of looseness

使用する場合:効率性のフロンティアを調べるとき。つまり、どれだけフィルタリングすれば、どれだけの品質を得られるかを調べる場合です。

パターン2 - 品質の制約
def obj_quality_at_target_n(result, cutoffs, target_n=10):
    n = result["n_intersection"]
    avg_pct = result["avg_percentile"]
    if n < target_n:
        return avg_pct * (n / target_n)  # Partial credit
    return avg_pct  # Full credit: maximize quality

使用する場合:ポートフォリオの目標規模があり、その中から最も品質の高いサブセットを選びたい場合です。

パターン3 - 最小予算
def obj_tightest_nonempty(result, cutoffs):
    n = result["n_intersection"]
    if n == 0:
        return 0.0
    total_budget = sum(cutoffs.values())
    return max_possible_budget - total_budget  # Lower budget = better

使用する場合:あらゆる点で最も優れた単一の設定を見つける場合です。

パターン4 - 多様性への報酬
def obj_diversity_reward(result, cutoffs):
    n = result["n_intersection"]
    if n == 0:
        return 0.0
    n_binding = result["n_binding_metrics"]
    n_active = sum(1 for v in cutoffs.values() if v < 100)
    if n_active == 0:
        return 0.0
    efficiency = n_binding / n_active
    return n * efficiency

使用する場合:カットオフを厳しくしたとき、冗長なフィルタリングではなく、独立した情報が得られることを確認する場合です。

パターン5 - パレート(多目的)
study = optuna.create_study(
    directions=["maximize", "minimize"],  # max survivors, min cutoff
    sampler=optuna.samplers.NSGAIISampler(seed=42),
)
def objective(trial):
    cutoffs = suggest_cutoffs(trial)
    result = run_ranking_with_cutoffs(cutoffs, metric_data=metric_data)
    return result["n_intersection"], sum(cutoffs.values()) / len(cutoffs)

使用する場合:競合する2つの目的間のトレードオフ全体を確認したい場合です。


フォレンジック分析プロトコル

すべての目的関数を実行した後、次の分析を行います。

ステップ1 - 目的関数を横断した生存セット
For each objective:
    survivors_{objective} = set of configs in final intersection

universal_champions = survivors_1 AND survivors_2 AND ... AND survivors_K

ある設定がすべてのK個の目的関数で生き残る場合、目的関数の選択に対して堅牢です。

ステップ2 - 特徴テーマの抽出

すべての生存設定について、各特徴の出現回数を数えます。

feature_counts = Counter()
for config_id in quality_survivors:
    for feature in config_id.split("__"):
        feature_counts[feature.split("_")[0]] += 1

優勢な特徴から、ランキングシステムが選び出している基盤となる市場のマイクロストラクチャが明らかになります。

ステップ3 - 一律に厳しくする曲線

すべてのメトリクスに同じカットオフを適用し、共通部分のサイズをプロットします。

@100%: 1008 survivors (no filter)
@80%:  502 survivors
@60%:  210 survivors
@40%:   68 survivors
@20%:   12 survivors
@10%:    3 survivors
@5%:     0 survivors

この曲線の形状から、メトリクス空間に自然なクラスターがあるのか、一様に分布しているのかがわかります。

ステップ4 - 制約の適用順序

一律に条件を厳しくし、各段階で最も多くの設定を除外したメトリクス、つまり「最も強力な除外要因」となったメトリクスを特定します。

@90%: <N> survivors | tightest killer: <metric> (-<k>)
@80%: <N> survivors | tightest killer: <metric> (-<k>)
@70%: <N> survivors | tightest killer: <metric> (-<k>)

これにより、制約の優先順位が明らかになります。生存数は、登録されているすべてのメトリクスの共通部分を求めて算出されるため、レジストリにメトリクスが追加または削除されるたびに、この順序は変わります。


実装チェックリスト

この手法を新しい分野に実装する際は、次の項目を確認します。

  1. MetricSpec レジストリを定義する(名前、方向、ソース、デフォルトのカットオフ)
  2. パーセンタイルランキングを実装する(scipy.stats.rankdata)
  3. メトリクスごとのカットオフ適用を実装する
  4. すべてのメトリクスにわたる集合の共通部分を実装する
  5. 各カットオフの環境変数による上書きを追加する
  6. run_ranking_with_cutoffs() API 関数を作成する
  7. 制約メトリクスの検出を追加する
  8. 条件を厳しくする分析関数を作成する
  9. Markdown レポートジェネレーターを作成する
  10. 少なくとも3つの目的関数を備えた Optuna オプティマイザーを追加する
  11. オプティマイザーのパフォーマンス向上のため、メトリクスデータを事前ロードする
  12. 5つの目的関数によるフォレンジック分析を実行する(目的関数ごとに10K回以上の試行)
  13. 汎用チャンピオンを抽出する(目的関数を横断した一貫性)
  14. 効果のない次元を特定する(探索空間から削除する)
  15. 制約の適用順序を記録する
  16. 生存設定に見られる特徴テーマを記録する

アンチパターン

アンチパターン症状対策重大度
加重和スコアリング単一の指標が支配的になり、他の指標が無視されます積集合を使用します(P3)CRITICAL
厳しい条件から開始全体最適を逃し、早期収束します100%から開始し、徐々に厳しくします(P4)HIGH
一律のカットオフのみ効果のない指標を過剰に除外し、制約となる指標を十分に除外できません指標ごとに独立したカットオフを設定します(P2)HIGH
単一の目的関数目的関数の偏りによる成果物です5つ以上の目的関数を実行し、一貫性を確認します(P5)HIGH
生の値を比較スケールに依存し、誤解を招きます常にパーセンタイル順位を使用します(P1)HIGH
効果のない指標を含める最適化の予算を浪費します効果のない次元を検出して削除します(P7)MEDIUM
データの事前読み込みなしオプティマイザーが10倍遅くなります一度だけ事前に読み込み、試行間で共有しますMEDIUM
オプティマイザーのシード未設定結果が再現できません常にサンプラーにシードを設定します(seed=42)MEDIUM
フォレンジック分析の不足洞察のない生の数値になりますフォレンジックプロトコル全体を実行します(P8)MEDIUM

参考資料

トピック参照ファイル
Range Bar のケーススタディcase-study-rangebar-ranking.md
目的関数objective-functions.md
指標設計ガイドmetric-design-guide.md
関連スキル
スキル関連性
rangebar-eval-metricsランキングに使用する指標の定義(TAMRS、Omega、PSR など)
adaptive-wfo-epochランキング可能なウォークフォワード指標
backtesting-py-oracle指標の計算に使用する取引結果を検証します
sharpe-ratio-non-iid-corrections非 IID 補正による PSR と MinTRL
依存関係
pip install scipy numpy optuna>=4.7

TodoWrite タスクテンプレート

テンプレート A - ランキングシステムの実装(新規プロジェクト)
1. [Preflight] Identify all evaluation metrics and their JSONL sources
2. [Preflight] Define MetricSpec registry (name, direction, source_file, source_field)
3. [Execute] Implement percentile_ranks() with scipy.stats.rankdata
4. [Execute] Implement apply_cutoff() and intersection()
5. [Execute] Add env var override for each metric cutoff (RANK_CUT_{NAME})
6. [Execute] Create run_ranking_with_cutoffs() API for optimizer
7. [Execute] Add binding metric detection and tightening analysis
8. [Execute] Write markdown report generator
9. [Verify] Unit tests for all pure functions (14+ tests)
10. [Verify] Run with default cutoffs (100%) - all configs should survive
テンプレート B - 進化的オプティマイザーの追加
1. [Preflight] Verify ranking module has run_ranking_with_cutoffs() API
2. [Preflight] Add optuna>=4.7 dependency
3. [Execute] Implement 5 objective functions
4. [Execute] Create suggest_cutoffs() with step=5 search space
5. [Execute] Pre-load metric data once, share across trials
6. [Execute] Handle pareto_efficiency (NSGA-II) as special case
7. [Execute] Write JSONL output with provenance (git commit, timestamp)
8. [Verify] POC with 10 trials - verify non-trivial cutoffs found
9. [Verify] Full run with 10K trials per objective
テンプレート C - フォレンジック分析
1. [Preflight] Collect optimization results from all 5 objectives
2. [Execute] Extract survivor sets per objective
3. [Execute] Compute cross-objective intersection (universal champions)
4. [Execute] Run uniform tightening analysis (100% -> 5%)
5. [Execute] Identify binding metrics at each tightening step
6. [Execute] Extract feature themes from quality survivors
7. [Execute] Detect inert dimensions (zero discrimination)
8. [Verify] Document findings in structured summary table

変更後のチェックリスト(自己保守)

このスキルを変更した後は、次の項目を確認してください。

  1. 原則 P1〜P8 の内部整合性が保たれている
  2. アンチパターンの表に新たに見つかったパターンが含まれている
  3. references/ 内の参照資料が最新である
  4. ケーススタディに最新の本番環境の結果が反映されている
  5. 実装チェックリストが完全で、順序も適切である
  6. 説明を変更した場合、プラグインの README が更新されている

トラブルシューティング

問題原因解決策
すべてのカットオフが100%に収束する指標がすべて相関している指標の冗長性を確認する(Spearman r > 0.95)
緩やかなカットオフで共通部分がゼロになる1つの指標の分散がほぼゼロ不活性な次元を検出する(P7)
オプティマイザーの処理に時間がかかりすぎる試行ごとにディスク I/O が発生する指標データを事前に読み込む(「パフォーマンス」セクションを参照)
目的関数が異なっても同じ結果になる目的関数の差別化が不十分目的関数の数式が異なるトレードオフを検証していることを確認する
万能な最有力候補の性能が平凡である生き残ることと優れていることは異なる生存率だけでなく、実測値を確認する
実行ごとに結合順序が変わるオプティマイザーにシードが設定されていない必ず seed=42 を使用する
生き残りが多すぎるカットオフが緩すぎるn_trials を増やし、ステップサイズを小さくする
生き残りがゼロであるカットオフが厳しすぎる不活性な指標が次元数を水増ししていないか確認する

実行後の振り返り

このスキルの実行後、タスクを終了する前に振り返ります。

  1. 現在の場所を確認します。 — 編集する前に、この SKILL.md の正式なパスを見つけます(このスキル名で Glob を実行します)。すべての修正はこのファイルとその sibling references/ を対象とし、他のドキュメントは対象にしません。
  2. 何が失敗しましたか? — 原因となった指示を修正します。再発する可能性がある場合は、アンチパターンとして追加します。
  3. 予想以上にうまくいったことは何ですか? — 推奨プラクティスにします。その理由も記録します。
  4. 何が現状とずれていましたか? — 実態と一致しなくなったスクリプト、リファレンス、外部依存関係があれば、今すぐ修正します。
  5. 記録します。 — 変更のたびに、きっかけ、修正内容、根拠を記した進化ログのエントリーを追加します。

先送りしないでください。次回の実行は、あなたが残した状態を引き継ぎます

コメント