Skip to main content
Glama
dun999

FinSight

analyze_factors

Analyze portfolio factor exposures including market beta, asset class contributions, regional and sector breakdowns, interest rate sensitivity, dividend yield, and currency exposure to assess diversification and risk.

Instructions

Factor exposure analysis: portfolio market beta, asset-class contributions, region/sector breakdown, interest-rate sensitivity (duration), dividend yield, currency exposure. Payment: $0.02 USDC on Tempo chain.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
holdingsYes
profileNoRisk profile — affects rebalance targets and scoring. Default: balanced.
benchmarkReturnNoAnnual benchmark return for Sharpe calculation, e.g. 0.08 = 8%. Default: 0.08.
riskFreeRateNoAnnual risk-free rate for Sortino and VaR excess return, e.g. 0.05 = 5%. Default: 0.05.
rebalanceMethodNoPortfolio construction method for rebalance recommendations. Default: profile.
marketIndicatorsNoOptional macro indicators — improves market regime detection confidence to HIGH when 3+ provided.

Implementation Reference

  • The main handler function for factor exposure analysis, calculating beta, asset class contributions, duration, and other risk factors.
    export function analyzeFactorExposure(
      portfolio: Portfolio,
      returnSeries?: Record<string, number[]>,
    ): FactorExposureResult {
      const holdings = portfolio.holdings
    
      // ── Market beta (real OLS regression when available, class default otherwise) ─
      const marketBeta = holdings.reduce(
        (acc, h) => acc + h.weight * resolveEffectiveBeta(h, returnSeries),
        0,
      )
    
      let betaInterpretation: string
      if (marketBeta < 0.2) {
        betaInterpretation = 'Defensive — portfolio moves far less than the broad market'
      } else if (marketBeta < 0.7) {
        betaInterpretation = 'Low-beta — underperforms in bull markets, outperforms in downturns'
      } else if (marketBeta < 1.2) {
        betaInterpretation = 'Market-neutral — tracks broad market closely'
      } else if (marketBeta < 1.8) {
        betaInterpretation = 'High-beta — amplified market moves in both directions'
      } else {
        betaInterpretation = 'Very high-beta — extreme market sensitivity'
      }
    
      // ── Asset class contributions ────────────────────────────────────────────
      const classMap = new Map<
        AssetClass,
        { weight: number; betaContrib: number; volContrib: number }
      >()
      for (const h of holdings) {
        const beta = resolveEffectiveBeta(h, returnSeries)
        const existing = classMap.get(h.assetClass) ?? { weight: 0, betaContrib: 0, volContrib: 0 }
        classMap.set(h.assetClass, {
          weight:      existing.weight      + h.weight,
          betaContrib: existing.betaContrib + h.weight * beta,
          volContrib:  existing.volContrib  + h.weight * h.volatility,
        })
      }
      const assetClassContributions: AssetClassContribution[] = Array.from(classMap.entries())
        .map(([assetClass, { weight, betaContrib, volContrib }]) => ({
          assetClass,
          weight:          Number(weight.toFixed(4)),
          betaContribution: Number(betaContrib.toFixed(4)),
          volContribution:  Number(volContrib.toFixed(4)),
        }))
        .sort((a, b) => b.weight - a.weight)
    
      // ── Region breakdown ─────────────────────────────────────────────────────
      const regionBreakdown = groupBy(
        holdings.map((h) => ({ key: h.region as Region, weight: h.weight })),
      ).map(({ key, weight }) => ({ region: key, weight }))
    
      // ── Sector breakdown ─────────────────────────────────────────────────────
      const sectorBreakdown = groupBy(
        holdings.map((h) => ({
          key: (h.sector ?? h.assetClass) as string,
          weight: h.weight,
        })),
      ).map(({ key, weight }) => ({ sector: key, weight }))
    
      // ── Fixed-income metrics ─────────────────────────────────────────────────
      const portfolioDuration = Number(
        holdings.reduce((acc, h) => acc + h.weight * h.duration, 0).toFixed(2),
      )
      const weightedDividendYield = Number(
        holdings.reduce((acc, h) => acc + h.weight * h.dividendYield, 0).toFixed(4),
      )
    
      const bondWeight = holdings
        .filter((h) => h.assetClass === 'bond')
        .reduce((acc, h) => acc + h.weight, 0)
      const reWeight = holdings
        .filter((h) => h.assetClass === 'real_estate')
        .reduce((acc, h) => acc + h.weight, 0)
    
      // ── Currency exposure ────────────────────────────────────────────────────
      const currencyExposure = groupBy(
        holdings.map((h) => ({ key: h.currency, weight: h.weight })),
      ).map(({ key, weight }) => ({ currency: key, weight }))
    
      return {
        marketBeta: Number(marketBeta.toFixed(4)),
        betaInterpretation,
        assetClassContributions,
        regionBreakdown,
        sectorBreakdown,
        portfolioDuration,
        weightedDividendYield,
        interestRateSensitivity: interestRateSensitivity(portfolioDuration, bondWeight, reWeight),
        currencyExposure,
      }
    }
  • src/index.ts:572-579 (registration)
    The MCP/API tool registration for 'analyze_factors' (via the /analyze/factors route).
    app.post('/analyze/factors', validate, charge('0.01'), async (c) => {
      try {
        return c.json(withMeta(c, analyzeFactorExposure(c.get('portfolio'), c.get('returnSeries'))))
      } catch (err) {
        console.error(err)
        return internalError(c)
      }
    })

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dun999/finsight-mpp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server