AssetManager.API
1. 卖出时累计成本计算:改为按比例减少成本,而非用卖出金额抵扣 2. 夏普比率计算:收益率从百分比转为小数后计算,修正年化公式 3. 最大回撤初始值:使用首条记录的净值作为初始peak,而非硬编码1.0 |
||
|---|---|---|
| AssetManager.API | ||
| AssetManager.Data | ||
| AssetManager.Infrastructure | ||
| AssetManager.Models | ||
| AssetManager.Services | ||
| AssetManager.Tests | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AssetManager.sln | ||
| DEPLOY.md | ||
| docker-compose.yml | ||
| Dockerfile | ||
| README.md | ||
AssetManager Backend (个人资产策略管理系统)
基于 .NET 8 + MySQL + SqlSugar 构建的高性能资产管理系统后端。采用 Database First (数据库优先) 模式开发,专注于量化策略管理与资产分析。
🛠 技术栈 (Tech Stack)
| 模块 | 技术选型 | 说明 |
|---|---|---|
| 核心框架 | .NET 8 Web API | 最新长期支持版,高性能 |
| 数据库 | MySQL 8.0+ | 稳定、开源的关系型数据库 |
| ORM | SqlSugar | 哪怕是 .NET 老手也爱用的国产轻量级 ORM |
| 架构模式 | Repository Pattern | 仓储模式,解耦业务与数据访问 |
| 接口文档 | Swagger / Knife4j | 在线接口调试 |
| 测试框架 | xUnit + Moq + FluentAssertions | 单元测试与 Mock |
🏗️ 架构设计 (Architecture)
分层架构
┌─────────────────────────────────────────────────────────────┐
│ API Layer (Controllers) │
│ 路由、认证、参数校验、响应封装 │
├─────────────────────────────────────────────────────────────┤
│ Service Layer (Facade) │
│ IPortfolioFacade, IMarketDataService... │
│ 业务逻辑编排、事务管理、权限校验 │
├─────────────────────────────────────────────────────────────┤
│ Repository Layer (数据访问) │
│ IPortfolioRepository, IMarketDataRepository │
│ 数据持久化、查询封装、缓存管理 │
├─────────────────────────────────────────────────────────────┤
│ Infrastructure Layer (基础设施) │
│ TencentMarketService, TiingoMarketService, OkxMarketService
│ 外部API调用、数据源路由、降级策略 │
└─────────────────────────────────────────────────────────────┘
市场数据服务架构(组合模式)
MarketDataService
│
┌──────────────────┼──────────────────┐
▼ ▼ ▼
TencentMarketService TiingoMarketService OkxMarketService
(腾讯财经-优先) (Tiingo-降级) (OKX-加密货币)
│ │ │
└──────────────────┴──────────────────┘
│
IMarketDataRepository
(缓存层)
设计原则
- 单一职责:每个 Service/Repository 只负责一个业务领域
- 依赖倒置:Service 依赖抽象接口,不依赖具体实现
- 开闭原则:新增数据源只需实现接口,无需修改现有代码
- 降级策略:腾讯财经 → Tiingo 自动降级,保证高可用
📂 项目结构 (Project Structure)
AssetManager.API
├── AssetManager.API # [入口层] Controllers, Middleware, IOC配置
├── AssetManager.Services # [业务层] PortfolioService, StrategyService, Facade
├── AssetManager.Data # [数据层] Entities, Repositories, DatabaseService
├── AssetManager.Models # [模型层] DTOs, Enums, Constants
├── AssetManager.Infrastructure # [基础层] MarketDataService, ExchangeRateService
└── AssetManager.Tests # [测试层] 单元测试, Mock测试
核心文件说明
AssetManager.Data/Repositories/
├── IPortfolioRepository.cs # 组合仓储接口
├── IMarketDataRepository.cs # 市场数据仓储接口
├── PortfolioRepository.cs # 组合仓储实现
└── MarketDataRepository.cs # 市场数据仓储实现
AssetManager.Infrastructure/Services/
├── TencentMarketService.cs # 腾讯财经数据源(免费无限制)
├── TiingoMarketService.cs # Tiingo数据源(美股)
├── OkxMarketService.cs # OKX数据源(加密货币)
└── MarketDataService.cs # 组合模式整合
AssetManager.Services/
├── IPortfolioFacade.cs # 组合门面接口
├── PortfolioFacade.cs # 组合门面实现
├── IPortfolioService.cs # 组合服务接口
└── PortfolioService.cs # 组合服务实现
📊 策略引擎 (Strategy Engine)
功能介绍
策略引擎是系统的核心组件,负责根据配置的策略参数计算交易信号。支持以下三种策略类型:
- 双均线策略 (ma_trend) - 经典的趋势跟踪策略,通过短期均线和长期均线的交叉产生买卖信号
- 吊灯止损策略 (chandelier_exit) - 趋势跟踪止损策略,通过计算最高价/最低价和 ATR(平均真实波幅)来设置止损止盈位
- 风险平价策略 (risk_parity) - 资产配置策略,通过调整各资产权重使每个资产对组合的风险贡献相等
策略配置示例
1. 双均线策略 (ma_trend)
{
"maType": "SMA", // 均线类型:SMA(简单移动平均) / EMA(指数移动平均)
"shortPeriod": 20, // 短期均线周期
"longPeriod": 60 // 长期均线周期
}
2. 吊灯止损策略 (chandelier_exit)
{
"period": 22, // 周期(通常为 22)
"multiplier": 3.0, // ATR 倍数(通常为 3.0)
"useClose": false // 是否使用收盘价计算(false 表示用最高价/最低价)
}
3. 风险平价策略 (risk_parity)
{
"lookbackPeriod": 60, // 历史数据回看周期
"rebalanceThreshold": 0.05, // 再平衡阈值(偏离度超过 5% 触发再平衡)
"assets": [ // 目标资产列表(可选,不指定则使用当前持仓)
{ "symbol": "AAPL", "targetWeight": 0.6 },
{ "symbol": "BTC/USD", "targetWeight": 0.4 }
]
}
📡 API 示例
🎯 策略 API (Strategy API)
1. 获取策略列表
请求 URL: GET /api/v1/strategy/strategies
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"data": [
{
"Id": "12345678-1234-1234-1234-1234567890ab",
"Title": "双均线策略",
"Type": "ma_trend",
"Description": "经典趋势跟踪策略",
"RiskLevel": "medium",
"Tags": ["趋势", "均线"],
"Parameters": {
"maType": "SMA",
"shortPeriod": 20,
"longPeriod": 60
},
"UserId": "user-123",
"CreatedAt": "2024-01-01T10:00:00",
"UpdatedAt": "2024-01-01T10:00:00"
},
{
"Id": "87654321-4321-4321-4321-210987654321",
"Title": "风险平价策略",
"Type": "risk_parity",
"Description": "资产配置策略",
"RiskLevel": "low",
"Tags": ["资产配置", "风险控制"],
"Parameters": {
"lookbackPeriod": 60,
"rebalanceThreshold": 0.05,
"assets": [
{ "symbol": "AAPL", "targetWeight": 0.6 },
{ "symbol": "BTC/USD", "targetWeight": 0.4 }
]
},
"UserId": "user-123",
"CreatedAt": "2024-01-02T10:00:00",
"UpdatedAt": "2024-01-02T10:00:00"
}
],
"message": "Success"
}
2. 获取单个策略详情
请求 URL: GET /api/v1/strategy/{id}
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"data": {
"Id": "12345678-1234-1234-1234-1234567890ab",
"Title": "双均线策略",
"Type": "ma_trend",
"Description": "经典趋势跟踪策略",
"RiskLevel": "medium",
"Tags": ["趋势", "均线"],
"Parameters": {
"maType": "SMA",
"shortPeriod": 20,
"longPeriod": 60
},
"UserId": "user-123",
"CreatedAt": "2024-01-01T10:00:00",
"UpdatedAt": "2024-01-01T10:00:00"
},
"message": "Success"
}
3. 创建策略
请求 URL: POST /api/v1/strategy
请求头:
Authorization: Bearer {token}
Content-Type: application/json
请求体示例:
3.1 创建双均线策略
{
"name": "双均线策略",
"type": "ma_trend",
"description": "经典趋势跟踪策略",
"riskLevel": "medium",
"tags": ["趋势", "均线"],
"parameters": {
"maType": "SMA",
"shortPeriod": 20,
"longPeriod": 60
}
}
3.2 创建风险平价策略
{
"name": "风险平价策略",
"type": "risk_parity",
"description": "资产配置策略",
"riskLevel": "low",
"tags": ["资产配置", "风险控制"],
"parameters": {
"lookbackPeriod": 60,
"rebalanceThreshold": 0.05,
"assets": [
{ "symbol": "AAPL", "targetWeight": 0.6 },
{ "symbol": "BTC/USD", "targetWeight": 0.4 }
]
}
}
3.3 创建吊灯止损策略
{
"name": "吊灯止损策略",
"type": "chandelier_exit",
"description": "趋势跟踪止损策略",
"riskLevel": "high",
"tags": ["止损", "趋势"],
"parameters": {
"period": 22,
"multiplier": 3.0,
"useClose": false
}
}
响应示例:
{
"code": 200,
"data": {
"Id": "12345678-1234-1234-1234-1234567890ab",
"Title": "双均线策略",
"Type": "ma_trend",
"Description": "经典趋势跟踪策略",
"RiskLevel": "medium",
"Tags": ["趋势", "均线"],
"Parameters": {
"maType": "SMA",
"shortPeriod": 20,
"longPeriod": 60
},
"UserId": "user-123",
"CreatedAt": "2024-01-01T10:00:00",
"UpdatedAt": "2024-01-01T10:00:00"
},
"message": "Strategy created successfully"
}
4. 更新策略
请求 URL: PUT /api/v1/strategy/{id}
请求头:
Authorization: Bearer {token}
Content-Type: application/json
请求体示例:
{
"name": "双均线策略(更新)",
"type": "ma_trend",
"description": "经典趋势跟踪策略",
"riskLevel": "medium",
"tags": ["趋势", "均线"],
"parameters": {
"maType": "EMA",
"shortPeriod": 15,
"longPeriod": 50
}
}
响应示例:
{
"code": 200,
"data": {
"Id": "12345678-1234-1234-1234-1234567890ab",
"Title": "双均线策略(更新)",
"Type": "ma_trend",
"Description": "经典趋势跟踪策略",
"RiskLevel": "medium",
"Tags": ["趋势", "均线"],
"Parameters": {
"maType": "EMA",
"shortPeriod": 15,
"longPeriod": 50
},
"UserId": "user-123",
"CreatedAt": "2024-01-01T10:00:00",
"UpdatedAt": "2024-01-15T14:30:00"
},
"message": "Strategy updated successfully"
}
5. 删除策略
请求 URL: DELETE /api/v1/strategy/{id}
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"data": {
"id": "12345678-1234-1234-1234-1234567890ab",
"status": "deleted"
},
"message": "Strategy deleted successfully"
}
错误响应格式
所有策略接口在发生错误时都会返回统一的错误格式:
{
"code": 401,
"data": null,
"message": "用户未授权"
}
常见错误码:
401: 用户未授权404: 策略不存在500: 服务器内部错误
💼 投资组合 API (Portfolio API)
1. 创建投资组合
请求 URL: POST /api/v1/portfolio
请求头:
Authorization: Bearer {token}
Content-Type: application/json
请求体示例:
{
"name": "我的投资组合",
"strategyId": "strategy-123",
"currency": "USD",
"stocks": [
{
"name": "Apple Inc.",
"code": "AAPL",
"price": 150.50,
"amount": 100,
"date": "2024-01-01",
"currency": "USD"
},
{
"name": "Microsoft Corp.",
"code": "MSFT",
"price": 300.25,
"amount": 50,
"date": "2024-01-01",
"currency": "USD"
}
]
}
响应示例:
{
"code": 200,
"data": {
"id": "port-abc12345",
"totalValue": 30075.00,
"returnRate": 0.0,
"currency": "USD",
"createdAt": "2024-01-01 10:30:00"
},
"message": "success"
}
2. 获取投资组合列表
请求 URL: GET /api/v1/portfolio
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"data": {
"items": [
{
"id": "port-abc12345",
"name": "我的投资组合",
"tags": "科技股",
"status": "运行中",
"statusType": "active",
"iconChar": "P",
"iconBgClass": "bg-blue-500",
"iconTextClass": "text-white",
"value": 30075.00,
"currency": "USD",
"returnRate": 0.15,
"returnType": "positive"
},
{
"id": "port-def67890",
"name": "保守组合",
"tags": "债券",
"status": "运行中",
"statusType": "active",
"iconChar": "C",
"iconBgClass": "bg-green-500",
"iconTextClass": "text-white",
"value": 50000.00,
"currency": "USD",
"returnRate": 0.08,
"returnType": "positive"
}
]
},
"message": "success"
}
3. 获取总资产情况
请求 URL: GET /api/v1/portfolio/assets
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"data": {
"totalValue": 80075.00,
"currency": "USD",
"todayProfit": 1250.50,
"todayProfitCurrency": "USD",
"totalReturnRate": 0.12
},
"message": "success"
}
4. 获取单个投资组合详情
请求 URL: GET /api/v1/portfolio/{id}
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"data": {
"id": "port-abc12345",
"name": "我的投资组合",
"currency": "CNY",
"status": "记录中",
"strategy": {
"id": "strategy-123",
"name": "HFEA 风险平价逻辑",
"description": "目标权重 季度调仓"
},
"portfolioValue": 156240.00,
"totalReturn": 0.4282,
"todayProfit": 1240.50,
"historicalChange": 42.82,
"dailyVolatility": 1240.50,
"todayProfitCurrency": "CNY",
"logicModel": "HFEA 风险平价逻辑",
"logicModelStatus": "监控中",
"logicModelDescription": "目标权重 季度调仓",
"totalItems": 2,
"totalRatio": 100.0,
"positions": [
{
"id": "pos-xyz12345",
"stockCode": "UPRO",
"stockName": "UPRO",
"symbol": "UPRO.US",
"amount": 142,
"averagePrice": 500.00,
"currentPrice": 605.00,
"totalValue": 85932.00,
"profit": 12400.00,
"profitRate": 0.248,
"changeAmount": 12400.00,
"ratio": 55.0,
"deviationRatio": 16.8,
"currency": "CNY"
},
{
"id": "pos-xyz67890",
"stockCode": "TMF",
"stockName": "TMF",
"symbol": "TMF.US",
"amount": 800,
"averagePrice": 90.00,
"currentPrice": 87.89,
"totalValue": 70308.00,
"profit": -3200.50,
"profitRate": -0.0445,
"changeAmount": -3200.50,
"ratio": 45.0,
"deviationRatio": -4.3,
"currency": "CNY"
}
]
},
"message": "success"
}
5. 获取交易记录
请求 URL: GET /api/v1/portfolio/transactions
请求头:
Authorization: Bearer {token}
查询参数:
portfolioId(必填): 投资组合IDlimit(可选): 每页记录数,默认 10offset(可选): 偏移量,默认 0
请求示例:
GET /api/v1/portfolio/transactions?portfolioId=port-abc12345&limit=10&offset=0
响应示例:
{
"code": 200,
"data": {
"items": [
{
"id": "trans-abc12345",
"portfolioId": "port-abc12345",
"date": "2024-02-14",
"time": "14:30",
"type": "buy",
"title": "定期定投 UPRO录入增加",
"amount": 500.00,
"currency": "USD",
"status": "completed",
"remark": "定期定投"
},
{
"id": "trans-def67890",
"portfolioId": "port-abc12345",
"date": "2024-01-01",
"time": "09:15",
"type": "sell",
"title": "季度再平衡 TMF结出减少 200股",
"amount": 200.00,
"currency": "USD",
"status": "completed",
"remark": "季度再平衡"
},
{
"id": "trans-ghi78901",
"portfolioId": "port-abc12345",
"date": "2023-12-15",
"time": "10:00",
"type": "buy",
"title": "建仓买入录入增加",
"amount": 100000.00,
"currency": "CNY",
"status": "completed",
"remark": "建仓买入"
},
{
"id": "trans-jkl23456",
"portfolioId": "port-abc12345",
"date": "2023-12-10",
"time": "11:20",
"type": "buy",
"title": "建仓买入录入增加",
"amount": 50000.00,
"currency": "CNY",
"status": "completed",
"remark": "建仓买入"
}
],
"total": 4,
"page": 1,
"pageSize": 10
},
"message": "success"
}
6. 创建交易
请求 URL: POST /api/v1/portfolio/transactions
请求头:
Authorization: Bearer {token}
Content-Type: application/json
请求体示例:
{
"portfolioId": "port-abc12345",
"type": "buy",
"stockCode": "AAPL",
"amount": 50,
"price": 155.00,
"currency": "USD",
"remark": "加仓"
}
响应示例:
{
"code": 200,
"data": {
"id": "trans-xyz12345",
"totalAmount": 7750.00,
"status": "processing",
"createdAt": "2024-01-15 14:30:00"
},
"message": "success"
}
错误响应格式
所有接口在发生错误时都会返回统一的错误格式:
{
"code": 401,
"data": null,
"message": "用户未授权"
}
常见错误码:
401: 用户未授权500: 服务器内部错误