A real-time portfolio risk engine built on OTP. It ingests trades, tracks
per-portfolio cash exposure, volatility, and drawdown, and broadcasts risk
snapshots and threshold alerts over Phoenix.PubSub.
https://github.com/user-attachments/assets/3b64e6c2-2890-4e60-a704-0cc567862154
Features
- Per-portfolio processes — each portfolio is an isolated
GenServer, started on demand and supervised independently, so one crashing portfolio never affects another. - Risk math — running cash exposure, trade-notional volatility, and
drawdown from peak exposure, recomputed after every trade
(
RiskEngine.RiskCalculator). - Live broadcasting — risk snapshots and threshold alerts are published
over
Phoenix.PubSub(RiskEngine.RiskBroadcaster) for any subscriber to consume. - Built-in trade simulator —
RiskEngine.Simulatorgenerates random trades out of the box, so the system is observable without wiring up a real feed.
Installation
Add risk_engine to your list of dependencies in mix.exs:
def deps do
[
{:risk_engine, "~> 0.1.0"}
]
endThen fetch dependencies:
mix deps.get
Usage
Submit a trade — the owning portfolio process is started automatically the first time its id is seen:
trade = %{portfolio_id: "acct_1", symbol: "AAPL", side: :buy, qty: 10, price: 189.32}
RiskEngine.TradeIngestion.submit(trade)
#=> :ok
RiskEngine.Portfolio.get_state("acct_1")
#=> %{id: "acct_1", cash_exposure: 1893.2, peak_exposure: 1893.2, ...}Subscribe to risk updates and alerts over PubSub:
Phoenix.PubSub.subscribe(RiskEngine.PubSub, "portfolio:risk")
Phoenix.PubSub.subscribe(RiskEngine.PubSub, "alerts")
receive do
{:risk_update, snapshot} -> IO.inspect(snapshot)
{:alert, alert} -> IO.inspect(alert)
endConfiguration
Risk thresholds, topics, and the built-in simulator are configurable per
environment in config/config.exs:
| Config key | Default | Purpose |
|---|---|---|
RiskEngine.RiskCalculator, :window | 10 | Number of recent trades used for volatility |
RiskEngine.RiskBroadcaster, :risk_topic | "portfolio:risk" | PubSub topic for risk snapshots |
RiskEngine.RiskBroadcaster, :alerts_topic | "alerts" | PubSub topic for threshold alerts |
RiskEngine.RiskBroadcaster, :drawdown_threshold | 20.0 | Drawdown % that triggers an alert |
RiskEngine.RiskBroadcaster, :volatility_threshold | 5_000.0 | Volatility that triggers an alert |
RiskEngine.Simulator, :tick_interval | 1_000 (ms) | How often the simulator submits a trade |
RiskEngine.Simulator, :portfolio_ids | ["acct_1", "acct_2", "acct_3"] | Portfolio ids the simulator trades against |
RiskEngine.Simulator, :symbols | ["AAPL", "TSLA", "GOOG", "MSFT"] | Symbols the simulator trades |
Project structure
risk_engine/
├── config/
│ └── config.exs # Runtime config: thresholds, topics, simulator
├── lib/
│ ├── risk_engine.ex
│ └── risk_engine/
│ ├── application.ex # Supervision tree
│ ├── portfolio.ex # Client API for a portfolio process
│ ├── portfolio/
│ │ └── server.ex # GenServer callbacks for portfolio state
│ ├── portfolio_supervisor.ex # DynamicSupervisor, one process per portfolio
│ ├── risk_calculator.ex # Pure volatility/drawdown math
│ ├── risk_broadcaster.ex # PubSub topics, thresholds, broadcasting
│ ├── simulator.ex # Random trade generator for dev/demo
│ └── trade_ingestion.ex # Validates and ingests trades
├── test/
│ ├── risk_engine/ # One test file per lib module
│ └── test_helper.exs
├── CHANGELOG.md
├── LICENSE
├── mix.exs
└── README.mdDevelopment
Clone the repo and fetch dependencies:
git clone https://github.com/Null-logic-0/risk_engine.git
cd risk_engine
mix deps.get
Running tests
mix test
Running docs
Documentation is generated with ExDoc:
mix docs
This builds HTML documentation into doc/index.html — open it directly in
a browser, or serve it locally:
open doc/index.html
Once published, the same docs are available at https://hexdocs.pm/risk_engine.
Formatting
mix format --check-formatted
Contributing
Contributions are welcome. To submit a change:
Fork the repository and create a topic branch off
main.Make your change, adding or updating tests under
test/to cover it.Run the checks locally before opening a PR:
mix format --check-formatted mix compile --warnings-as-errors mix testOpen a pull request describing the change and the reasoning behind it.
Please keep pull requests focused — prefer several small, reviewable PRs over one large one. Bug reports and feature requests are welcome via GitHub Issues.
License
MIT — see LICENSE for details.