RiskOfficer Portfolio Management
This skill connects to RiskOfficer API to manage investment portfolios and calculate risks.
Setup
- Open RiskOfficer app → Settings → API Keys
- Create new token for "OpenClaw"
- Set environment variable:
RISK_OFFICER_TOKEN=ro_pat_...
Or configure in ~/.openclaw/openclaw.json:
{
"skills": {
"entries": {
"riskofficer": {
"enabled": true,
"apiKey": "ro_pat_..."
}
}
}
}
API Base URL
https://api.riskofficer.tech/api/v1
All requests require header: Authorization: Bearer ${RISK_OFFICER_TOKEN}
Available Commands
Portfolio Management
List Portfolios
When user asks to see their portfolios, list portfolios, or show portfolio overview:
curl -s "https://api.riskofficer.tech/api/v1/portfolios/list" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Response contains array of portfolios with: id, name, total_value, currency, positions_count, broker, sandbox.
Get Portfolio Details
When user asks about a specific portfolio or wants to see positions:
curl -s "https://api.riskofficer.tech/api/v1/portfolio/snapshot/{snapshot_id}" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Response contains: name, total_value, currency, positions (array with ticker, quantity, current_price, value, weight).
Get Portfolio History
When user asks for portfolio history, how portfolio changed over time, or list of past snapshots:
curl -s "https://api.riskofficer.tech/api/v1/portfolio/history?days=30" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Query params: days (optional, default 30, 1–365). Response: snapshots array with snapshot_id, timestamp, total_value, positions_count, sync_source, type (aggregated/manual/broker), name, broker, sandbox.
Get Snapshot Diff (compare two portfolio versions)
When user wants to compare two portfolio states (e.g. before/after rebalance, or two dates):
curl -s "https://api.riskofficer.tech/api/v1/portfolio/snapshot/{snapshot_id}/diff?compare_to={other_snapshot_id}" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Response: added/removed/modified positions, total_value_delta. Both snapshots must belong to the user.
Get Aggregated Portfolio
When user asks for total/combined portfolio, overall position, or "show everything together":
curl -s "https://api.riskofficer.tech/api/v1/portfolio/aggregated?type=all" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Query params:
type=production— manual + broker (sandbox=false)type=sandbox— broker (sandbox=true) onlytype=all— everything (default)
Response:
portfolio.positions— all positions merged across portfoliosportfolio.total_value— total value in base currencyportfolio.currency— base currency (RUB or USD)portfolio.sources_count— number of portfolios aggregated
Example response:
{
"portfolio": {
"positions": [
{"ticker": "SBER", "quantity": 150, "value": 42795, "sources": ["Т-Банк", "Manual"]},
{"ticker": "AAPL", "quantity": 10, "value": 189500, "original_currency": "USD"}
],
"total_value": 1500000,
"currency": "RUB",
"sources_count": 3
},
"snapshot_id": "uuid-of-aggregated"
}
Currency conversion: Positions in different currencies are automatically converted to base currency using current exchange rates (CBR for RUB).
Change Base Currency (Aggregated Portfolio)
When user wants to see aggregated portfolio in different currency:
curl -s -X PATCH "https://api.riskofficer.tech/api/v1/portfolio/{aggregated_snapshot_id}/settings" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"base_currency": "USD"}'
Supported currencies: RUB, USD
After changing, aggregated portfolio recalculates automatically.
User prompt examples:
- "Покажи всё в долларах" → change base_currency to USD
- "Переведи портфель в рубли" → change base_currency to RUB
Include/Exclude from Aggregated
When user wants to exclude a portfolio from total calculation:
curl -s -X PATCH "https://api.riskofficer.tech/api/v1/portfolio/{snapshot_id}/settings" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"include_in_aggregated": false}'
Use cases:
- "Не учитывай песочницу в общем портфеле" → exclude sandbox
- "Убери демо-портфель из расчёта" → exclude manual portfolio
Create Manual Portfolio
When user wants to create a new portfolio with specific positions:
curl -s -X POST "https://api.riskofficer.tech/api/v1/portfolio/manual" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "Portfolio Name",
"positions": [
{"ticker": "SBER", "quantity": 100},
{"ticker": "GAZP", "quantity": 50}
]
}'
IMPORTANT RULE - Single Currency: All assets in a portfolio must be in the same currency.
- RUB assets: SBER, GAZP, LKOH, YNDX, etc.
- USD assets: AAPL, MSFT, GOOGL, etc. Cannot mix! If user tries to mix currencies, explain and suggest creating separate portfolios.
Update Portfolio (Add/Remove Positions)
When user wants to modify an existing portfolio:
- First get current portfolio to find the name:
curl -s "https://api.riskofficer.tech/api/v1/portfolio/snapshot/{snapshot_id}" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
- Then create new snapshot with updated positions (use same name):
curl -s -X POST "https://api.riskofficer.tech/api/v1/portfolio/manual" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "<same name from step 1>",
"positions": [<updated list of all positions>]
}'
IMPORTANT: Always show user what will change and ask for confirmation before updating.
Broker Integration
List Connected Brokers
When user asks about connected brokers or broker status:
curl -s "https://api.riskofficer.tech/api/v1/brokers/connections" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Refresh Portfolio from Tinkoff
When user wants to sync/update portfolio from Tinkoff (broker must be connected via app):
curl -s -X POST "https://api.riskofficer.tech/api/v1/portfolio/proxy/broker/tinkoff/portfolio" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"sandbox": false}'
If response is 400 with missing_api_key, broker is not connected. Explain how to connect:
- Get API token from https://www.tbank.ru/invest/settings/api/
- Open RiskOfficer app → Settings → Brokers → Connect Tinkoff
- Paste token and connect
Risk Calculations
Calculate VaR (FREE)
When user asks to calculate risks, VaR, or risk metrics:
curl -s -X POST "https://api.riskofficer.tech/api/v1/risk/calculate-var" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"portfolio_snapshot_id": "{snapshot_id}",
"method": "historical",
"confidence": 0.95,
"horizon_days": 1,
"force_recalc": false
}'
- Methods:
historical,parametric,garch - force_recalc (optional, default false): If user wants a fresh calculation ignoring cache (e.g. "recalculate VaR", "refresh risk"), set
"force_recalc": true. Otherwise the API may return a cached result when prices have not changed.
This returns calculation_id. Poll for result:
curl -s "https://api.riskofficer.tech/api/v1/risk/calculation/{calculation_id}" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Wait until status is done, then present results. If the POST response already has status: "done" and var_95/cvar_95 (cached result), you can present those without polling.
Get VaR / Risk Calculation History
When user asks for last risk calculations, previous VaR results, or "show my risk history":
curl -s "https://api.riskofficer.tech/api/v1/risk/history?limit=50" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Query params: limit (optional, default 50, max 100).
Response: calculations array with calculation_id, portfolio_snapshot_id, status, method, var_95, cvar_95, sharpe_ratio, created_at, completed_at. Use to show a short list of recent VaR runs or to let user pick a past result.
Run Monte Carlo (QUANT - currently free for all users)
When user asks for Monte Carlo simulation:
curl -s -X POST "https://api.riskofficer.tech/api/v1/risk/monte-carlo" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"portfolio_snapshot_id": "{snapshot_id}",
"simulations": 1000,
"horizon_days": 365,
"model": "gbm"
}'
Poll: GET /api/v1/risk/monte-carlo/{simulation_id}
Run Stress Test (QUANT - currently free for all users)
When user asks for stress test:
First, get available crises:
curl -s "https://api.riskofficer.tech/api/v1/risk/stress-test/crises" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Then run stress test:
curl -s -X POST "https://api.riskofficer.tech/api/v1/risk/stress-test" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"portfolio_snapshot_id": "{snapshot_id}",
"crisis": "covid_19"
}'
Poll: GET /api/v1/risk/stress-test/{stress_test_id}
Portfolio Optimization (QUANT - currently free for all users)
Risk Parity Optimization
When user asks to optimize portfolio or balance risks:
curl -s -X POST "https://api.riskofficer.tech/api/v1/portfolio/{snapshot_id}/optimize" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"optimization_mode": "preserve_directions",
"constraints": {
"max_weight": 0.30,
"min_weight": 0.02
}
}'
Modes:
long_only: All weights ≥ 0preserve_directions: Keep long/short as-isunconstrained: Any direction allowed
Poll: GET /api/v1/portfolio/optimizations/{optimization_id}
Result: GET /api/v1/portfolio/optimizations/{optimization_id}/result
Calmar Ratio Optimization
When user asks for Calmar optimization, maximize Calmar Ratio (CAGR / |Max Drawdown|). Requires 200+ trading days of price history per ticker (backend requests 252 days). If user has short history, suggest Risk Parity instead.
curl -s -X POST "https://api.riskofficer.tech/api/v1/portfolio/{snapshot_id}/optimize-calmar" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"optimization_mode": "long_only",
"constraints": {
"max_weight": 0.50,
"min_weight": 0.05,
"min_expected_return": 0.0,
"max_drawdown_limit": 0.15,
"min_calmar_target": 0.5
}
}'
Poll: GET /api/v1/portfolio/optimizations/{optimization_id} (check optimization_type === "calmar_ratio").
Result: GET /api/v1/portfolio/optimizations/{optimization_id}/result — includes current_metrics, optimized_metrics (cagr, max_drawdown, calmar_ratio, recovery_time_days).
Apply: same as Risk Parity — POST /api/v1/portfolio/optimizations/{optimization_id}/apply.
Apply Optimization
IMPORTANT: Always show rebalancing plan and ask for explicit user confirmation first!
curl -s -X POST "https://api.riskofficer.tech/api/v1/portfolio/optimizations/{optimization_id}/apply" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Subscription Status
Note: Quant subscription is currently FREE for all users. All features work without payment.
Check Subscription
When you need to check if user has Quant subscription:
curl -s "https://api.riskofficer.tech/api/v1/subscription/status" \
-H "Authorization: Bearer ${RISK_OFFICER_TOKEN}"
Currently all users have has_subscription: true (free tier enabled).
Async Operations
VaR, Monte Carlo, Stress Test, and Optimization are asynchronous.
Polling pattern:
- Call POST endpoint → get
calculation_id/simulation_id/optimization_id - Poll GET endpoint every 2-3 seconds
- Check
statusfield:pendingorprocessing→ keep pollingdone→ present resultsfailed→ show error message
Typical times:
| Operation | Typical Time |
|---|---|
| VaR | 3-10 seconds |
| Monte Carlo | 10-30 seconds |
| Stress Test | 5-15 seconds |
| Optimization | 10-30 seconds |
User communication:
- Show "Calculating..." message immediately after starting
- If polling takes >10 seconds, update: "Still calculating... please wait"
- Always show result or error when complete
Important Rules
Single Currency Rule (Manual/Broker portfolios): Each individual portfolio must have same-currency assets. Cannot mix SBER (RUB) with AAPL (USD) in one manual portfolio. Suggest separate portfolios.
Aggregated Portfolio: The aggregated portfolio CAN contain assets in different currencies - they are automatically converted to base currency (RUB or USD) using CBR rates.
Subscription: Monte Carlo, Stress Test, and Optimization are Quant features (currently free for all users). VaR is always FREE.
Broker Integration: User must connect broker in RiskOfficer app first. Cannot connect via chat (security).
Confirmations: Before applying optimizations or making significant portfolio changes, always show what will change and ask for confirmation.
Async Operations: VaR, Monte Carlo, Stress Test, and Optimization are async. Poll for results.
Error Handling:
- 401 Unauthorized → Token invalid or expired, user needs to recreate
- 403 subscription_required → Need Quant subscription (currently free for all)
- 400 missing_api_key → Broker not connected
- 400 currency_mismatch → Mixed currencies
Example Conversations
User wants portfolio overview
User: "Show my portfolios" → Call GET /portfolios/list → Format nicely with values, positions count, last updated
User wants combined/total portfolio
User: "Покажи всё вместе" / "Total portfolio" / "Сколько у меня всего?" → Call GET /portfolio/aggregated?type=all → Show total value, all positions merged, sources count → Note which positions were converted from other currencies
User wants to change display currency
User: "Покажи в долларах" / "Switch to USD" → Call PATCH /portfolio/{aggregated_id}/settings with {"base_currency": "USD"} → Call GET /portfolio/aggregated again → Show portfolio in new currency
User wants to analyze risks
User: "What are the risks of my main portfolio?" → Call GET /portfolios/list to find the portfolio → Call POST /risk/calculate-var → Poll until done → Present VaR, CVaR, volatility, risk contributions → Offer optimization if risks are unbalanced
User wants Calmar optimization
User: "Оптимизируй портфель по Калмару" / "Optimize using Calmar Ratio" / "Maximize return per drawdown" → Call GET /portfolios/list or aggregated to get snapshot_id → Call POST /portfolio/{snapshot_id}/optimize-calmar with optimization_mode and optional constraints → If 400 INSUFFICIENT_HISTORY: explain need 200+ trading days of history, suggest Risk Parity as alternative → Poll GET /optimizations/{id} until status is done → Call GET /optimizations/{id}/result — show current_metrics vs optimized_metrics (Calmar ratio, CAGR, max drawdown) → Show rebalancing plan and ask for confirmation before apply
User tries to mix currencies
User: "Add Apple to my portfolio" → Check portfolio currency (RUB) vs AAPL currency (USD) → Explain cannot mix, suggest creating separate USD portfolio
User requests Monte Carlo or Stress Test
User: "Run Monte Carlo" → Call POST /risk/monte-carlo with portfolio snapshot → Poll until done → Present simulation results with percentiles and projections
User asks for risk or VaR history
User: "Show my last VaR results" / "Previous risk calculations" / "История расчётов рисков" → Call GET /risk/history?limit=50 → Present list of recent calculations (method, var_95, cvar_95, date)
User asks for portfolio history
User: "How did my portfolio change?" / "История портфеля" → Call GET /portfolio/history?days=30 → Present snapshots (date, total_value, positions_count, source)
User wants to compare two portfolio versions
User: "Compare my portfolio now vs last week" / "Что изменилось в портфеле?" → Get two snapshot_ids from GET /portfolio/history (or from context) → Call GET /portfolio/snapshot/{snapshot_id}/diff?compare_to={other_snapshot_id} → Present added/removed/modified positions and value delta