Quant.Infra.Net 是一个面向 .NET 的量化交易基础设施类库,帮助你用最少的代码完成数据获取、统计分析、交易执行和消息通知。
Quant.Infra.Net is a .NET quantitative trading infrastructure library that lets you fetch data, run statistical analysis, execute trades, and send notifications with minimal code.
在量化交易开发中,你可能反复遇到这些问题:
| 你遇到的问题 | 本库提供的方案 |
|---|---|
| 股票 / 加密货币数据接口分散、格式不统一 | 统一的 ITraditionalFinanceSourceDataService 和 ICryptoSourceDataService,标准化 OHLCV 模型 |
| 配对交易需要 ADF 检验、OLS 回归、Z-Score、相关性分析 | IAnalysisService 提供现成方法,一行代码调用 |
| 对接 Binance、Alpaca 等券商时重复造轮子 | IBinanceUsdFutureService、IUSEquityBrokerService 等统一抽象 |
| 策略运行后缺少通知链路 | 钉钉 IDingtalkService、企业微信 IWeChatService、邮件 EmailService |
| 评估 CAGR、Sharpe、Calmar、最大回撤要自己写 | StrategyPerformanceAnalyzer 内置全部指标 |
| 定时器、滚动窗口每次都重写 | IntervalTrigger、RollingWindow<T> 开箱即用 |
简而言之:不重复造轮子,专注策略本身。
- 数据获取 — 从 Yahoo Finance 下载股票日线,从 Binance 批量下载加密货币 K 线,从 CSV/MySQL/MongoDB 读取本地历史数据。
- 统计分析 — 相关性、ADF 平稳性检验、OLS 线性回归、Z-Score、Shapiro-Wilk 正态性检验、配对交易价差计算。
- 交易执行 — Binance 合约下单 / 清仓、Alpaca 美股下单 / 清仓,支持 Testnet / Paper / Live 环境切换。
- 通知推送 — 钉钉群机器人、企业微信 Webhook、个人邮箱 / 商业邮件批量发送。
- 组合与绩效 — 投资组合快照、净值曲线绘图、CAGR / Sharpe / Calmar / 最大回撤计算。
- 工具类 — 滚动窗口、定时触发器、分辨率转换、DataFrame 读写。
关于数据源的说明
C# 包
YahooFinanceApi的更新速度跟不上 Yahoo Finance API 的频繁变动,经常出现401 Unauthorized等错误。 Python 社区的yfinance包更新更快、更稳定。因此本项目通过pythonnet在 C# 中调用本地 Anaconda 虚拟环境中的yfinance来获取行情数据。
dotnet new console -n MyQuantApp
cd MyQuantApp
dotnet add package Quant.Infra.Net
dotnet add package pythonnet
dotnet add package Microsoft.Extensions.DependencyInjection如果你在仓库内开发,也可以用 ProjectReference:
<ProjectReference Include="..\Quant.Infra.Net\Quant.Infra.Net.csproj" />conda create -n quant python=3.9 -y
conda activate quant
pip install yfinance- 记录虚拟环境路径和 Python DLL 文件名,后续代码中需要用到:
# Windows 示例
环境路径: D:\ProgramData\PythonVirtualEnvs\pair_trading
或 C:\Users\<你的用户名>\miniconda3\envs\quant
Python DLL:python39.dll (对应 Python 3.9)
将以下两个常量修改为你的实际路径:
private const string CondaEnvPath = @"D:\ProgramData\PythonVirtualEnvs\pair_trading";
private const string PythonDllName = "python39.dll";using Microsoft.Extensions.DependencyInjection;
using Python.Runtime;
using Quant.Infra.Net.Analysis.Service;
using Quant.Infra.Net.Shared.Model;
class Program
{
private const string CondaEnvPath = @"D:\ProgramData\PythonVirtualEnvs\pair_trading";
private const string PythonDllName = "python39.dll";
static async Task Main(string[] args)
{
// 1. Register services
var services = new ServiceCollection();
services.AddScoped<IAnalysisService, AnalysisService>();
var provider = services.BuildServiceProvider();
var analysis = provider.GetRequiredService<IAnalysisService>();
// 2. Download AAPL & MSFT via Python yfinance
var end = DateTime.UtcNow;
var start = end.AddYears(-1);
InitializePython();
Console.WriteLine("Downloading AAPL daily OHLCV via yfinance...");
var aaplClose = DownloadCloseViaYFinance("AAPL", start, end);
Console.WriteLine($"AAPL rows: {aaplClose.Count}");
Console.WriteLine("Downloading MSFT daily OHLCV via yfinance...");
var msftClose = DownloadCloseViaYFinance("MSFT", start, end);
Console.WriteLine($"MSFT rows: {msftClose.Count}");
// 3. Correlation
int minLen = Math.Min(aaplClose.Count, msftClose.Count);
aaplClose = aaplClose.Take(minLen).ToList();
msftClose = msftClose.Take(minLen).ToList();
double corr = analysis.CalculateCorrelation(aaplClose, msftClose);
Console.WriteLine($"AAPL vs MSFT correlation: {corr:F4}");
// 4. OLS regression
var (slope, intercept) = analysis.PerformOLSRegression(aaplClose, msftClose);
Console.WriteLine($"OLS regression: Slope={slope:F4}, Intercept={intercept:F4}");
// 5. ADF stationarity test
var spread = msftClose
.Zip(aaplClose, (m, a) => m - slope * a - intercept).ToList();
bool isStationary = analysis.AugmentedDickeyFullerTest(spread, adfTestStatisticThreshold: -2.86);
Console.WriteLine($"Spread ADF stationary: {isStationary}");
// 6. Z-Score
double zScore = analysis.CalculateZScores(spread, spread.Last());
Console.WriteLine($"Latest Z-Score: {zScore:F4}");
await Task.CompletedTask;
}
private static bool _pythonInitialized;
private static readonly object _initLock = new();
private static void InitializePython()
{
if (_pythonInitialized) return;
lock (_initLock)
{
if (_pythonInitialized) return;
var infra = PythonNetInfra.GetPythonInfra(CondaEnvPath, PythonDllName);
Runtime.PythonDLL = infra.PythonDLL;
PythonEngine.PythonHome = infra.PythonHome;
PythonEngine.PythonPath = infra.PythonPath;
PythonEngine.Initialize();
_pythonInitialized = true;
}
}
private static List<double> DownloadCloseViaYFinance(string symbol, DateTime start, DateTime end)
{
using (Py.GIL())
{
dynamic yf = Py.Import("yfinance");
string startStr = start.ToString("yyyy-MM-dd");
string endStr = end.ToString("yyyy-MM-dd");
dynamic df = yf.download(symbol, start: startStr, end: endStr, auto_adjust: true);
dynamic closeSeries = df.__getitem__("Close");
dynamic pyList = closeSeries.values.flatten().tolist();
var result = new List<double>();
foreach (dynamic item in pyList)
result.Add((double)item);
return result;
}
}
}运行:
dotnet run预期输出示例:
Downloading AAPL daily OHLCV via yfinance...
AAPL rows: 251
Downloading MSFT daily OHLCV via yfinance...
MSFT rows: 251
AAPL vs MSFT correlation: 0.9213
OLS regression: Slope=1.8472, Intercept=23.5610
Spread ADF stationary: True
Latest Z-Score: -0.3172
var dataService = provider.GetRequiredService<ITraditionalFinanceSourceDataService>();
var symbols = await dataService.GetSp500SymbolsAsync();
Console.WriteLine($"S&P 500 成分股数量: {symbols.Count()}");var futureService = provider.GetRequiredService<IBinanceUsdFutureService>();
futureService.ExchangeEnvironment = ExchangeEnvironment.Testnet; // 先用测试网
// 查询余额
decimal balance = await futureService.GetusdFutureAccountBalanceAsync();
Console.WriteLine($"账户余额: {balance}");
// 按比例建仓
await futureService.SetUsdFutureHoldingsAsync("BTCUSDT", 0.10, PositionSide.Long);
// 查看持仓
var positions = await futureService.GetHoldingPositionAsync();
Console.WriteLine($"持仓数量: {positions.Count()}");
// 清仓
await futureService.LiquidateUsdFutureAsync("BTCUSDT");var broker = provider.GetRequiredService<IUSEquityBrokerService>();
broker.ExchangeEnvironment = ExchangeEnvironment.Paper; // 模拟盘
decimal equity = await broker.GetAccountEquityAsync();
Console.WriteLine($"账户权益: {equity}");
await broker.SetHoldingsAsync("AAPL", 0.05m); // 5% 仓位
await broker.LiquidateAsync("AAPL"); // 清仓// 钉钉
var dingtalk = provider.GetRequiredService<IDingtalkService>();
await dingtalk.SendNotificationAsync("策略信号:买入 AAPL", accessToken, secret);
// 企业微信
var wechat = provider.GetRequiredService<IWeChatService>();
await wechat.SendTextNotificationAsync("订单已成交", webHookUrl);var analysis = provider.GetRequiredService<IAnalysisService>();
// 相关性
double corr = analysis.CalculateCorrelation(seriesA, seriesB);
// OLS 回归:diff = B - Slope * A - Intercept
var (slope, intercept) = analysis.PerformOLSRegression(seriesA, seriesB);
// ADF 平稳性检验(返回 bool)
bool isStationary = analysis.AugmentedDickeyFullerTest(spread);
// ADF 平稳性检验(返回详细结果,需要 Python 环境)
AdfTestResult adfResult = analysis.AugmentedDickeyFullerTestPython(spread);
// Z-Score
double z = analysis.CalculateZScores(spread, currentValue);
// Shapiro-Wilk 正态性检验
bool isNormal = analysis.PerformShapiroWilkTest(spread);// 滚动窗口:保持最近 20 根 K 线
var window = new RollingWindow<double>(20);
window.Add(100.5);
window.Add(101.2);
// ... 持续添加
if (window.IsReady)
{
Console.WriteLine($"窗口已满,共 {window.Count} 个元素");
}
// 定时触发器:每小时整点前 1 分钟触发
var trigger = new IntervalTrigger(StartMode.NextHour, TimeSpan.FromMinutes(-1));
trigger.IntervalTriggered += (sender, e) =>
{
Console.WriteLine($"触发! {DateTime.UtcNow}");
};
trigger.Start();// marketValueDict: Dictionary<DateTime, decimal> — 每日净值
double cagr = StrategyPerformanceAnalyzer.CalculateCAGR(marketValueDict);
double sharpe = StrategyPerformanceAnalyzer.CalculateSharpeRatio(marketValueDict, riskFreeRate);
double calmar = StrategyPerformanceAnalyzer.CalculateCalmarRatio(marketValueDict);
double maxDD = StrategyPerformanceAnalyzer.CalculateMaximumDrawdown(values);
Console.WriteLine($"CAGR={cagr:P2}, Sharpe={sharpe:F2}, Calmar={calmar:F2}, MaxDD={maxDD:P2}");cd src
dotnet restore
dotnet build
dotnet test运行指定测试类:
dotnet test --filter "FullyQualifiedName~AnalysisServiceTests"Quant.Infra.Net/
├── Analysis/ # 统计分析:ADF、OLS、相关性、Z-Score、配对交易
├── Broker/ # 券商接入:Binance、Alpaca、InteractiveBrokers
├── Notification/ # 通知:钉钉、企业微信、邮件
├── Order/ # 订单模型
├── Portfolio/ # 投资组合快照、绩效分析、净值曲线
├── Shared/ # 公共模型、枚举、RollingWindow、IntervalTrigger、UtilityService
└── SourceData/ # 数据采集:Yahoo Finance、Binance、CSV、MySQL、MongoDB
- ADF Python 模式:
AugmentedDickeyFullerTestPython方法依赖本机 Python 环境,需安装numpy、pandas、statsmodels。如果没有 Python 环境,请使用AugmentedDickeyFullerTest(纯 .NET 实现)。 - API Key 配置:Binance / Alpaca 等实盘接口需要在
appsettings.json或 User Secrets 中配置 API Key 和 Secret。 - ExchangeEnvironment:支持
Testnet(测试网)、Paper(模拟盘)、Live(实盘)三种环境,建议开发阶段使用 Testnet 或 Paper。 - Binance IP 限制:Binance API 对部分国家/地区的 IP 存在访问限制,如果你在运行 Binance 相关单元测试时遇到连接错误,这并非代码问题,请查阅 Binance 官方文档 了解受限地区列表。
- 合规免责声明:本项目仅提供量化交易基础设施工具,不构成任何投资建议。用户需自行确保使用本库时符合所在国家/地区的法律法规及交易所合规要求,因使用本库产生的任何法律或财务后果由用户自行承担。
Quant.Infra.Net is a professional quantitative trading infrastructure framework built on .NET 8. It is designed to provide a robust foundation for quant developers, covering market data ingestion, advanced statistical analysis, order execution, and automated monitoring.
When developing quantitative trading systems, you likely face these recurring problems:
| Problem | What this library provides |
|---|---|
| Stock / crypto data APIs are scattered with inconsistent formats | Unified ITraditionalFinanceSourceDataService and ICryptoSourceDataService with standardized OHLCV models |
| Pair trading requires ADF test, OLS regression, Z-Score, correlation | IAnalysisService provides ready-to-use methods in one line |
| Integrating Binance, Alpaca, etc. means rewriting boilerplate | IBinanceUsdFutureService, IUSEquityBrokerService unified abstractions |
| No notification pipeline after strategy runs | DingTalk IDingtalkService, WeChat IWeChatService, Email EmailService |
| CAGR, Sharpe, Calmar, max drawdown must be coded from scratch | StrategyPerformanceAnalyzer with all metrics built in |
| Timers and rolling windows rewritten every project | IntervalTrigger, RollingWindow<T> ready to use |
In short: stop reinventing the wheel — focus on your strategy.
- Data Acquisition — Download stock daily bars from Yahoo Finance, batch-download crypto klines from Binance, read local data from CSV/MySQL/MongoDB.
- Statistical Analysis — Correlation, ADF stationarity test, OLS regression, Z-Score, Shapiro-Wilk normality test, pair-trading spread calculation.
- Trade Execution — Binance futures order/liquidate, Alpaca US equity order/liquidate, with Testnet/Paper/Live environment switching.
- Notifications — DingTalk bot, WeChat Work webhook, personal/commercial bulk email.
- Portfolio & Performance — Portfolio snapshots, equity curve charting, CAGR/Sharpe/Calmar/MaxDrawdown calculation.
- Utilities — Rolling window, interval trigger, resolution conversion, DataFrame I/O.
Note on Data Sources
The C# package
YahooFinanceApicannot keep up with Yahoo Finance's frequent API changes and often returns401 Unauthorized. The Pythonyfinancepackage is maintained more actively by a large community. This project usespythonnetto callyfinancefrom C# via a local Anaconda virtual environment.
dotnet new console -n MyQuantApp
cd MyQuantApp
dotnet add package Quant.Infra.Net
dotnet add package pythonnet
dotnet add package Microsoft.Extensions.DependencyInjectionOr use a ProjectReference when developing inside the repo:
<ProjectReference Include="..\Quant.Infra.Net\Quant.Infra.Net.csproj" />Why Python? The C# package
YahooFinanceApicannot keep up with Yahoo Finance's frequent API changes and often returns401 Unauthorized. The Pythonyfinancepackage is maintained more actively by a large community. This project usespythonnetto callyfinancefrom C# via a local Anaconda virtual environment.
conda create -n quant python=3.9 -y
conda activate quant
pip install yfinance- Note the environment path and Python DLL filename for the code:
# Windows example
Env path: D:\ProgramData\PythonVirtualEnvs\pair_trading
or C:\Users\<you>\miniconda3\envs\quant
Python DLL: python39.dll (for Python 3.9)
Set these two constants to match your environment:
private const string CondaEnvPath = @"D:\ProgramData\PythonVirtualEnvs\pair_trading";
private const string PythonDllName = "python39.dll";using Microsoft.Extensions.DependencyInjection;
using Python.Runtime;
using Quant.Infra.Net.Analysis.Service;
using Quant.Infra.Net.Shared.Model;
class Program
{
private const string CondaEnvPath = @"D:\ProgramData\PythonVirtualEnvs\pair_trading";
private const string PythonDllName = "python39.dll";
static async Task Main(string[] args)
{
// 1. Register services
var services = new ServiceCollection();
services.AddScoped<IAnalysisService, AnalysisService>();
var provider = services.BuildServiceProvider();
var analysis = provider.GetRequiredService<IAnalysisService>();
// 2. Download AAPL & MSFT via Python yfinance
var end = DateTime.UtcNow;
var start = end.AddYears(-1);
InitializePython();
Console.WriteLine("Downloading AAPL daily OHLCV via yfinance...");
var aaplClose = DownloadCloseViaYFinance("AAPL", start, end);
Console.WriteLine($"AAPL rows: {aaplClose.Count}");
Console.WriteLine("Downloading MSFT daily OHLCV via yfinance...");
var msftClose = DownloadCloseViaYFinance("MSFT", start, end);
Console.WriteLine($"MSFT rows: {msftClose.Count}");
// 3. Correlation
int minLen = Math.Min(aaplClose.Count, msftClose.Count);
aaplClose = aaplClose.Take(minLen).ToList();
msftClose = msftClose.Take(minLen).ToList();
double corr = analysis.CalculateCorrelation(aaplClose, msftClose);
Console.WriteLine($"AAPL vs MSFT correlation: {corr:F4}");
// 4. OLS regression
var (slope, intercept) = analysis.PerformOLSRegression(aaplClose, msftClose);
Console.WriteLine($"OLS regression: Slope={slope:F4}, Intercept={intercept:F4}");
// 5. ADF stationarity test
var spread = msftClose
.Zip(aaplClose, (m, a) => m - slope * a - intercept).ToList();
bool isStationary = analysis.AugmentedDickeyFullerTest(spread, adfTestStatisticThreshold: -2.86);
Console.WriteLine($"Spread ADF stationary: {isStationary}");
// 6. Z-Score
double zScore = analysis.CalculateZScores(spread, spread.Last());
Console.WriteLine($"Latest Z-Score: {zScore:F4}");
await Task.CompletedTask;
}
private static bool _pythonInitialized;
private static readonly object _initLock = new();
private static void InitializePython()
{
if (_pythonInitialized) return;
lock (_initLock)
{
if (_pythonInitialized) return;
var infra = PythonNetInfra.GetPythonInfra(CondaEnvPath, PythonDllName);
Runtime.PythonDLL = infra.PythonDLL;
PythonEngine.PythonHome = infra.PythonHome;
PythonEngine.PythonPath = infra.PythonPath;
PythonEngine.Initialize();
_pythonInitialized = true;
}
}
private static List<double> DownloadCloseViaYFinance(string symbol, DateTime start, DateTime end)
{
using (Py.GIL())
{
dynamic yf = Py.Import("yfinance");
string startStr = start.ToString("yyyy-MM-dd");
string endStr = end.ToString("yyyy-MM-dd");
dynamic df = yf.download(symbol, start: startStr, end: endStr, auto_adjust: true);
dynamic closeSeries = df.__getitem__("Close");
dynamic pyList = closeSeries.values.flatten().tolist();
var result = new List<double>();
foreach (dynamic item in pyList)
result.Add((double)item);
return result;
}
}
}Run:
dotnet runExpected output:
Downloading AAPL daily OHLCV via yfinance...
AAPL rows: 251
Downloading MSFT daily OHLCV via yfinance...
MSFT rows: 251
AAPL vs MSFT correlation: 0.9213
OLS regression: Slope=1.8472, Intercept=23.5610
Spread ADF stationary: True
Latest Z-Score: -0.3172
var dataService = provider.GetRequiredService<ITraditionalFinanceSourceDataService>();
var symbols = await dataService.GetSp500SymbolsAsync();
Console.WriteLine($"S&P 500 count: {symbols.Count()}");var futureService = provider.GetRequiredService<IBinanceUsdFutureService>();
futureService.ExchangeEnvironment = ExchangeEnvironment.Testnet;
decimal balance = await futureService.GetusdFutureAccountBalanceAsync();
Console.WriteLine($"Balance: {balance}");
await futureService.SetUsdFutureHoldingsAsync("BTCUSDT", 0.10, PositionSide.Long);
var positions = await futureService.GetHoldingPositionAsync();
Console.WriteLine($"Positions: {positions.Count()}");
await futureService.LiquidateUsdFutureAsync("BTCUSDT");var broker = provider.GetRequiredService<IUSEquityBrokerService>();
broker.ExchangeEnvironment = ExchangeEnvironment.Paper;
decimal equity = await broker.GetAccountEquityAsync();
Console.WriteLine($"Account equity: {equity}");
await broker.SetHoldingsAsync("AAPL", 0.05m);
await broker.LiquidateAsync("AAPL");var dingtalk = provider.GetRequiredService<IDingtalkService>();
await dingtalk.SendNotificationAsync("Signal: Buy AAPL", accessToken, secret);
var wechat = provider.GetRequiredService<IWeChatService>();
await wechat.SendTextNotificationAsync("Order filled", webHookUrl);var analysis = provider.GetRequiredService<IAnalysisService>();
double corr = analysis.CalculateCorrelation(seriesA, seriesB);
var (slope, intercept) = analysis.PerformOLSRegression(seriesA, seriesB);
bool isStationary = analysis.AugmentedDickeyFullerTest(spread);
AdfTestResult adfResult = analysis.AugmentedDickeyFullerTestPython(spread);
double z = analysis.CalculateZScores(spread, currentValue);
bool isNormal = analysis.PerformShapiroWilkTest(spread);var window = new RollingWindow<double>(20);
window.Add(100.5);
window.Add(101.2);
if (window.IsReady)
Console.WriteLine($"Window full, count={window.Count}");
var trigger = new IntervalTrigger(StartMode.NextHour, TimeSpan.FromMinutes(-1));
trigger.IntervalTriggered += (sender, e) =>
{
Console.WriteLine($"Triggered at {DateTime.UtcNow}");
};
trigger.Start();double cagr = StrategyPerformanceAnalyzer.CalculateCAGR(marketValueDict);
double sharpe = StrategyPerformanceAnalyzer.CalculateSharpeRatio(marketValueDict, riskFreeRate);
double calmar = StrategyPerformanceAnalyzer.CalculateCalmarRatio(marketValueDict);
double maxDD = StrategyPerformanceAnalyzer.CalculateMaximumDrawdown(values);
Console.WriteLine($"CAGR={cagr:P2}, Sharpe={sharpe:F2}, Calmar={calmar:F2}, MaxDD={maxDD:P2}");cd src
dotnet restore
dotnet build
dotnet testRun a specific test class:
dotnet test --filter "FullyQualifiedName~AnalysisServiceTests"Quant.Infra.Net/
├── Analysis/ # Statistical analysis: ADF, OLS, correlation, Z-Score, pair trading
├── Broker/ # Broker integration: Binance, Alpaca, InteractiveBrokers
├── Notification/ # Notifications: DingTalk, WeChat Work, Email
├── Order/ # Order models
├── Portfolio/ # Portfolio snapshots, performance analysis, equity curves
├── Shared/ # Common models, enums, RollingWindow, IntervalTrigger, UtilityService
└── SourceData/ # Data: Yahoo Finance, Binance, CSV, MySQL, MongoDB
- ADF Python mode:
AugmentedDickeyFullerTestPythonrequires a local Python environment withnumpy,pandas, andstatsmodels. If Python is not available, useAugmentedDickeyFullerTest(pure .NET implementation). - API Key configuration: Binance / Alpaca live trading requires API Key and Secret in
appsettings.jsonor User Secrets. - ExchangeEnvironment: Supports
Testnet,Paper, andLive. Use Testnet or Paper during development. - Binance IP Restrictions: The Binance API restricts access from certain countries/regions. If you encounter connection errors when running Binance-related unit tests, this is not a code issue. Please refer to the Binance official documentation for the list of restricted regions.
- Compliance Disclaimer: This project provides quantitative trading infrastructure tools only and does not constitute investment advice. Users are solely responsible for ensuring compliance with all applicable laws, regulations, and exchange rules in their jurisdiction. The authors assume no liability for any legal or financial consequences arising from the use of this library.
See LICENSE for details.