v2.0

GoRules Version 2 is here - redesigned, now with managed cloud.GoRules Version 2 is here!

Watch the launch videoWatch

Portfolio Risk Monitor

Dynamic portfolio management system that continuously evaluates investment holdings against market conditions to implement appropriate risk mitigation actions.

Solution

This intelligent portfolio monitoring system safeguards investments by continuously evaluating customer holdings against changing market conditions. The system analyzes market volatility, downward trends, and economic indicators to calculate current risk exposure. It assesses portfolio composition, focusing on high-risk asset percentages, current volatility levels, and deviation from target allocations.

Based on comprehensive risk calculations, the system determines appropriate actions using weighted factors from market conditions, portfolio exposure, and volatility metrics. When moderate risk is detected, customized alerts are generated with specific recommendations. For portfolios that have drifted significantly from targets or face high risk, the system suggests precise rebalancing actions with detailed allocation adjustments. In critical scenarios, immediate risk mitigation strategies are implemented, including automatic defensive reallocation for clients who have enabled this feature.

How it works

The decision graph processes portfolio data through a series of specialized evaluation nodes:

  1. Market Condition Assessment: Evaluates current volatility index and market trend percentage to determine overall market conditions and assigns a risk factor.
  2. Portfolio Exposure Analysis: Calculates risk exposure based on high-risk asset percentage and current market conditions.
  3. Volatility Assessment: Measures portfolio volatility against market risk factors to determine susceptibility to market movements.
  4. Risk Score Calculation: Computes a weighted risk score using market, exposure, and volatility factors to categorize overall portfolio risk.
  5. Action Determination: Based on risk category, portfolio drift, and market conditions, the system selects the appropriate response.
  6. Response Execution: Implements one of four actions: generating alerts, suggesting portfolio rebalancing, implementing risk mitigation strategies, or confirming no action is required.

Where teams use it

  • Wealth management firms overseeing client portfolios
  • Robo-advisor platforms providing automated investment services
  • Financial advisory practices managing retirement accounts
  • Investment management companies offering managed funds
  • Private banking services for high-net-worth individuals
  • Self-directed investment platforms with risk monitoring features

Inside the decision model

Portfolio Risk Monitor ships as a JDM decision graph with 10 nodes, 3 decision tables and 17 rules. Download it, load it into GoRules, and run it as-is on Zen Engine.

Decision graph10 nodes · read-only
input requesttable marketConditionsAssessmenttable portfolioExposureAssessmenttable portfolioVolatilityAssessmentexpression calculateRiskScoreswitch determineActionfunction generateAlertfunction suggestRebalancingfunction implementRiskMitigationexpression noActionNeeded
01

Request

input

Three request objects drive the monitor: customer preferences (including allowAutomaticAdjustments), the portfolio with its volatility, highRiskPercentage, and current versus target allocations, and a market snapshot carrying volatilityIndex and trendPercentage.

Sample requestJSON
{
  "customer": {
    "id": "cust-78945",
    "name": "John Smith",
    "riskTolerance": "moderate",
    "investmentHorizon": "long-term",
    "preferences": {
      "allowAutomaticAdjustments": true,
      "alertThreshold": "moderate",
      "communicationPreference": "email"
    }
  },
  "portfolio": {
    "id": "port-12345",
    "name": "Retirement Portfolio",
    "totalValue": 750000,
    "creationDate": "2019-05-12",
    "lastRebalance": 95,
    "volatility": 22.5,
    "highRiskPercentage": 35,
    "currentAllocation": {
      "equity": 65,
      "bonds": 25,
      "cash": 10
    },
    "targetAllocation": {
      "equity": 60,
      "bonds": 35,
      "cash": 5
    },
    "holdings": [
      {
        "symbol": "VTI",
        "category": "equity",
        "percentage": 30,
        "value": 225000
      },
      {
        "symbol": "VXUS",
        "category": "equity",
        "percentage": 20,
        "value": 150000
      },
      {
        "symbol": "VGT",
        "category": "equity",
        "percentage": 15,
        "value": 112500
      },
      {
        "symbol": "BND",
        "category": "bonds",
        "percentage": 25,
        "value": 187500
      },
      {
        "symbol": "CASH",
        "category": "cash",
        "percentage": 10,
        "value": 75000
      }
    ]
  },
  "market": {
    "volatilityIndex": 28.5,
    "trendPercentage": -12.5,
    "interestRate": 3.75,
    "sectorPerformance": {
      "technology": -15.2,
      "healthcare": -5.1,
      "financials": -18.4,
      "consumerStaples": -3.2,
      "utilities": 1.5
    },
    "economicIndicators": {
      "gdpGrowth": 0.8,
      "inflation": 4.2,
      "unemploymentRate": 4.1
    }
  }
}
02

Market Conditions Assessment

table

Market state comes from crossing market.volatilityIndex with market.trendPercentage under a first hit policy: volatility above 30 with a trend below -15 reads 'severe' at a 0.8 risk factor, above 25 with below -10 'high' at 0.7, and above 20 with below -5 'moderate' at 0.5. High volatility with a positive trend above 5 is only 'neutral' at 0.3, single-signal rows cover 'elevated' volatility and a 'negative' trend, and the default is 'normal' at 0.2.

The volatility cutoffs echo how practitioners read the VIX, where sustained values above 30 signal a stressed market, and the -10 to -15 trend bands sit in the range commonly described as a correction. Pairing the two signals matters because volatility alone is ambiguous: the 'neutral' row explicitly downgrades risk when prices are volatile but rising.

Decision tablefirst hit policy
Market Volatility Indexmarket.volatilityIndexMarket Trend Percentmarket.trendPercentageMarket Conditionassessment.marketConditionMarket Risk Factorassessment.marketRiskFactorMarket Assessmentassessment.marketAssessment
> 30< -15'severe'0.8'Highly volatile market with significant downward trend'
> 25< -10'high'0.7'Elevated volatility with substantial market decline'
> 20< -5'moderate'0.5'Increased volatility with market decline'
> 25> 5'neutral'0.3'High volatility but positive market trend'
> 15-'elevated'0.4'Elevated market volatility'
-< -10'negative'0.6'Significant market decline'

+1 more row in the downloadable template

03

Portfolio Exposure Assessment

table

Exposure combines portfolio.highRiskPercentage with the upstream market read: more than 50 percent in high-risk assets during a 'severe' or 'high' market is 'critical' at a 0.9 factor, more than 40 percent in the same conditions 'high' at 0.7, and more than 30 percent whenever assessment.marketCondition != 'normal' is 'elevated' at 0.5. Calmer books fall through to 'moderate' above 20 percent or 'low' at 0.1.

Making concentration conditional on market state is the realistic part: a 45 percent allocation to risky assets is tolerable in quiet markets but becomes the main loss driver in a drawdown, which is why the same percentage maps to different levels. The specific 20/30/40/50 breakpoints are house risk-appetite settings rather than an external standard.

Decision tablefirst hit policy
High-Risk Asset Percentageportfolio.highRiskPercentageMarket Condition CheckExposure Levelassessment.exposureLevelExposure Factorassessment.exposureFactor
> 50assessment.marketCondition == 'severe' or assessment.marketCondition == 'high''critical'0.9
> 40assessment.marketCondition == 'severe' or assessment.marketCondition == 'high''high'0.7
> 30assessment.marketCondition != 'normal''elevated'0.5
> 20-'moderate'0.3
--'low'0.1
04

Portfolio Volatility Assessment

table

Realized portfolio.volatility is judged against the market backdrop: above 30 with a marketRiskFactor over 0.6 rates 'high' at a 0.8 factor, above 25 with a factor over 0.4 'elevated' at 0.6, then unconditional bands step down through 'moderate' above 20, 'low' above 15, and 'minimal' at 0.1 for everything else.

Requiring both a volatile portfolio and a risky market for the top ratings keeps the monitor from over-reacting to a strategy that is volatile by design, such as an equity-heavy long-horizon allocation. The tiered factors feed the weighted score next, so this table effectively decides how much the portfolio's own turbulence contributes to the final category.

Decision tablefirst hit policy
Portfolio Volatilityportfolio.volatilityMarket Risk Factorassessment.marketRiskFactorVolatility Levelassessment.volatilityLevelVolatility Factorassessment.volatilityFactor
> 30> 0.6'high'0.8
> 25> 0.4'elevated'0.6
> 20-'moderate'0.4
> 15-'low'0.2
--'minimal'0.1
05

Calculate Risk Score

expression

A weighted blend produces the headline number: riskScore multiplies the market, exposure, and volatility factors by weights of 0.3, 0.4, and 0.3, giving exposure the largest say. The same node derives portfolioDrift as abs(portfolio.currentAllocation.equity - portfolio.targetAllocation.equity) and buckets riskScore into a riskCategory of 'critical', 'high', 'moderate', or 'low' at the 0.7, 0.5, and 0.3 cutpoints that the switch reads next.

Expressions6 fields
marketWeight0.3
exposureWeight0.4
volatilityWeight0.3
riskScoreassessment.marketRiskFactor * $.marketWeight + assessment.exposureFactor * $.exposureWeight + assessment.volatilityFactor * $.volatilityWeight
portfolioDriftabs(portfolio.currentAllocation.equity - portfolio.targetAllocation.equity)
riskCategory$.riskScore >= 0.7 ? 'critical' : $.riskScore >= 0.5 ? 'high' : $.riskScore >= 0.3 ? 'moderate' : 'low'
06

Determine Action

switch

Four branches resolve in order under a first hit policy: an alert when riskCategory == 'moderate' and the market is not 'normal', a rebalance when portfolioDrift > 10 or a 'high' category coincides with portfolio.lastRebalance > 90 days, mitigation when the category is 'critical' or 'high' in a 'severe' market, and a default no-action path. The ordering means a moderately risky but drifted portfolio alerts rather than rebalances, so branch sequence is part of the policy.

Branches4 paths
riskCategory == 'moderate' and assessment.marketCondition != 'normal' Generate Alert
portfolioDrift > 10 or (riskCategory == 'high' and portfolio.lastRebalance > 90) Suggest Rebalancing
riskCategory == 'critical' or (riskCategory == 'high' and assessment.marketCondition == 'severe') Implement Risk Mitigation
otherwise No Action Needed
07

Generate Alert

function

On the alert branch a notification payload is assembled: alertDetails carries the customer and portfolio ids, a dayjs-formatted date, the riskScore and riskCategory, and a message embedding the market assessment, with recommendedAction fixed at 'Review portfolio allocation'. The outcome block reports status 'alert_generated', which downstream systems can use to log or dispatch the notice.

08

Suggest Rebalancing

function

Getting back to target is expressed as concrete deltas: suggestedChanges subtracts each of portfolio.currentAllocation.equity, bonds, and cash from the matching targetAllocation values, and driftPercentage carries the measured drift into the message. Returning signed adjustments per asset class means an order-management layer can act on the output directly, and the outcome status 'rebalance_suggested' keeps the action auditable.

09

Implement Risk Mitigation

function

Defensive de-risking is computed from the target book: suggestedEquity drops the equity target by 15 points, bonds rise by up to 10, and cash absorbs the remainder so the allocation still sums to 100. The automaticChanges flag only turns true when the market is 'severe', the category 'critical', and the customer has opted in via allowAutomaticAdjustments, so the system never reallocates without consent.

10

No Action Needed

expression

Quiet portfolios exit through this step, which emits an outcome of { status: 'no_action_required' } stamped with the riskScore and a timestamp from date('now'). Recording the score even when nothing happens gives the monitoring history a baseline to compare against on the next run.

Expressions1 fields
outcome{ status: 'no_action_required', riskScore: riskScore, timestamp: string(date('now')) }

Make this template
your own.

Load Portfolio Risk Monitor into GoRules, adjust the rules to your policy, and ship it behind your own API.