AI Platform · Machine Learning
InsightAide
AI-driven investment research & analysis platform
The problem
InsightAide is a quantitative investment-research platform built end-to-end by one person on .NET 10. The goal was a system that could ingest market data, engineer features, train and rank models, and layer LLM reasoning on top — with the architectural discipline to stay maintainable as it grew to 25 projects, and the statistical rigor to avoid fooling itself.
- Financial ML is dangerously easy to overfit — a naive backtest leaks future information and looks brilliant until it meets real data.
- LLM costs and vendor lock-in balloon quickly without a routing and accounting layer.
- A solo-built system spanning data, ML, and API surface has to stay modular, or it becomes impossible to change safely.
Architecture
The solution is a strict four-tier clean architecture spanning 25 projects. Dependencies point inward — Presentation depends on the domain engines and application services, which depend on Foundation, never the reverse. Each engine is an isolated, testable unit, so the ML pipeline can evolve without touching the API and vice versa.
Presentation
Entry pointsDomain engines
11 modulesApplication
ServicesFoundation
Core + infrastructureDependencies point downward — inner layers never depend on outer ones.
Engineering in depth
The snippets below are illustrative of the techniques used — simplified to show the approach without exposing proprietary signal logic.
// Route each request to the right provider by model prefix,
// then record token usage + estimated cost for every call.
public async Task<LlmResponse> CompleteAsync(LlmRequest request, CancellationToken ct)
{
ILlmProvider provider = request.Model switch
{
var m when m.StartsWith("claude-") => _anthropic,
var m when m.StartsWith("gpt-") => _openAi,
_ => _default,
};
var response = await provider.CompleteAsync(request, ct);
await _usageLog.RecordAsync(new ApiUsage(
provider: provider.Name,
model: request.Model,
inputTokens: response.Usage.InputTokens,
outputTokens: response.Usage.OutputTokens,
estimatedCostUsd: _pricing.Estimate(request.Model, response.Usage)), ct);
return response;
}// Drop any training row whose label window overlaps the test fold,
// plus an embargo buffer on each side.
private IReadOnlyList<Sample> PurgeAndEmbargo(
IReadOnlyList<Sample> train,
DateRange testFold,
TimeSpan labelWindow,
TimeSpan embargo)
{
var lower = testFold.Start - labelWindow - embargo;
var upper = testFold.End + embargo;
return train
.Where(s => s.Timestamp < lower || s.Timestamp > upper)
.ToList();
}// Persist a model only if its out-of-sample top-vs-bottom spread
// is statistically significant — not merely positive.
var spread = oos.TopDecileReturn - oos.BottomDecileReturn;
var tStat = spread.Mean / spread.StdError;
if (tStat < MinTStat) // MinTStat = 1.0
{
_log.LogInformation(
"Rejecting model: OOS spread t-stat {T:F2} below gate", tStat);
return SaveResult.Rejected;
}
await _registry.SaveAsync(model, metrics, ct);Key decisions
No live trading, by design
Keeping the system research-and-analysis only removes execution risk and regulatory surface entirely, and lets the engineering focus stay on signal quality and evaluation rigor rather than order routing.
In-process ML.NET over a Python microservice
Running LightGBM natively in .NET keeps the whole platform a single deployable — no cross-language serialization, no extra service to operate, no model-server drift between training and inference.
A router in front of the LLMs
Abstracting Claude and OpenAI behind one provider interface avoids vendor lock-in, allows routing by capability and cost, and makes per-call accounting a first-class concern instead of an afterthought.