EventGraph is a small .NET sample that demonstrates an event-driven market quote pipeline using simulated instruments and basket aggregation.
- Simulates stock-like assets with
EquitySource. - Subscribes the same
QuoteSubscriberto both individual quotes and an aggregated basket. - Emits basket updates only after all constituent values are available for the current cycle.
- Validates the graph-node dependency graph at startup and rejects cycles before processing begins.
- Keeps the sample self-contained, deterministic enough for tests, and suitable for public release as a demo project.
flowchart LR
equityDefinition["EquityDefinition"]
rateDefinition["CurrencyRateDefinition"]
basketDefinition["BasketDefinition"]
optionDefinition["EquityOptionDefinition"]
equity["EquitySource [ISpotSourceNode, IVolSourceNode]"]
rate["CurrencyRateSource [IRateSourceNode]"]
equityDefinition -.->|compiled into| equity
rateDefinition -.->|compiled into| rate
equity --> spot["SpotNode [ISpotNode]"]
equity --> volatility["VolatilityNode [IVolNode]"]
rate --> discount["RateCurveNode [IDiscountCurveNode]"]
spot --> forward["ForwardCurveNode [IForwardCurveNode]"]
discount --> forward
forward --> option["EquityOptionNode [IEquityOptionNode]"]
optionDefinition -.->|compiled into| option
volatility --> option
discount --> option
spot -->|one or more constituents| basket["BasketSpotNode [ISpotNode]"]
basketDefinition -.->|compiled into| basket
basket --> basketForward["ForwardCurveNode [IForwardCurveNode]"]
discount --> basketForward
basketForward --> basketOption["EquityOptionNode [IEquityOptionNode]"]
basket --> basketVolatility["BasketVolatilityNode [IVolNode, 30%]"]
basketVolatility --> basketOption
discount --> basketOption
An EquityDefinition JSON definition in EventGraph/graph-definition uses the following fields:
{
"type": "EquityDefinition",
"name": "TSLA",
"currency": "USD",
"spot": 800.0,
"volatility": 0.2,
"meanTickTimeSeconds": 3.0
}Every JSON file in graph-definition/ is a static *Definition specification compiled into live nodes and sources by GraphDefinitionCompiler. Each EquityDefinition compiles into an EquitySource. A BasketDefinition uses name, currency, constituents, and weights; before graph construction, the loader compiles it into a BasketSpotNode and materializes a SpotNode for each constituent. The application loads all JSON definitions from this folder at startup, in filename order. Terminal colors are assigned by QuoteSubscriber, not stored as node properties.
A CurrencyRateDefinition provides a named flat interestRate (for example, 0.02 for 2%) and compiles into a CurrencyRateSource. The loader materializes a dependent RateCurveNode when a forward curve or equity option needs a discount curve. Its DiscountFactor property is a date -> double function implemented as exp(-interestRate * (date - today) / 365).
An EquityOptionDefinition uses underlyer, maturity, strike, and optionType. Before graph construction, the loader compiles it into an EquityOptionNode and materializes its forward, volatility, and discount-curve dependencies. An equity underlyer uses VolatilityNode; a basket underlyer uses BasketVolatilityNode, currently fixed at 30%. The current example uses maturity: "1Y", meaning today plus one year, and sets the strike to the equity's current spot. Pricing uses the forward Black-Scholes form: discountFactor * (forward * N(d1) - strike * N(d2)), with the rate-curve discount factor at maturity.
Graph loading uses Kahn's algorithm to resolve dependencies. The loader builds an in-degree count for each node, processes dependency-free nodes first, and then releases dependent nodes as their prerequisites are created. This keeps startup ordering deterministic while avoiding repeated full scans of unresolved definitions.
A GraphSession coordinates the lifecycle of the active QuoteGraph against an external MarketClock. When the valuation date advances, the session recompiles a new graph, filters out expired contracts, adjusts relative tenors, reconnects subscriptions, and carries over running spot state.
The loaded graph also exposes stable node indices through QuoteGraph, which gives future runtime optimizations a dense representation without replacing the current node model. More specialized graph layouts such as compressed sparse row storage or parallel execution should be considered only after profiling shows that traversal or update propagation is a bottleneck.
Each simulated spot follows a GBM-style lognormal process. The model does this at each step:
- Samples a random time interval from a Poisson distribution.
- Converts the interval into a volatility scaling over a year.
- Applies a normal shock and the Itô drift correction.
- Updates the current value and raises the tick event.
The underlying update is:
This is the standard base-process approach for a spot simulation. It does not add a pricing-specific convexity adjustment, because that is a payoff-level consideration rather than a spot-generation concern.
The basket does not publish on every constituent tick. Instead, it stores the latest values and emits a new basket value only after all constituents have provided an observation for the current cycle.
- .NET 8 SDK
- A terminal with access to the repository root
dotnet run --project EventGraph/EventGraph.csprojCustomise the run with CLI options:
dotnet run --project EventGraph/EventGraph.csproj -- --ticks 3 --quietAvailable options:
--ticks <n>: Number of ticks to emit; defaults to continuous mode until interrupted--quiet: Suppresses subscription and quote output--basket-color <color>: Console color used for basket updates--help: Displays usage information
dotnet test EventGraph.Tests/EventGraph.Tests.csproj --collect:"XPlat Code Coverage" --results-directory ./coverageQuality gate:
./scripts/quality.shThe repo enforces at least 97% line coverage for the production code path.
- Source is organized around a single library and a focused test project.
- Test naming follows production types (
AppOptions,BasketSpotNode,QuoteSubscriber,EquitySource,QuoteTick). - Coverage, quality checks, and clean test execution are included in the repo workflow.
- The project includes a permissive open-source license in the root of the repository.
- No secrets, credentials, or local-only environment artifacts are required to build or run the sample.
- The sample uses Math.NET for random sampling.
- The default execution model is continuous mode unless
--ticksis supplied.