Upgrade to Pro — share decks privately, control downloads, hide ads and more …

KagglerがMixSeekを触ってみた

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for Morim Morim
March 25, 2026

 KagglerがMixSeekを触ってみた

第2回 マケデコハッカソン発表会の発表資料

Avatar for Morim

Morim

March 25, 2026
Tweet

Other Decks in Programming

Transcript

  1. 自己紹介 & 本日の発表テーマ プロフィール • 製造業でデータサイエンス職 • Kaggle Expert •

    JQuants API Premiumプランを利用中(機械学 習を用いたトレードを実践) 本日の発表テーマ MixSeek Quants Insightを使って 投資戦略を作ってみた • 6つのLLMチームを競争させ、open2closeの投 資戦略を自動探索 • バックテストによる実用性の検証 • 各モデルの特徴の確認 2 (Mitsuiコンペ: 97th / 1,711チーム)
  2. MixSeek Quant Insightとは: コンペ型AIエージェントでアルファを自動探索 1 データ入力 OHLCV、ファンダメンタルズ、海外 市場、為替等の時系列 データを投入 2

    チーム並列探索 複数のLLMチームが独立にデー タ分析し、シグナル計算ロジック を生成 3 コンペ評価 提出されたシグナルを 秘匿された評価データで スコアリング(リーク防止) 4 反復改善 スコアをフィードバックし、各チー ムがラウンドを重ねて戦略を洗練 主な特徴 • Kaggle Timeseries API方式のリーク防止評価: エージェントは学習データのみアクセス可能。評価はシグナル関数を 秘匿データに適用 • 提出形式は統一されたPython関数: generate_signal() → 実運用にそのまま転用可能 • OSSとして公開(GitHub: mixseek/mixseek-quant-insight) 出所: https://zenn.dev/gamella/articles/a624a7eb2c78f7 3
  3. 実施概要 データ分割 区分 期間 用途 Train 2020/1 ~ 2023/12 Train

    Analyzerの検証用 Valid 2024/1 ~ 2024/12 Sub Creatorの検証用 Test 2025/1 ~ 2026/2 Subの評価用 評価方法 • ターゲット : open2close • 評価指標  : 日ごとの順位相関のSharpe Ratio • コンペ終了後にバックテストも実施 5 (イニシャル500万円、上位5銘柄ロング & 下位 5銘柄ショート) • ユニバースはTOPIX500銘柄。 • データ期間は2020年1月~2026年2月(約6年分)。 • エージェントチームは6チームを設定 • min_rounds = 4 | max_rounds = 10 | timeout = 7,200秒
  4. 使用データ ファイル ソース 主な内容 ohlcv.parquet J-Quants API 調整済み四本値、出来高、売買代金 master.parquet J-Quants

    API 企業名、市場、業種コード、 TOPIX規模区分 fin_summary.parquet J-Quants API EPS/BPS/売上等(四半期→日次forward-fill) sector_index.parquet J-Quants API 33業種別指数の終値 short_ratio.parquet J-Quants API 業種別空売り比率 topix_index.parquet J-Quants API TOPIX四本値 investor_types.parquet J-Quants API 投資部門別売買動向(週次 →日次forward-fill) overseas.parquet yfinance S&P500, NASDAQ, 日経先物, VIX forex.parquet yfinance ドル円・ユーロ円 commodities.parquet yfinance WTI原油・金・銅 calendar.parquet ローカル 曜日・月末フラグ・SQ日等 ※ yfinanceのデータが(更新タイミングや信頼性の観点で)実運用で利用可能かは未確認 6
  5. エージェントチームについて チーム リーダーモデル temperature 役割 Team 1 Gemini 3.1 Pro

    0.8 シグナル探索 Team 2 Gemini 3 Flash 0.8 シグナル探索 Team 3 GPT 5.4 (Chat Completions API) (設定不可) シグナル探索 Team 4 GPT 5.4 (Response API) (設定不可) シグナル探索 Team 5 Claude Opus 4.6 0.8 シグナル探索 Team 6 Claude Sonnet 4.6 0.8 シグナル探索 共通エージェント エージェント モデル temperature 役割 Train Analyzer Gemini 3.1 Flash-Lite 0.1 学習データの分析・コード実行 Submission Creator Gemini 3.1 Flash-Lite 0.0 シグナル関数の生成・提出 7
  6. 各チームの結果 チーム コンペ結果 バックテスト結果 スコア ラウンド数 総リターン Sharpe MaxDD Gemini

    3.1 Pro 0.23 10(完走) +40.78% 0.994 6.36% Gemini 3 Flash 0.21 10(完走) +31.82% 0.693 12.78% Claude Sonnet 4.6 0.18 4(打ち切り) -5.92% -0.071 24.46% Claude Opus 4.6 0.16 1(エラー) +11.35% 0.247 20.42% GPT 5.4 (Res API) 0.13 5(打ち切り) -16.90% -0.253 36.71% GPT 5.4 (Chat API) 0.13 8(打ち切り) -20.91% -0.515 31.88% • Gemini 3.1 Proが最高スコア0.23を達成。Sharpe Ratio 0.994、MaxDD 6.36%と比較的安定した成績 • GPT 5.4はスコア0.13で頭打ち。シンプルなリバーサルから脱却できず 10
  7. 優勝チームの戦略(Gemini 3.1 Pro) Factor Weight 方向 説明 EP +1.0 Long

    予想EPS / close(益回り) BP +0.5 Long BPS / close(簿価比率) ROE +0.4 Long 純利益 / 純資産 EPS_Growth +0.6 Long 予想EPS 20日変化率 Overnight +0.4 Long 翌日オーバーナイトリターン Mom1M +0.2 Long 1ヶ月モメンタム ShortRatio +0.2 Long 業種別空売り比率 SectorMom +0.2 Long 業種指数リターン O2C -0.8 Short 当日open→closeリターン(逆張り) TurnoverRev -0.4 Short 出来高調整リバーサル Rev1W -0.6 Short 1週間リバーサル Amihud -0.5 Short Amihud非流動性 後処理: ボラティリティスケーリング(20日σで除算) → 日次パーセンタイルランク 11
  8. 優勝チームのシグナル算出コード additional_data = { 'master': master, 'fin_summary': fin_summary, 'short_ratio': short_ratio,

    'sector_index': sector_index, } FACTOR_COLS = ['EP', 'BP', 'ROE', 'EPS_Growth', 'Overnight', 'Mom1M', 'ShortRatio', 'SectorMom', 'O2C', 'TurnoverRev', 'Rev1W', 'Amihud'] WEIGHTS = { 'EP': 1.0, 'BP': 0.5, 'ROE': 0.4, 'EPS_Growth': 0.6, 'Overnight': 0.4, 'Mom1M': 0.2, 'ShortRatio': 0.2, 'SectorMom': 0.2, 'O2C': -0.8, 'TurnoverRev': -0.4, 'Rev1W': -0.6, 'Amihud': -0.5 } def get_z(s): return s.rank(pct=True) - 0.5 def generate_signal_with_components( ohlcv: pl.DataFrame, additional_data: dict[str, pl.DataFrame] ) -> pd.DataFrame: df = ohlcv.to_pandas() master = additional_data['master'].to_pandas() fin_summary = additional_data['fin_summary'].to_pandas() short_ratio = additional_data['short_ratio'].to_pandas() sector_index = additional_data['sector_index'].to_pandas() # --- masterの結合 --- if 'datetime' in master.columns: df = df.sort_values(['datetime', 'symbol']) master = master.sort_values(['datetime', 'symbol']) df = pd.merge_asof( df, master[['datetime', 'symbol', 'sector33_code']], on='datetime', by='symbol', direction='backward' ) else: df = pd.merge(df, master[['symbol', 'sector33_code']], on='symbol', how='left') # --- fin_summaryの結合 --- df = pd.merge( df, fin_summary[['datetime', 'symbol', 'feps', 'bps', 'net_profit', 'equity']], on=['datetime', 'symbol'], how='left' ) # --- short_ratio / sector_index をロング形式へ変換して結合 --- sr_cols = [c for c in short_ratio.columns if c.startswith('short_ratio_')] sr_long = short_ratio.melt(id_vars=['datetime'], value_vars=sr_cols, var_name='sector33_code', value_name='ShortRatio') sr_long['sector33_code'] = sr_long['sector33_code'] \ .str.replace('short_ratio_', '').astype(int) si_cols = [c for c in sector_index.columns if c.endswith('_return')] si_long = sector_index.melt(id_vars=['datetime'], value_vars=si_cols, var_name='sector33_code', value_name='SectorMom') si_long['sector33_code'] = si_long['sector33_code'] \ .str.replace('sector_','').str.replace('_return','').astype(int) df['sector33_code'] = df['sector33_code'].fillna(-1).astype(int) df = pd.merge(df, sr_long, on=['datetime','sector33_code'], how='left') df = pd.merge(df, si_long, on=['datetime','sector33_code'], how='left') # --- 特徴量計算 --- df = df.sort_values(['symbol', 'datetime']).reset_index(drop=True) df['prev_close'] = df.groupby('symbol')['close'].shift(1) df['daily_ret'] = df['close']/df['prev_close'].replace(0,np.nan) - 1 df['EP'] = df['feps'] / df['close'].replace(0, np.nan) df['BP'] = df['bps'] / df['close'].replace(0, np.nan) df['ROE'] = df['net_profit']/df['equity'].abs().replace(0, np.nan) feps_s20 = df.groupby('symbol')['feps'].shift(20) df['EPS_Growth'] = (df['feps']-feps_s20)/feps_s20.abs().replace(0,np.nan) df['O2C'] = df['close'] / df['open'].replace(0, np.nan) - 1 df['SMA20_to'] = df.groupby('symbol')['turnover'] \ .transform(lambda x: x.rolling(20, min_periods=1).mean()) df['TurnoverRev'] = df['O2C']*(df['turnover']/df['SMA20_to'].replace(0,np.nan)) df['Rev1W'] = df['close']/df.groupby('symbol')['close'].shift(5) - 1 df['Mom1M'] = df['close']/df.groupby('symbol')['close'].shift(20) - 1 df['Overnight'] = df['open']/df['prev_close'].replace(0, np.nan) - 1 df['amihud_raw'] = df['daily_ret'].abs()/df['turnover'].replace(0,np.nan) df['Amihud'] = df.groupby('symbol')['amihud_raw'] \ .transform(lambda x: x.rolling(20, min_periods=1).mean()) # --- IC加重合成 + ボラスケーリング + 最終シグナル --- df['composite'] = 0.0 for col, w in WEIGHTS.items(): if col in df.columns: df[col] = df[col].replace([np.inf, -np.inf], np.nan) z = df.groupby('datetime')[col].transform(get_z) df['composite'] += z.fillna(0) * w df['volatility'] = df.groupby('symbol')['daily_ret'] \ .transform(lambda x: x.rolling(20, min_periods=5).std()) med = df.groupby('datetime')['volatility'].transform('median') df['volatility'] = df['volatility'].fillna(med).replace(0,np.nan).fillna(1.0) df['scaled_composite'] = df['composite'] / df['volatility'] df['signal'] = df.groupby('datetime')['scaled_composite'] \ .rank(pct=True).fillna(0.5) return df[['datetime','symbol','signal']] 13
  9. 1位 Gemini 3.1 Pro(スコア : 0.23) • バリュー系: EP(+1.0), BP(+0.5),

    ROE(+0.4) • 成長: EPS 20日変化率(+0.6) • モメンタム: 1ヶ月Mom(+0.2), Overnight(+0.4) • セクター: 業種空売り比率( +0.2), 業種指数リターン (+0.2) • リバーサル: O2C(-0.8), Rev1W(-0.6), TurnoverRev (-0.4) • 流動性: Amihud非流動性(-0.5) • 後処理: ボラティリティスケーリング( 20日σ) 15 12ファクター IC加重モデル ※P.11, P12に詳細 スコア: 0.23 (Round 9)  着実にスコアを伸ばしながら10Rを完走していた。
  10. 2位 Gemini 3 Flash(スコア: 0.21) 16 • 日中リターン逆張り( weight: 1.0)

    • オーバーナイトリターン逆張り( weight: 0.5) • 20日MA乖離の逆張り(weight: 0.3) • 確信度重み①: ROE(財務クオリティ) • 確信度重み②: 異常出来高(Turnover / 20日平均) • 確信度重み③: 低ボラティリティ( 1/vol_20) • 後処理: Sector17中立化 → パーセンタイルランク マルチTFリバーサル + 確信度重み スコア: 0.21 (Round 10)  着実にスコアを伸ばしながら10Rを完走していた。
  11. 3位 Claude Sonnet 4.6(スコア: 0.18) 17 • vol_ratio = volume

    / rolling(20).mean(volume)、 clip[0.1, 10] • sector17×日付グループ内で vol_ratioをpct_rank • raw_signal = (1 - O2C rank) × vol_rank_in_sector • 日付グループ内で raw_signalをpct_rank → NaN補完 (0.5) ※R1とR4で同一ロジック(スコア 0.18で打ち切り) 業種内相対出来高リバーサル スコア: 0.18 (Round 4)  1R目のスコアは1位。TAとSCの呼び出しも多かった。
  12. 4位 Claude Opus 4.6(スコア: 0.16) 18 • reversal_1d: -1 ×

    O2C rank • reversal_2d: -1 × 2日MA(O2C rank) • reversal_5d: -1 × 5日MA(O2C rank) • volume_shock: -1 × (vol/20日平均) × O2C rank • low_vol: -1 × 20日std(O2C rank) • high_low_range: -1 × (high-low)/close × O2C rank • 後処理: 1%/99% winsorize → ランク正規化 → nanmean合成 ※ R2開始時にエラー終了、 1ラウンドのみ 6シグナル等ウェイト合成( Combo L+) スコア: 0.16 (Round 1)  1R目のTAとSCの呼び出し回数が最も多かった。
  13. 5位 GPT 5.4 Response API(スコア: 0.13) 19 • q =

    open2close_return_rank • signal = -(q - 0.5) ※全5ラウンドでスコア変わらず終了 シンプル短期リバーサル スコア: 0.13 (Round 5)  ラウンドは重ねるも、シンプルなリバーサルから脱却できず。
  14. 5位 GPT 5.4 Chat Completion API(スコア: 0.13) 20 • x

    = open2close_return_rank • sector_mean_x = 当日のsector33別平均x • signal_raw = -(x - sector_mean_x)(sector33中立化) • signal = ロバストz-score化(median/MAD、clip±5) • MAD→std→0のフォールバック付き • 全8ラウンドで打ち切り ※SCを呼ばずリーダーがコードを書くパターン多い Sector33中立化リバーサル + ロバスト z-score スコア: 0.13 (Round 8)  ラウンドは重ねるも、シンプルなリバーサルから脱却できず。
  15. まとめ • 今回の検証ではGemini系モデルが最も好成績、それらしい戦略が得られた。 • 各チームを安定して走らせるためには、適宜挙動を確認し、プロンプトを調整する必要がある。 所感 23 • 思ったよりも大変だった。使用するモデルやプロンプト、データの渡し方など、考慮すべき点が多い。(今後参考に できる活用例が増えることに期待)

    • 0ベースで戦術を作らせるのには限界を感じる。こちらで有望なベースラインやシグナルを事前に与えておくほう が、おもしろい戦術が出てくるのかも。 • 汎化性能の評価が難しい。交差検証結果をコンペの評価に含める等の対策を考えたい。