One change.
Every answer.
CalcGraph turns business logic into a living dependency graph. Publish once, and every affected value is invalidated, recomputed, and made consistent—automatically.
Watch change find
its consequences.
CalcGraph follows only the affected edges, keeps the order deterministic, and brings the graph back to fresh.
The picture begins
with a definition.
cg-lang, CalcGraph's own language, defines identity, inputs, triggers, and outputs. Modular Python implementations plug in as calculators and run as part of the reactive graph.
source MarketTick {
symbol String @id
price Float
currency String
observed Timestamp
}
source Position {
desk String @id
symbol String @id
quantity Float
}
source VolSurface {
underlying String @id
expiry Date @id
implied_vol Float
}
calc OptionValue {
input { tick MarketTick vol VolSurface }
on { update(tick) update(vol) }
output { value Float }
}
calc Exposure {
input { position Position option_value OptionValue }
on { update(position) update(option_value) }
output {
desk String = position.desk
market_value Float = position.quantity * option_value.value
}
}
calc Greeks {
input { tick MarketTick position Position vol VolSurface }
on { update(tick) update(position) update(vol) }
output {
delta Float
gamma Float
theta Float
vega Float
rho Float
}
}
aggregate DeskRisk {
input { exposure Exposure }
group { exposure.desk }
output { gross_exposure Float = sum(exposure.market_value) }
}
calc MonteCarloVaR {
input { exposure Exposure greeks Greeks risk DeskRisk }
on { update(exposure) update(greeks) update(risk) }
output { var Float expected_shortfall Float }
}
def _norm_cdf(x: float) -> float:
return 0.5 * (1.0 + math.erf(x / math.sqrt(2.0)))
class OptionValue(OptionValue_CalculatorBase):
async def compute(
self,
inputs: OptionValue_Inputs,
assumptions: OptionValue_Assumptions,
) -> OptionValue_Output:
tick = inputs.tick
vol = inputs.vol
spot = float(tick.price)
sigma = float(vol.implied_vol)
strike = 190.0
rate = 0.043
years = 0.25
root_t = math.sqrt(years)
d1 = (
math.log(spot / strike) + (rate + 0.5 * sigma**2) * years
) / (sigma * root_t)
d2 = d1 - sigma * root_t
value = spot * _norm_cdf(d1) - strike * math.exp(-rate * years) * _norm_cdf(d2)
return self.output(value=value)
def _norm_pdf(x: float) -> float:
return math.exp(-0.5 * x * x) / math.sqrt(2.0 * math.pi)
class Greeks(Greeks_CalculatorBase):
async def compute(
self,
inputs: Greeks_Inputs,
assumptions: Greeks_Assumptions,
) -> Greeks_Output:
spot = float(inputs.tick.price)
sigma = float(inputs.vol.implied_vol)
strike = 190.0
rate = 0.043
years = 0.25
root_t = math.sqrt(years)
d1 = (
math.log(spot / strike) + (rate + 0.5 * sigma**2) * years
) / (sigma * root_t)
d2 = d1 - sigma * root_t
density = _norm_pdf(d1)
discount = math.exp(-rate * years)
delta = _norm_cdf(d1)
gamma = density / (spot * sigma * root_t)
theta = (
-(spot * density * sigma) / (2.0 * root_t)
- rate * strike * discount * _norm_cdf(d2)
) / 365.0
vega = spot * density * root_t / 100.0
rho = strike * years * discount * _norm_cdf(d2) / 100.0
return self.output(
delta=delta,
gamma=gamma,
theta=theta,
vega=vega,
rho=rho,
)
PATHS = 100_000
CONFIDENCE = 0.99
class MonteCarloVaR(MonteCarloVaR_CalculatorBase):
async def compute(
self,
inputs: MonteCarloVaR_Inputs,
assumptions: MonteCarloVaR_Assumptions,
) -> MonteCarloVaR_Output:
gross = float(inputs.exposure.market_value)
desk_gross = float(inputs.risk.gross_exposure)
greeks = inputs.greeks
seed = int(abs(gross) * 1_000_000) % (2**32)
rng = np.random.default_rng(seed)
correlation = np.array([
[1.00, -0.35, 0.18],
[-0.35, 1.00, -0.22],
[0.18, -0.22, 1.00],
])
daily_vol = np.array([0.018, 0.012, 0.0008])
cholesky = np.linalg.cholesky(correlation)
shocks = rng.standard_t(df=6, size=(PATHS, 3)) @ cholesky.T
spot_shock, vol_shock, rate_shock = (shocks * daily_vol).T
delta_pnl = gross * float(greeks.delta) * spot_shock
gamma_pnl = 0.5 * gross * float(greeks.gamma) * spot_shock**2
vega_pnl = gross * float(greeks.vega) * vol_shock
rho_pnl = gross * float(greeks.rho) * rate_shock
liquidity = np.maximum(0.0, -spot_shock) * desk_gross * 0.002
losses = -(delta_pnl + gamma_pnl + vega_pnl + rho_pnl) + liquidity
var = float(np.quantile(losses, CONFIDENCE))
tail = losses[losses >= var]
return self.output(
var=var,
expected_shortfall=float(tail.mean()),
)
A graph you can
query like a database.
CalcGraph exposes every source, calculation, and aggregate as a table. Ordinary SQL joins their latest values from one pinned snapshot.
SELECT
mt.symbol,
mt.price AS market_price,
p.quantity AS position,
ov.value AS option_value,
e.market_value AS exposure,
dr.gross_exposure AS desk_risk,
v.var AS monte_carlo_var
FROM market_tick AS mt
JOIN position AS p
ON p.symbol = mt.symbol
JOIN option_value AS ov
ON ov.symbol = mt.symbol
JOIN exposure AS e
ON e.symbol = p.symbol AND e.desk = p.desk
JOIN desk_risk AS dr
ON dr.desk = e.desk
JOIN monte_carlo_var AS v
ON v.symbol = e.symbol AND v.desk = e.desk
WHERE mt.symbol = 'AAPL'
AND p.desk = 'EQUITY-OPTIONS';
| symbol | market_price | position | option_value | exposure | desk_risk | monte_carlo_var |
|---|---|---|---|---|---|---|
| AAPL | $192.84 | +25k | $10.55 | $5.29m | $81.40m | $2.80m |
Subscribe to exactly
what matters.
Match graph events by definition, identity fields, and event kind. CalcGraph replays and delivers the resulting feed over one long-lived, bidirectional gRPC stream.
No glue required.
CalcGraph sits between the data an organization publishes and every application that needs a fresh, consistent answer.
One system owns the dependencies, recompute, history, and delivery.