Skip to content

Quickstart

The whole stack, one call

RiskManager is the façade over all six components. Build it once from a single RiskConfig, push equity in as your account moves, and ask one question per trade — it keeps the drawdown, session, and open-position state in sync for you.

from riskkit import RiskManager, RiskConfig, TradeIntent

risk = RiskManager(RiskConfig(
    base_risk_pct=1.0, max_notional_pct=4.0,
    drawdown=dict(tier1_pct=3, halt_pct=10),
    session=dict(max_trades_per_day=5),
    correlation=dict(static_groups={"majors": {"BTC/USDT", "ETH/USDT"}}),
))

risk.on_equity(10_000)                              # refresh drawdown/session state
decision = risk.evaluate(TradeIntent(
    symbol="BTC/USDT", side="long",
    entry_price=100.0, stop_price=98.0, target_price=104.0,
    score=82, atr=2.0, atr_baseline=2.0,
))

if decision.ok:
    place(decision.units, decision.stop)           # your execution layer
    risk.on_fill(decision)                          # tell riskkit it filled
else:
    print("skip:", decision.reasons)                # every gate that vetoed it

When a position closes, hand the round-trip back so the session's streak, daily-loss, and cooldown state stays current:

risk.on_close(trade_record, equity_before=10_000)

The full runnable demo is in examples/risk_manager.py. Prefer to wire the components yourself? Everything below still works standalone.

Presets

Don't want to tune every knob? Start from a preset and adjust from there:

from riskkit import RiskConfig, RiskManager

risk = RiskManager(RiskConfig.conservative())   # or .balanced() / .aggressive()
risk = RiskManager(RiskConfig.preset("aggressive"))   # by name

The three presets move together along a risk-appetite axis — conservative risks less per trade, halts on shallower drawdowns, and demands a higher signal score and reward:risk; aggressive loosens all of those (and is still strictly anti-martingale). balanced mirrors the library defaults.

Configs also load from a plain dict or a YAML file, so you can keep risk policy in version control:

RiskConfig.from_dict({"base_risk_pct": 0.75, "drawdown": {"halt_pct": 8}})
RiskConfig.from_yaml("risk.yaml")               # needs riskkit[yaml]

Sizing a trade

from riskkit import PositionSizer, SizingInputs

sizer = PositionSizer(base_risk_pct=1.0, max_risk_pct=1.5, max_notional_pct=4.0)

result = sizer.size(SizingInputs(
    equity=10_000,
    entry_price=100.0,
    stop_price=98.0,     # the stop distance defines your risk per unit
    atr=2.5,             # current volatility
    atr_baseline=2.0,    # "normal" volatility -> scales risk down when choppy
    consecutive_losses=2,
    drawdown_pct=4.0,
))

if result.units > 0:
    print(f"Buy {result.units:.4f} units (risk {result.risk_pct:.2%})")
    print("adjustments:", result.multipliers_applied)
else:
    print("Skip:", result.reason_for_zero)

Adapting to drawdown

from riskkit import DrawdownManager

dm = DrawdownManager(tier1_pct=3, tier2_pct=5, tier3_pct=7, halt_pct=10)

state = dm.update(current_equity)   # call once per equity refresh
if state.halted:
    print("No new trades:", state.reason)
else:
    size = base_size * state.size_multiplier

Wiring it all together by hand

The RiskManager façade above does this wiring for you. To assemble the flow yourself — drawdown posture → sizing → final-gate validation — see examples/pipeline.py.