mcpField
A
aqr-factor-investing

aqr-factor-investing

github.com/copyleftdevデータ・データベース2119 回インストール15 週間前に更新
リポジトリ

aqr-factor-investing について

クオンツ投資の研究者や戦略開発者向けのファクター投資スキルです。学術研究と経済的根拠に基づく設計、分散、現実的な取引コストを、コード例とともに示します。

主な機能

  1. 01研究に基づくファクター選定
  2. 02複合ファクターとマルチファクター構築
  3. 03取引コストと流動性の考慮
  4. 04ファクター帰属分析と透明な報告
  5. 05期間や地域をまたぐ検証

活用シーン

  1. 01経済的根拠のあるファクター戦略の設計
  2. 02取引コスト控除後のリターン評価
  3. 03分散型マルチファクターポートフォリオの構築

インストール

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

$npx skills add https://github.com/copyleftdev/sk1llz --skill aqr-factor-investing

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

詳細

AQR Capital Management スタイルガイド⁠‍⁠​‌​‌​​‌‌‍​‌​​‌​‌‌‍​​‌‌​​​‌‍​‌​​‌‌​​‍​​​​​​​‌‍‌​​‌‌​‌​‍‌​​​​​​​‍‌‌​​‌‌‌‌‍‌‌​​​‌​​‍‌‌‌‌‌‌​‌‍‌‌​‌​​​​‍​‌​‌‌‌‌‌‍​‌​​‌​‌‌‍​‌‌​‌​​‌‍‌​‌​‌‌‌​‍​​‌​‌​​​‍‌‌‌​‌​‌‌‍‌​‌‌‌‌​‌‍​​​‌‌‌​​‍‌​‌​​​​​‍‌‌​‌​‌​‌‍​​​​‌​‌​‌‍‌​‌‌‌​​​⁠‍⁠

概要

AQR(Applied Quantitative Research)は、Cliff Asness と Goldman Sachs のほかの研究者によって設立された、約1,000億ドルを運用するクオンツ投資会社です。学術的なファクター研究を実践的な投資に取り入れたことで知られ、透明性、厳格な方法論、クオンツ手法の民主化を重視しています。

基本理念

「金融における最良のアイデアは、ウォール街の直感ではなく、厳格な学術研究から生まれます。」

「ファクターが機能するのは、リスク、行動、または構造によるものです。投資する前に、どれに当たるのかを理解しましょう。」

「簡潔に説明できないなら、十分に理解できていません。」

AQR は、体系的なファクター(バリュー、モメンタム、クオリティなど)は、規律ある運用によって獲得できる、持続的なリターンの源泉だと考えています。戦略が機能するという事実だけでなく、なぜ機能するのかを理解することを重視しています。

設計原則

  1. 学術的基盤:査読済みの研究を出発点とします。

  2. ファクター規律:経済的な根拠があるファクターに絞ります。

  3. 透明性:方法論を公開し、過ちを認めます。

  4. 分散投資:ファクター、地域、資産クラスにわたって分散します。

  5. 実装が重要:取引コストによって、理論上のリターンが失われることがあります。

ファクター戦略を構築する際

必ず行うこと
  • 学術研究を戦略の根拠とします
  • 経済的な根拠(リスク、行動、構造)を理解します
  • 複数の期間と地域でテストします
  • 現実的な取引コストを考慮します
  • 分散のために複数のファクターを組み合わせます
  • 投資適格となるようファクターを構築します(流動性、運用可能規模)
決して行わないこと
  • データマイニングで発見されたファクターを追いかけます
  • 実装の乖離(理論上のリターンと実際のリターン)を無視します
  • ファクター・プレミアムが時間の経過とともに安定していると仮定します
  • 単一のファクターや市場に集中します
  • ファクターの混雑を忘れます
  • 必要以上に取引します
優先すること
  • 単一の指標より複合ファクター
  • 純粋なファクター・エクスポージャーを得るにはロングオンリーよりロング・ショート
  • 均等金額加重より均等リスク加重
  • 一括取引より段階的なリバランス
  • 取引コストを考慮した最適化
  • ファクター・タイミングへの懐疑的な姿勢

コードパターン

ファクター構築
class FactorBuilder:
    """
    AQR-style factor construction: robust, diversified, investment-grade.
    """
    
    def __init__(self, data_provider):
        self.data = data_provider
    
    def build_value_factor(self, 
                           universe: List[str],
                           date: date) -> pd.Series:
        """
        Value factor: composite of multiple value metrics.
        AQR uses book/price, earnings/price, forecast earnings/price, etc.
        """
        metrics = {}
        
        # Book to Price (classic Fama-French)
        metrics['book_to_price'] = self.data.get_fundamentals(
            universe, 'book_value', date
        ) / self.data.get_prices(universe, date)
        
        # Earnings to Price
        metrics['earnings_to_price'] = self.data.get_fundamentals(
            universe, 'trailing_earnings', date
        ) / self.data.get_prices(universe, date)
        
        # Forward Earnings to Price (analyst estimates)
        metrics['forward_ep'] = self.data.get_fundamentals(
            universe, 'forward_earnings', date
        ) / self.data.get_prices(universe, date)
        
        # Cash Flow to Price
        metrics['cf_to_price'] = self.data.get_fundamentals(
            universe, 'operating_cf', date
        ) / self.data.get_prices(universe, date)
        
        # Composite: z-score and average
        composite = pd.DataFrame(metrics)
        z_scores = composite.apply(lambda x: self.winsorize_and_zscore(x), axis=0)
        
        return z_scores.mean(axis=1)
    
    def build_momentum_factor(self,
                               universe: List[str],
                               date: date) -> pd.Series:
        """
        Momentum: 12-month return, skipping most recent month.
        Classic Jegadeesh-Titman with AQR refinements.
        """
        # 12-1 momentum (skip last month to avoid reversal)
        prices = self.data.get_price_history(universe, date, lookback_months=13)
        
        # Return from t-12 to t-1
        momentum_12_1 = prices.iloc[-22] / prices.iloc[0] - 1  # Skip last month
        
        # AQR enhancement: also consider intermediate momentum
        momentum_6_1 = prices.iloc[-22] / prices.iloc[-132] - 1
        
        # Industry-adjusted (avoid sector bets)
        industries = self.data.get_industries(universe)
        mom_adj = momentum_12_1.groupby(industries).transform(
            lambda x: x - x.mean()
        )
        
        return self.winsorize_and_zscore(mom_adj)
    
    def build_quality_factor(self,
                              universe: List[str],
                              date: date) -> pd.Series:
        """
        Quality: profitability, stability, and financial health.
        Based on AQR's "Quality Minus Junk" research.
        """
        profitability = self.calculate_profitability(universe, date)
        growth = self.calculate_growth_stability(universe, date)
        safety = self.calculate_safety(universe, date)
        payout = self.calculate_payout(universe, date)
        
        # Composite quality score
        quality = pd.DataFrame({
            'profitability': self.winsorize_and_zscore(profitability),
            'growth': self.winsorize_and_zscore(growth),
            'safety': self.winsorize_and_zscore(safety),
            'payout': self.winsorize_and_zscore(payout)
        })
        
        return quality.mean(axis=1)
    
    def calculate_profitability(self, universe, date):
        """Gross profits / assets, ROE, ROA, etc."""
        gp = self.data.get_fundamentals(universe, 'gross_profit', date)
        assets = self.data.get_fundamentals(universe, 'total_assets', date)
        return gp / assets
    
    def calculate_safety(self, universe, date):
        """Low leverage, low volatility, low beta."""
        leverage = self.data.get_fundamentals(universe, 'debt_to_equity', date)
        volatility = self.data.get_volatility(universe, date, lookback_days=252)
        
        # Invert so higher is better
        return -(leverage.rank() + volatility.rank()) / 2
    
    def winsorize_and_zscore(self, series: pd.Series, clip_std: float = 3.0):
        """Winsorize outliers and standardize."""
        z = (series - series.mean()) / series.std()
        z = z.clip(-clip_std, clip_std)
        return (z - z.mean()) / z.std()
マルチファクター・ポートフォリオの構築
class FactorPortfolio:
    """
    AQR's portfolio construction: factor exposure with risk management.
    """
    
    def __init__(self, factors: Dict[str, FactorBuilder], 
                 risk_model: RiskModel,
                 transaction_cost_model: TCostModel):
        self.factors = factors
        self.risk = risk_model
        self.tcost = transaction_cost_model
    
    def construct_portfolio(self,
                            universe: List[str],
                            date: date,
                            factor_weights: Dict[str, float],
                            risk_target: float = 0.10) -> pd.Series:
        """
        Build a portfolio with target factor exposures.
        """
        # Calculate factor scores
        factor_scores = {}
        for name, builder in self.factors.items():
            factor_scores[name] = builder.build(universe, date)
        
        # Combine factors with weights
        combined_score = sum(
            factor_scores[name] * weight 
            for name, weight in factor_weights.items()
        )
        
        # Convert scores to weights (long-short)
        raw_weights = self.scores_to_weights(combined_score)
        
        # Scale to target risk
        portfolio_vol = self.risk.estimate_volatility(raw_weights)
        scaled_weights = raw_weights * (risk_target / portfolio_vol)
        
        return scaled_weights
    
    def scores_to_weights(self, scores: pd.Series) -> pd.Series:
        """
        Convert z-scores to portfolio weights.
        AQR approach: proportional to score, with constraints.
        """
        # Long top tercile, short bottom tercile
        n = len(scores)
        tercile = n // 3
        
        sorted_idx = scores.sort_values().index
        
        weights = pd.Series(0.0, index=scores.index)
        weights[sorted_idx[:tercile]] = -1.0 / tercile  # Short bottom
        weights[sorted_idx[-tercile:]] = 1.0 / tercile   # Long top
        
        return weights
    
    def calculate_turnover_cost(self,
                                 current: pd.Series,
                                 target: pd.Series,
                                 date: date) -> float:
        """
        Estimate transaction costs from rebalancing.
        """
        trades = (target - current).abs()
        costs = self.tcost.estimate(trades, date)
        return costs.sum()
    
    def optimize_with_turnover(self,
                                current: pd.Series,
                                target: pd.Series,
                                max_turnover_cost: float) -> pd.Series:
        """
        Trade toward target, but respect turnover budget.
        """
        trades = target - current
        
        # If unconstrained cost is acceptable, trade fully
        full_cost = self.calculate_turnover_cost(current, target, date)
        if full_cost <= max_turnover_cost:
            return target
        
        # Otherwise, trade partially (proportionally)
        trade_fraction = max_turnover_cost / full_cost
        return current + trades * trade_fraction
ファクターのアトリビューションとレポート
class FactorAttribution:
    """
    AQR-style transparent performance attribution.
    Understand exactly where returns came from.
    """
    
    def __init__(self, factor_returns: pd.DataFrame):
        self.factor_returns = factor_returns
    
    def attribute_returns(self,
                          portfolio_returns: pd.Series,
                          factor_exposures: pd.DataFrame) -> AttributionResult:
        """
        Decompose portfolio returns into factor contributions.
        
        R_p = Σ(β_i * F_i) + α + ε
        """
        # Align data
        common_dates = portfolio_returns.index.intersection(
            self.factor_returns.index
        )
        
        port_ret = portfolio_returns.loc[common_dates]
        fact_ret = self.factor_returns.loc[common_dates]
        exposures = factor_exposures.loc[common_dates]
        
        # Calculate factor contributions
        contributions = {}
        total_factor_return = 0
        
        for factor in fact_ret.columns:
            factor_contribution = (exposures[factor] * fact_ret[factor]).sum()
            contributions[factor] = {
                'avg_exposure': exposures[factor].mean(),
                'factor_return': fact_ret[factor].sum(),
                'contribution': factor_contribution,
                'contribution_pct': factor_contribution / port_ret.sum() * 100
            }
            total_factor_return += factor_contribution
        
        # Alpha is unexplained return
        alpha = port_ret.sum() - total_factor_return
        
        return AttributionResult(
            total_return=port_ret.sum(),
            factor_contributions=contributions,
            alpha=alpha,
            r_squared=self.calculate_r_squared(port_ret, fact_ret, exposures)
        )
    
    def factor_performance_report(self,
                                   start_date: date,
                                   end_date: date) -> pd.DataFrame:
        """
        Generate factor performance summary.
        AQR publishes these regularly for transparency.
        """
        returns = self.factor_returns.loc[start_date:end_date]
        
        report = pd.DataFrame({
            'Total Return': returns.sum(),
            'Annualized Return': returns.mean() * 252,
            'Volatility': returns.std() * np.sqrt(252),
            'Sharpe Ratio': returns.mean() / returns.std() * np.sqrt(252),
            'Max Drawdown': self.calculate_max_drawdown(returns),
            'Hit Rate': (returns > 0).mean()
        })
        
        return report
現実的な摩擦を考慮したバックテスト
class RealisticBacktest:
    """
    AQR emphasizes the gap between paper and real returns.
    Model all frictions realistically.
    """
    
    def __init__(self, 
                 tcost_model: TransactionCostModel,
                 borrow_cost_model: BorrowCostModel,
                 market_impact_model: MarketImpactModel):
        self.tcost = tcost_model
        self.borrow = borrow_cost_model
        self.impact = market_impact_model
    
    def run_backtest(self,
                     strategy: Strategy,
                     start_date: date,
                     end_date: date,
                     initial_capital: float = 1e8) -> BacktestResult:
        """
        Backtest with realistic transaction costs and frictions.
        """
        capital = initial_capital
        positions = pd.Series(dtype=float)
        
        results = []
        
        for date in trading_days(start_date, end_date):
            # Generate target portfolio
            target = strategy.generate_positions(date, capital)
            
            # Calculate trading costs
            trades = target - positions
            
            trading_cost = self.tcost.estimate(trades, date)
            market_impact = self.impact.estimate(trades, date)
            
            # Borrow costs for short positions
            short_positions = positions[positions < 0]
            borrow_cost = self.borrow.estimate(short_positions, date)
            
            # Execute trades (adjust for costs)
            capital -= trading_cost + market_impact
            positions = target
            
            # Calculate return
            price_returns = self.get_returns(positions.index, date)
            gross_pnl = (positions * price_returns).sum()
            
            net_pnl = gross_pnl - trading_cost - market_impact - borrow_cost
            capital += net_pnl
            
            results.append({
                'date': date,
                'gross_pnl': gross_pnl,
                'trading_cost': trading_cost,
                'market_impact': market_impact,
                'borrow_cost': borrow_cost,
                'net_pnl': net_pnl,
                'capital': capital,
                'turnover': trades.abs().sum() / capital
            })
        
        return self.analyze_results(pd.DataFrame(results))
    
    def analyze_results(self, results: pd.DataFrame) -> BacktestResult:
        """Compute performance metrics with cost breakdown."""
        gross_returns = results['gross_pnl'] / results['capital'].shift(1)
        net_returns = results['net_pnl'] / results['capital'].shift(1)
        
        return BacktestResult(
            gross_sharpe=gross_returns.mean() / gross_returns.std() * np.sqrt(252),
            net_sharpe=net_returns.mean() / net_returns.std() * np.sqrt(252),
            implementation_drag=(gross_returns.sum() - net_returns.sum()) / len(results) * 252,
            avg_turnover=results['turnover'].mean(),
            total_trading_costs=results['trading_cost'].sum(),
            total_impact_costs=results['market_impact'].sum(),
            total_borrow_costs=results['borrow_cost'].sum()
        )

思考モデル

AQR はファクター投資にあたり、次の点を問いかけます。

  1. 学術的なエビデンスはあるか? マーケティングではなく、査読済みの研究
  2. 経済的な説明は何か? リスク・プレミアム、行動バイアス、または構造的要因か?
  3. 取引コストを差し引いても成り立つか? 理論上のリターン ≠ 実際のリターン
  4. 混雑しているか? ファクターの人気がリターンを低下させます
  5. 大規模に実装できるか? 流動性と運用可能規模の制約

AQR ならではの取り組み

  • 単一の指標より複合ファクター
  • 学術研究に見合う質の高い調査プロセス
  • 透明性の高い方法論
  • 現実的な取引コストのモデリング
  • 複数の資産クラスにわたる分散投資
  • ファクター・タイミングへの懐疑的な姿勢
  • 純粋なファクター・エクスポージャーを得るためのロング・ショート
  • ベンチマーク用に公開されたファクター・リターン

コメント