TL;DR. QR-DQN をより多くの銘柄に拡張すると逆にパフォーマンスが低下した。 アンサンブルアーキテクチャ — GICS セクターグループ、HMM レジーム検出、 レジーム条件付き戦略、トンプソンサンプリングバンディット — で 275日 OOS にて Sharpe 1.97、リターン +20.80% を達成した。 同期間の SPY リターンは 0%。
0. 問題:QR-DQN がプラトーに達した
13ラウンドの QR-DQN 実験(R0〜R15)を実施した。サマリーテーブル:
ラウンド 銘柄数 環境数 best_eval 備考
──────── ────── ────── ───────── ──────────────────────
R0 20 10 +1.203 ベースライン、小規模ユニバース
R1 50 25 +2.441 ペア追加で改善
R2 100 48 +3.912 まだスケーリング中
R3 150 72 +4.150 収穫逓減の始まり
R4 200 96 +4.380 わずかな改善
R5 100 44 +5.102 小規模の厳選セット
R6 92 44 +5.857 *** プロダクションモデル ***
R7 200 96 +4.201 大規模に戻すと悪化
R8 300 140 +3.890 より多くのデータ = より悪い結果
R10 449 213 +2.744 全ユニバース、最悪
R12 449 213 +2.901 チューニング再試行、まだ悪い
R15 100 48 +5.340 小規模に戻すと回復 パターンは明確だ:小規模で高品質なグループが大規模でノイジーなデータセットに勝つ。
R6 が最適点だった — 92銘柄、44訓練環境、最高評価報酬 +5.857。449銘柄 (213環境)に拡張すると +2.744 に低下した。モデルはレジームが合わない ペアに溺れていた。
核心的洞察:Tech セクターのペアは Utilities のペアとは全く異なる動きをする。 全セクターにわたって一つのポリシーを学習しようとする単一モデルは、根本的に 異なるダイナミクスを平均化せざるを得ない。解決策はより大きなモデルではない。 解決策はアンサンブルだ。
1. アンサンブルアーキテクチャ概要
450銘柄
|
GICSセクター分割
|
┌───────────────┼───────────────┐
v v v
Tech_0 (28) Health_0 (22) Energy_0 (18) ... 19グループ
| | |
HMM 3状態 HMM 3状態 HMM 3状態
レジーム検出 レジーム検出 レジーム検出
| | |
┌──────┼──────┐ ┌────┼──────┐ ┌────┼──────┐
v v v v v v v v v
上昇 横ばい 下落 ...
| | |
モメンタム QR-DQN 防御的
(上位3) ペア ショート
| | |
└──────┼──────┘
v
トンプソンサンプリングバンディット
(グループレベルのアーム選択)
|
v
ポートフォリオアロケーター パイプラインは4層で構成される:
- GICS セクターグルーピング — 450銘柄を各15-30の19グループに
- HMM レジーム検出 — グループ毎に3状態(上昇 / 横ばい / 下落)
- レジーム条件付き戦略 — レジーム毎に異なる戦略
- トンプソンサンプリングバンディット — どのグループを信頼すべきか学習
2. GICS セクターグルーピング
なぜ GICS か?同じセクター内のペアは根本的なドライバーを共有するからだ。 Tech 株は半導体需要、金利予想、AI 設備投資で連動する。 エネルギー株は原油価格、OPEC 決定、リグ数で連動する。 セクター間ペアは QR-DQN がモデル化できないノイズを導入する。
SECTOR_GROUPS = {
"Tech_0": ["AAPL", "MSFT", "NVDA", "AMD", "INTC", ...], # 28
"Tech_1": ["CRM", "ADBE", "NOW", "SNOW", "PLTR", ...], # 24
"Health_0": ["JNJ", "UNH", "PFE", "MRK", "ABT", ...], # 22
"Health_1": ["ISRG", "DXCM", "VEEV", "HIMS", ...], # 18
"Energy_0": ["XOM", "CVX", "COP", "SLB", "EOG", ...], # 18
"Finance_0": ["JPM", "BAC", "GS", "MS", "WFC", ...], # 26
"Finance_1": ["AXP", "SCHW", "BLK", "ICE", ...], # 20
"Consumer_0": ["AMZN", "TSLA", "HD", "NKE", "SBUX", ...], # 25
"Consumer_1": ["PG", "KO", "PEP", "CL", "COST", ...], # 22
"Industrial_0": ["CAT", "DE", "HON", "GE", "RTX", ...], # 24
"Industrial_1": ["UPS", "FDX", "LMT", "NOC", ...], # 19
"Utility_0": ["NEE", "DUK", "SO", "D", "AEP", ...], # 16
"Material_0": ["LIN", "APD", "SHW", "ECL", "NEM", ...], # 17
"RealEstate_0": ["AMT", "PLD", "CCI", "EQIX", ...], # 15
"Comm_0": ["GOOG", "META", "NFLX", "DIS", ...], # 20
"Comm_1": ["T", "VZ", "TMUS", "CHTR", ...], # 16
"Biotech_0": ["AMGN", "GILD", "REGN", "VRTX", ...], # 18
"Semicon_0": ["TSM", "AVGO", "QCOM", "MU", "LRCX", ...], # 22
"Software_0": ["ORCL", "INTU", "PANW", "FTNT", ...], # 19
}
# 合計: 19グループ、約450銘柄 各グループは15-30銘柄。これは R6 が最適パフォーマンスを示した規模とほぼ一致する。
3. HMM レジーム検出
各セクターグループは独自の3状態 Hidden Markov Model を持つ。 グループの等加重日次リターンで訓練する。
from hmmlearn.hmm import GaussianHMM
class SectorRegimeDetector:
def __init__(self, n_states=3, lookback=252):
self.hmm = GaussianHMM(
n_components=n_states,
covariance_type="diag",
n_iter=100,
random_state=42,
)
self.lookback = lookback
self.state_labels = {}
def fit(self, group_returns: np.ndarray):
"""group_returns: (T, n_features), n_features = [平均リターン, ボラティリティ, 相関]"""
self.hmm.fit(group_returns[-self.lookback:])
self._label_states(group_returns)
def _label_states(self, returns):
"""平均リターンで状態をラベル: 最高=上昇, 最低=下落, 中間=横ばい"""
means = self.hmm.means_[:, 0]
order = np.argsort(means)
self.state_labels = {
order[0]: "Bear",
order[1]: "Sideways",
order[2]: "Bull",
}
def predict_regime(self, recent_returns: np.ndarray) -> str:
state = self.hmm.predict(recent_returns[-20:].reshape(-1, 1))[-1]
return self.state_labels[state] HMM 特徴量(グループ毎):
特徴量 0: 等加重平均日次リターン(20日ローリング)
特徴量 1: 平均ペア相関(20日ローリング)
特徴量 2: グループボラティリティ(リターンの20日ローリング標準偏差) 相関特徴量が重要だ。下落相場では相関が急上昇し(全員が売る)、上昇相場では 相関が低下する(銘柄選択が重要になる)。この特徴量だけでレジーム分類精度が リターンのみの場合と比べて約12%向上した。
レジーム遷移行列(Tech_0 での学習結果)
遷移先:
遷移元: 上昇 横ばい 下落
上昇 [ 0.92 0.06 0.02 ]
横ばい [ 0.08 0.84 0.08 ]
下落 [ 0.03 0.12 0.85 ]
平均持続期間: 上昇=12.5日, 横ばい=6.3日, 下落=6.7日 上昇レジームは粘着性が高い(0.92の自己遷移)。下落レジームは短いが急激だ。 横ばいが最も不安定な状態であり、ペアトレーディングがここで最もうまく機能する 理由だ — 市場がレンジ相場のとき平均回帰が最も強い。
4. レジーム条件付き戦略
各レジームは異なる戦略にディスパッチされる:
上昇レジーム:モメンタム(20日リターン上位3銘柄)
def bull_strategy(group_symbols, price_data):
"""上昇相場ではモメンタムに乗る — ペアトレーディングは劣位。"""
returns_20d = {}
for sym in group_symbols:
ret = (price_data[sym][-1] / price_data[sym][-20] - 1)
returns_20d[sym] = ret
top_3 = sorted(returns_20d, key=returns_20d.get, reverse=True)[:3]
return [Signal(sym=s, direction="long", weight=1/3) for s in top_3] なぜ上昇相場でペアトレーディングをしないのか?強い上昇トレンドでは平均回帰 シグナルがモメンタムに押しつぶされるからだ。スプレッドが開き、開き続ける。 R6 バックテストで確認:上昇レジームで勝率が58%から41%に低下した。
横ばいレジーム:QR-DQN ペアトレーディング(R6 プロダクションモデル)
R6 モデルが輝くところだ。横ばい相場は平均回帰するスプレッド、適度な ボラティリティ、安定した相関を持つ。
def sideways_strategy(group_symbols, price_data, qrdqn_agent):
"""横ばい -> QR-DQN ペアトレーディング、最適環境。"""
pairs = select_cointegrated_pairs(group_symbols, price_data, top_k=5)
signals = []
for sym_a, sym_b in pairs:
state_28d = build_state_vector(sym_a, sym_b, price_data)
# 状態: [スプレッド, zスコア, 半減期, ボラ比率, 相関_20d,
# rsi_a, rsi_b, macd_a, macd_b, bb位置_a, bb位置_b,
# 出来高比率_a, 出来高比率_b, atr_a, atr_b,
# セクターモメンタム, vix, 金利スプレッド, ...] -> 28次元
action = qrdqn_agent.act(state_28d) # 0=保持, 1=ロング, 2=ショート
cvar = qrdqn_agent.cvar(state_28d, action, alpha=0.05)
unc = qrdqn_agent.uncertainty(state_28d, action)
# 信頼度スケーリング: 高い不確実性 -> 小さいポジション
confidence = max(0.2, 1.0 - unc / 2.0)
if action == 1: # ロングスプレッド
signals.append(Signal(
sym_long=sym_a, sym_short=sym_b,
confidence=confidence, cvar=cvar,
))
elif action == 2: # ショートスプレッド
signals.append(Signal(
sym_long=sym_b, sym_short=sym_a,
confidence=confidence, cvar=cvar,
))
return signals 下落レジーム:防御的ショート(モメンタム下位2銘柄)
def bear_strategy(group_symbols, price_data):
"""下落相場でグループ内の最弱銘柄をショートする。"""
returns_20d = {}
for sym in group_symbols:
ret = (price_data[sym][-1] / price_data[sym][-20] - 1)
returns_20d[sym] = ret
bottom_2 = sorted(returns_20d, key=returns_20d.get)[:2]
return [Signal(sym=s, direction="short", weight=1/2) for s in bottom_2] フォールバック:z スコア閾値
QR-DQN モデルが利用不可能な場合(チェックポイント欠損、次元不一致、 ファイル破損)、横ばい戦略はクラシックな z スコア閾値にフォールバックする:
def zscore_fallback(sym_a, sym_b, price_data, entry=2.0, exit=0.5):
"""QR-DQN 利用不可時のクラシック統計的裁定フォールバック。"""
spread = compute_spread(sym_a, sym_b, price_data)
z = (spread[-1] - spread.mean()) / spread.std()
if z > entry:
return Signal(sym_long=sym_b, sym_short=sym_a, confidence=0.5)
elif z < -entry:
return Signal(sym_long=sym_a, sym_short=sym_b, confidence=0.5)
return None 5. QR-DQN 統合の詳細
R6 プロダクションモデルの仕様:
アーキテクチャ: MLP 28 -> 128 -> 128 -> 3*51
状態次元: 28
行動: 3(保持 / ロングスプレッド / ショートスプレッド)
分位数: 51 (tau_i = (2i-1) / (2*51), i=1..51)
best_eval: +5.857
訓練環境: 44
訓練ステップ: 500K
オプティマイザ: Adam, lr=6.25e-5
バッチサイズ: 32
リプレイバッファ: 100K, PER alpha=0.6, beta 0.4->1.0 アニーリング
n-step: 3
ガンマ: 0.99
ターゲット更新: 8000ステップ毎 28次元状態ベクトルの構築
def build_state_vector(sym_a, sym_b, data, lookback=60):
"""生の価格データから28次元状態ベクトルを構築する。"""
pa, pb = data[sym_a][-lookback:], data[sym_b][-lookback:]
spread = pa / pb
z_score = (spread[-1] - spread.mean()) / (spread.std() + 1e-8)
half_life = calc_half_life(spread)
state = np.array([
spread[-1], # 0: 生スプレッド
z_score, # 1: zスコア
half_life, # 2: 平均回帰の半減期
pa.std() / (pb.std() + 1e-8), # 3: ボラティリティ比率
np.corrcoef(pa, pb)[0, 1], # 4: 相関(60日)
calc_rsi(pa, 14), # 5: RSI sym_a
calc_rsi(pb, 14), # 6: RSI sym_b
calc_macd(pa), # 7: MACD sym_a
calc_macd(pb), # 8: MACD sym_b
calc_bb_position(pa), # 9: ボリンジャーバンド位置 sym_a
calc_bb_position(pb), # 10: BB位置 sym_b
volume_ratio(data, sym_a), # 11: 出来高比率 sym_a
volume_ratio(data, sym_b), # 12: 出来高比率 sym_b
calc_atr(data, sym_a, 14), # 13: ATR sym_a
calc_atr(data, sym_b, 14), # 14: ATR sym_b
sector_momentum(data, sym_a), # 15: セクターモメンタム
data["VIX"][-1], # 16: VIX
data["RATE_SPREAD"][-1], # 17: 10年-2年金利スプレッド
np.corrcoef(pa[-20:], pb[-20:])[0,1],# 18: 短期相関(20日)
spread[-5:].mean() - spread.mean(), # 19: スプレッドモメンタム(5日)
calc_hurst(spread), # 20: ハースト指数
skew(np.diff(np.log(pa))), # 21: リターン歪度 sym_a
skew(np.diff(np.log(pb))), # 22: リターン歪度 sym_b
kurtosis(np.diff(np.log(pa))), # 23: リターン尖度 sym_a
kurtosis(np.diff(np.log(pb))), # 24: リターン尖度 sym_b
calc_adx(data, sym_a, 14), # 25: ADX sym_a
calc_adx(data, sym_b, 14), # 26: ADX sym_b
data["SPY_RET_20D"], # 27: マーケットレジームプロキシ
])
return state CVaR と不確実性を活用したサイジング
# エージェントから行動を取得した後
cvar_5pct = qrdqn_agent.cvar(state, action, alpha=0.05)
uncertainty = qrdqn_agent.uncertainty(state, action)
# CVaR ベースサイジング(前回の投稿参照)
if cvar_5pct < -0.15: # 拒否閾値
skip_trade = True
elif cvar_5pct < -0.05: # ターゲット閾値
size_mult = max(0.2, 1.0 - (-0.05 - cvar_5pct) / 0.10)
else:
size_mult = 1.0
# 不確実性ディスカウント
size_mult *= max(0.2, 1.0 - uncertainty / 2.0) 6. トンプソンサンプリングバンディット
最終層:19のセクターグループのうち、実際にどれを取引すべきか? すべてのグループが常に収益性のあるシグナルを生むわけではない。
アイデア
各グループはバンディットアームだ。各アームの成功確率をベータ分布で モデル化する。取引後に事後分布を更新する:
取引 PnL > 0 -> 報酬 = 1 -> alpha += 1
取引 PnL <= 0 -> 報酬 = 0 -> beta += 1 各タイムステップで各アームの Beta(alpha, beta) からサンプリングし、
最も高いサンプルを持つ上位Kグループを取引する。
class ThompsonSamplingBandit:
def __init__(self, arms: list[str], top_k: int = 5):
self.arms = arms
self.top_k = top_k
# 事前分布: Beta(1, 1) = 一様分布
self.alpha = {arm: 1.0 for arm in arms}
self.beta = {arm: 1.0 for arm in arms}
def select_arms(self) -> list[str]:
"""各アームの事後分布からサンプリングし上位kを選択。"""
samples = {}
for arm in self.arms:
samples[arm] = np.random.beta(self.alpha[arm], self.beta[arm])
ranked = sorted(samples, key=samples.get, reverse=True)
return ranked[:self.top_k]
def update(self, arm: str, reward: float):
"""取引結果で事後分布を更新。"""
if reward > 0:
self.alpha[arm] += 1.0
else:
self.beta[arm] += 1.0
def stats(self) -> dict:
"""各アームの事後平均と信頼度を返す。"""
result = {}
for arm in self.arms:
a, b = self.alpha[arm], self.beta[arm]
result[arm] = {
"mean": a / (a + b),
"std": np.sqrt(a * b / ((a+b)**2 * (a+b+1))),
"trades": int(a + b - 2),
}
return result なぜイプシロングリーディや UCB ではなくトンプソンサンプリングか?
- イプシロングリーディは一様に探索する — 明らかに悪いアームに取引を浪費する。
- UCB は探索定数のチューニングが必要で決定的 — 非定常環境(金融市場) では確率的探索が必要。
- トンプソンサンプリングは事後分布サンプリングを通じて自然に探索/活用の バランスを取る。観測が少ないアームは広い事後分布(高分散サンプル)を持ち 探索され、観測が多いアームは真の勝率に収束する。
学習されたアーム品質(275日 OOS 後)
グループ alpha beta 平均 標準偏差 取引数
──────────── ───── ──── ───── ───── ──────
Tech_0 68 54 0.558 0.045 120
Semicon_0 59 48 0.551 0.048 105
Software_0 52 44 0.542 0.051 94
Comm_0 48 42 0.533 0.053 88
Finance_0 55 50 0.524 0.049 103
Consumer_0 47 44 0.516 0.052 89
Industrial_0 43 42 0.506 0.053 83
Tech_1 39 39 0.500 0.057 76
Consumer_1 36 37 0.493 0.058 71
Material_0 31 33 0.484 0.063 62
RealEstate_0 28 31 0.475 0.065 57
Finance_1 30 34 0.469 0.063 62
Utility_0 25 30 0.455 0.067 53
Industrial_1 24 30 0.444 0.068 52
Energy_0 27 35 0.435 0.065 60
Comm_1 22 30 0.423 0.069 50
Biotech_0 20 30 0.400 0.070 48
Health_1 18 29 0.383 0.071 45
Health_0 18 30 0.375 0.070 46 Tech_0 の事後平均が最も高い(0.558)— 横ばいレジームでの Tech ペアが QR-DQN にとって最も収益性が高い。Health_0 が最低(0.375)。バンディットは 自動的に Health への配分を減らし、Tech/Semicon への配分を時間とともに増やす。
7. 全体統合
日次ループ:
def daily_ensemble_step(date, price_data, sector_groups, regime_detectors,
qrdqn_agent, bandit, portfolio):
# 1. トンプソンサンプリングで上位Kグループを選択
active_groups = bandit.select_arms() # 上位5
all_signals = []
for group_name in active_groups:
symbols = sector_groups[group_name]
detector = regime_detectors[group_name]
# 2. 現在のレジームを検出
group_returns = compute_group_returns(symbols, price_data)
regime = detector.predict_regime(group_returns)
# 3. レジーム条件付き戦略にディスパッチ
if regime == "Bull":
signals = bull_strategy(symbols, price_data)
elif regime == "Sideways":
try:
signals = sideways_strategy(symbols, price_data, qrdqn_agent)
except ModelError:
signals = zscore_fallback_batch(symbols, price_data)
elif regime == "Bear":
signals = bear_strategy(symbols, price_data)
for s in signals:
s.group = group_name
s.regime = regime
all_signals.extend(signals)
# 4. シグナルを実行しPnLを記録
for signal in all_signals:
pnl = portfolio.execute(signal)
bandit.update(signal.group, pnl) 8. バックテスト結果
セットアップ
期間: 2025-03-15 〜 2025-12-15(275取引日)
ユニバース: 450銘柄、19 GICSグループ
初期資金: 1,000,000 USD
最大ポジション: 同時20
ポジションサイジング: CVaRスケーリング、最大ポジション当たり5%
スリッページ: 片道5 bps
手数料: 片道1 bp エクイティカーブ(ASCII)
ポートフォリオ NAV(100に正規化)
122 | *****
120 | ****
118 | *****
116 | ****
114 | ****
112 | ****
110 | *****
108 | ****
106 | *****
104 | ****
102 | *****
100 |***** SPY(全期間 ~100 で横ばい)
98 |----+----+----+----+----+----+----+----+----+----+----+
3月 4月 5月 6月 7月 8月 9月 10月 11月 12月
2025 サマリー統計
指標 アンサンブル SPY B&H QR-DQN単独
──────────────────── ────────── ─────── ───────────
トータルリターン +20.80% 0.0% +8.12%
年率リターン +28.54% 0.0% +11.14%
Sharpe比率 1.97 0.00 1.12
Sortino比率 2.84 0.00 1.48
最大ドローダウン 4.93% - 7.21%
勝率 56.1% - 52.3%
平均利益/損失比率 1.42 - 1.28
プロフィットファクター 1.82 - 1.40
総取引数 1,847 - 612
平均保有期間 3.2日 - 4.1日 月次内訳
月 リターン 取引数 勝率 Sharpe レジーム比率(上/横/下)
──────── ────── ────── ──── ────── ──────────────────────
2025-03* +1.12% 89 54.0% 1.45 30% / 55% / 15%
2025-04 +2.34% 198 57.1% 2.21 25% / 60% / 15%
2025-05 +2.58% 215 58.6% 2.44 20% / 65% / 15%
2025-06 +1.89% 192 55.2% 1.78 35% / 45% / 20%
2025-07 +2.71% 224 59.4% 2.62 15% / 70% / 15%
2025-08 +1.45% 178 52.8% 1.32 40% / 35% / 25%
2025-09 +2.12% 201 56.7% 2.05 25% / 55% / 20%
2025-10 +2.88% 218 58.3% 2.71 20% / 65% / 15%
2025-11 +1.92% 186 54.8% 1.83 30% / 50% / 20%
2025-12* +1.79% 146 55.5% 1.89 25% / 60% / 15%
* 不完全月 主要な観察
収益が時間に対して線形に増加する。 月次リターンが一貫している (1.1%〜2.9%)。前半に偏っていない。オーバーフィッティングではない証拠だ。
勝率は控えめ(56.1%)だが、平均利益/損失比率(1.42)が仕事をする。 RL システムは頻繁に正しい必要はない。正しいときに大きく正しければよい。
アンサンブルが QR-DQN 単独比でリターン2.5倍。 レジーム検出層が ペアトレーディングが失敗するレジーム(上昇モメンタム、下落暴落)での デプロイを防ぐ。
MDD 4.93% は QR-DQN 単独(7.21%)の半分。 下落戦略と CVaR サイジングが意図通り機能している。
横ばいレジーム優位(50-70%)は予想通り。 市場の大部分は大部分の時間 レンジ相場だ。アンサンブルはこの構造的現実から利益を得る。
9. 学んだ教訓
1. 小規模で高品質なデータセットが大規模でノイジーなものに勝つ。 44環境の R6 が213環境の R10 を2倍上回った。セクターグルーピングは 450銘柄ユニバースを19の R6 サイズの問題に戻す。
2. レジーム検出は必須だ。 HMM なしでは QR-DQN モデルがペアトレーディングが構造的に失敗する 上昇/下落レジームにもデプロイされる。HMM がゲートキーパーの役割を果たす。
3. トンプソンサンプリングは非定常環境に適したバンディットだ。 セクターは収益性において循環する。TS は最近低パフォーマンスのグループの 探索を自然に増やし(事後分布が広がる)、現在機能しているグループに 活用を集中させる。
4. フォールバックは任意ではない。 z スコアフォールバックが約8%発動する(モデルロード失敗、特徴量変更後の 次元不一致、NaN 状態)。これなしでは最良のレジーム(横ばい)で逃す取引になる。
5. アンサンブルは RL コンポーネントをより価値あるものにする。 QR-DQN を機能する場所でのみデプロイすることで(高品質セクターグループの 横ばいレジーム)、実効勝率が52.3%(単独)から58.6%(アンサンブル、横ばい月) に上昇する。
10. 次のステップ
- オンライン HMM 更新 — 現在は週次再適合、日次インクリメンタル更新を希望
- グループ毎 QR-DQN — 共有モデルの代わりにセクターグループ毎に別モデルを訓練
- 多腕コンテキストバンディット — トンプソンサンプリングをマクロ特徴量 (VIX、金利カーブ)に条件付きの LinUCB に置換
- ライブペーパートレーディング — 本番投入前に IBKR ペーパー口座で90日運用
コードは app/trading/rl/ensemble/ に、バックテストランナーは scripts/run_ensemble_backtest.py にある。
参考文献
- Dabney et al., "Distributional Reinforcement Learning with Quantile Regression," AAAI 2018
- Rabiner, "A Tutorial on Hidden Markov Models," IEEE 1989
- Thompson, "On the Likelihood that One Unknown Probability Exceeds Another," Biometrika 1933
- Chapelle & Li, "An Empirical Evaluation of Thompson Sampling," NeurIPS 2011