A production-grade C# market data provider exposing:
- Spot prices — pulled live from KDB+/q
- Implied volatility — EQBrain spline parameter cache + on-the-fly interpolation
Clients can connect via gRPC (C#, C++), REST/JSON (Excel HTTP), or the bundled Excel-DNA XLL add-in.
┌──────────────────────────────────────────────────────────────────────┐
│ MarketDataService.Server │
│ (ASP.NET Core 8) │
│ │
│ ┌──────────────────────────┐ ┌──────────────────────────────────┐ │
│ │ gRPC SpotPriceService │ │ gRPC VolatilityService │ │
│ │ REST /api/v1/spot/... │ │ REST /api/v1/vol/... │ │
│ └────────────┬─────────────┘ └──────────────┬───────────────────┘ │
│ │ │ │
│ ┌────────────▼──────────┐ ┌──────────────▼───────────────────┐ │
│ │ KdbSpotPriceProvider │ │ VolatilitySurface │ │
│ │ (ISpotPriceProvider) │ │ (IVolatilitySurface) │ │
│ └────────────┬──────────┘ │ ┌─────────────────────────────┐ │ │
│ │ │ │ ConcurrentDictionary cache │ │ │
│ ┌────────────▼──────────┐ │ │ (ticker, expiry) → SplineP │ │ │
│ │ KdbConnection │ │ └──────────────┬──────────────┘ │ │
│ │ (TCP IPC protocol) │ │ │ │ │
│ └────────────┬──────────┘ │ ┌──────────────▼──────────────┐ │ │
│ │ │ │ SplineInterpolator │ │ │
│ │ │ │ NaturalCubic / Akima / SVI│ │ │
│ │ │ └─────────────────────────────┘ │ │
│ │ └──────────────────────────────────┘ │
└───────────────┼──────────────────────────────────────────────────────┘
│
┌──────▼──────┐ ┌─────────────────────┐
│ KDB+/q │ │ EQBrain │
│ (port 5000)│ │ → PushSplineParams │
└─────────────┘ └─────────────────────┘
Clients:
┌─────────────────┐ ┌──────────────────┐ ┌──────────────────────┐
│ C# gRPC client │ │ C++ gRPC client │ │ Excel-DNA XLL │
│ (Grpc.Net) │ │ (grpc++ vcpkg) │ │ =MDSpot("AAPL") │
└─────────────────┘ └──────────────────┘ │ =MDVol("AAPL",0.25,K)│
└──────────────────────┘
MarketDataService/
├── proto/
│ └── market_data.proto # Single source-of-truth IDL
├── src/
│ ├── MarketDataService.Core/ # Domain models + interfaces
│ │ ├── Models/
│ │ │ ├── MarketDataModels.cs # SpotPriceData, VolatilityData
│ │ │ └── SplineParams.cs # Vol surface parameters
│ │ └── Interfaces/
│ │ └── IMarketDataInterfaces.cs
│ ├── MarketDataService.KdbAdapter/
│ │ ├── KdbConnection.cs # KDB IPC TCP client
│ │ ├── KdbDeserializer.cs # Binary → .NET objects
│ │ └── KdbSpotPriceProvider.cs # Implements ISpotPriceProvider
│ ├── MarketDataService.VolEngine/
│ │ ├── SplineInterpolator.cs # NaturalCubic / Akima / SVI math
│ │ └── VolatilitySurface.cs # Cache + interpolation orchestrator
│ └── MarketDataService.Server/
│ ├── GrpcServices/
│ │ ├── SpotPriceGrpcService.cs
│ │ └── VolatilityGrpcService.cs
│ ├── RestControllers/
│ │ └── MarketDataController.cs
│ ├── Program.cs
│ └── appsettings.json
└── clients/
├── excel/
│ ├── MarketDataAddIn.cs # Excel-DNA UDF functions
│ └── MarketData.dna # Excel-DNA manifest
└── cpp/
├── CMakeLists.txt
├── market_data_client.h # Public C++ API
├── market_data_client.cpp
└── example_main.cpp
cd src/MarketDataService.Server
# Edit appsettings.json → set KDB host/port/credentials
dotnet run
# gRPC on https://localhost:7001
# REST on https://localhost:7001/swaggervar channel = GrpcChannel.ForAddress("https://localhost:7001");
var vol = new VolatilityService.VolatilityServiceClient(channel);
await vol.UpdateSplineParamsAsync(new SplineParamsUpdate
{
Ticker = "AAPL",
ExpiryYears = 0.25,
SplineType = "natural_cubic",
Knots = { -0.3, -0.2, -0.1, 0.0, 0.1, 0.2, 0.3 },
Coeffs = { /* 4*(n-1) coefficients */ },
CreatedUtcMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
});var spot = await spotClient.GetSpotPriceAsync(new SpotPriceRequest { Ticker = "AAPL" });
Console.WriteLine($"AAPL = {spot.Price}");
var vol = await volClient.GetVolAsync(new VolRequest
{ Ticker = "AAPL", ExpiryYears = 0.25, Strike = 150.0 });
Console.WriteLine($"AAPL 3M 150 vol = {vol.Vol:P1}");=MDSpot("AAPL") → 182.35
=MDVol("AAPL", 0.25, 150) → 0.2718
md::MarketDataClient client("localhost:7001");
auto spot = client.GetSpot("AAPL");
auto vol = client.GetVol("AAPL", 0.25, 150.0);curl https://localhost:7001/api/v1/spot/AAPL
curl "https://localhost:7001/api/v1/vol/AAPL?expiry=0.25&strike=150"| Type | Description | When to use |
|---|---|---|
Quadratic (default) |
EQBrain primary format: S(x) = a + b·dx + c·dx² | Production — EQBrain push |
NaturalCubic |
Classic cubic spline with natural boundary conditions | Backward compatibility |
Akima |
Akima spline — less oscillatory at outliers | Sparse or noisy input data |
Parameters per segment: [a, b, c]
S_i(x) = a_i + b_i*(x - knot_i) + c_i*(x - knot_i)²
Coefficient array layout (n knots → 3*(n-1) values):
[a₀,b₀,c₀, a₁,b₁,c₁, …, aₙ₋₂,bₙ₋₂,cₙ₋₂]
Vol at intermediate expiries is obtained by linear interpolation in total variance:
w(T) = w(T₁) + (T-T₁)/(T₂-T₁) × (w(T₂)-w(T₁))
σ(T) = √(w(T)/T)
This ensures a non-negative forward variance and no calendar spread arbitrage.
{
"Kdb": {
"Host": "kdb-prod-01",
"Port": 5000,
"User": "marketdata",
"Password": "***",
"TimeoutMs": 5000
},
"KdbSpot": {
"CacheTtlMs": 200
}
}The adapter calls getSpot[TICKER]which should return a mixed list(price;bid;ask;timestamp). Adjust KdbSpotPriceProvider.csandKdbDeserializer.cs` to match your actual KDB function/table names.
cd clients/cpp
cmake -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake
cmake --build build
./build/md_exampleDependencies (via vcpkg): grpc, protobuf
- Replace the minimal KDB IPC parser with kx's official
c.csor a robust library. - Add mTLS between EQBrain → service (set
use_tls=true). - Consider Redis or Apache Ignite for distributed spline param cache across service replicas.
- Add Prometheus metrics via
prometheus-net.AspNetCorefor latency/error monitoring.
The target machine must have:
- .NET Framework 4.8 (pre-installed on Windows 10 1903+)
- Visual Studio 2019 (with .NET Framework 4.8 targeting pack)
No internet, no NuGet, no .NET SDK, no other tools needed.
| Project | NuGet Dependencies |
|---|---|
MarketDataService.Core |
✅ NONE |
MarketDataService.VolEngine |
✅ NONE |
MarketDataService.KdbAdapter |
✅ NONE |
ConsoleRunner (test app) |
✅ NONE (references above 3 projects only) |
These 4 projects compile and run with just the .NET Framework 4.8 DLLs.
| Project | Needs NuGet? |
|---|---|
MarketDataService.Server |
❌ Yes — Grpc.AspNetCore, Swashbuckle, etc. |
MarketDataExcelAddIn |
❌ Yes — Grpc.Net.Client, ExcelDna |
- Copy the entire
MarketDataService/folder to the offline machine - Open
MarketDataService.slnin VS 2019 - In Solution Explorer, right-click
ConsoleRunner→ Set as StartUp Project - Right-click
ConsoleRunner→ Build - Press F5 to run the test suite
The test output shows:
=== MarketDataService — Offline Test Runner ===
QuadraticSpline_Eval ... PASS
QuadraticSpline_Build ... PASS
QuadraticSpline_BuildAndEval ... PASS
CubicSpline_Eval ... PASS
ExpiryInterpolation ... PASS
VolSurface_SingleSlice ... PASS
VolSurface_MultiSlice ... PASS
VolSurface_NotFound ... PASS
FindSegment_Edges ... PASS
Horner_Formula ... PASS
Results: 10 passed, 0 failed, 10 total
-
On a machine with internet, run
pack-offline.bat:cd MarketDataService pack-offline.batThis creates
deploy-offline/MarketDataService/with source +packages/folder. -
Copy
deploy-offline/MarketDataService/to the offline machine -
Open
MarketDataService.slnin VS 2019 -
If VS prompts for NuGet restore — point it to the
packages/folder:- Tools → NuGet Package Manager → Package Manager Settings
- Under "Package Sources", add:
packages/folder path - Right-click Solution → Restore NuGet Packages
-
Build and run Server as normal
| File | Where to set breakpoint | What you're testing |
|---|---|---|
SplineInterpolator.cs line 44 |
EvalQuadraticSpline return |
Verify Horner's formula output |
VolatilitySurface.cs line 129 |
BracketIndex |
Verify expiry bracket lookup |
VolatilitySurface.cs line 135 |
InterpolateExpiryLinearVariance call |
Verify total-variance interpolation |
KdbDeserializer.cs line 30 |
ParseSpotResponse |
When you connect to real KDB |
The ConsoleRunner includes 10 tests. Key ones:
- QuadraticSpline_Eval: exact eval at knots and midpoints
- QuadraticSpline_Build: fit from raw (x,y), verify reproduction
- ExpiryInterpolation: linear total-variance formula correctness
- VolSurface_MultiSlice: full pipeline — cache → bracket → interpolate