OpenClaw Agent
c411caea17
feat: Position表增加TotalCost字段 + 金融计算单元测试
...
1. Position实体增加TotalCost字段
- 精确追踪卖出后的剩余成本
- 避免用Shares*AvgPrice计算成本的精度问题
2. PortfolioService逻辑更新
- 买入时更新TotalCost
- 卖出时按比例减少TotalCost
- 所有成本计算改用TotalCost字段
3. 增加关键计算步骤日志
- 创建/更新持仓时记录成本变化
- 持仓计算时记录关键数值
4. 新增金融计算单元测试
- 卖出成本计算测试
- 汇率变化影响测试
- 夏普比率计算测试
- 最大回撤计算测试
- 边界情况测试
5. 提供数据库迁移SQL脚本
2026-03-25 04:27:40 +00:00
OpenClaw Agent
d07bede125
fix: 修复 BackfillNavHistoryInternalAsync 中卖出成本计算的严重bug
...
问题:卖出时用全局累计成本计算减少量,而非该标的的成本
修复:使用该标的的成本(转换为目标币种后)计算减少量
同时添加除零安全检查
2026-03-25 04:12:01 +00:00
OpenClaw Agent
82264ecc25
fix: 修复更多金融计算bug
...
1. PortfolioNavService: CalculateAndSaveDailyNavAsync 卖出成本计算
- 使用动态持仓数量字典,而非当前持仓状态
- 确保多次卖出时比例计算正确
2. PortfolioService: GetPortfolioByIdAsync 盈亏率计算
- 先转换汇率再计算盈亏率,避免汇率变化影响
- 正确处理跨币种持仓的盈亏计算
2026-03-25 04:09:55 +00:00
OpenClaw Agent
2a297081b0
fix: 修复多个金融计算bug
...
1. 卖出时累计成本计算:改为按比例减少成本,而非用卖出金额抵扣
2. 夏普比率计算:收益率从百分比转为小数后计算,修正年化公式
3. 最大回撤初始值:使用首条记录的净值作为初始peak,而非硬编码1.0
2026-03-25 04:03:37 +00:00
OpenClaw Agent
650d59aaff
refactor: 统一使用 SqlSugarClient(官方推荐高性能模式)
...
官方建议:
- SqlSugarClient 超高性能模式
- 每次 new 创建实例
- IsAutoCloseConnection=true 自动关闭连接
- 适合 DI Scoped 注入、后台任务、Task.Run 场景
修改:
1. DI 注入改为 Scoped(每次 HTTP 请求一个实例)
2. 移除 SqlSugarScope,统一使用 SqlSugarClient
3. 后台任务创建新实例(已实现)
4. MySQL 连接池复用底层 TCP 连接
优点:
- 性能更好
- 代码更简单
- 符合官方最佳实践
2026-03-25 02:47:00 +00:00
OpenClaw Agent
dc50dfc917
fix: 区分 SqlSugarScope 和 SqlSugarClient 使用场景
...
区分:
- GetSqlSugarScope(): 返回 SqlSugarScope,用于 DI 注入(Singleton)
- GetSqlSugarClient(): 返回 SqlSugarClient,用于后台任务(每次创建新实例)
原因:
- SqlSugarScope 使用 AsyncLocal 隔离上下文
- Task.Run 后台线程中 AsyncLocal 可能无法正确传递
- 后台任务使用 SqlSugarClient 更安全,每个实例独立连接
使用:
- DI 注入:GetSqlSugarScope()
- Repository 后台写入:GetSqlSugarClient()
- MySQL 连接池复用底层 TCP 连接,性能开销小
2026-03-25 02:42:26 +00:00
OpenClaw Agent
cbe0ac9f4a
fix: 每次写入创建新 SqlSugarClient 实例
...
根因分析:
- MarketDataRepository 使用注入的 ISqlSugarClient(Scoped)
- 多个 Task.Run 并发调用 SavePriceCacheAsync
- Storageable 操作使用同一个 SqlSugarScope 实例
- 连接在 await 边界被复用 → 冲突
正确方案:
- SavePriceCacheAsync 每次创建新的 SqlSugarClient 实例
- MySQL 连接池会复用底层 TCP 连接,性能开销很小
- 不再需要 SemaphoreSlim 锁
优点:
- 完全避免连接冲突
- 代码更简洁
- 并发写入无限制
2026-03-25 02:37:18 +00:00
OpenClaw Agent
39808c6d5d
refactor: 调整实时价格数据源优先级
...
调整:
- 实时价格:腾讯 → Yahoo → Tiingo
- 历史K线:Yahoo → Tiingo(不变)
原因:
- Yahoo 经常 429 限流
- 腾讯财经对国内用户更稳定
- 腾讯支持美股 ETF(如 UPRO、TMF)
2026-03-24 10:57:24 +00:00
OpenClaw Agent
f2105eeb3a
fix: 使用 SemaphoreSlim 串行化数据库写入
...
问题:
- 多个后台线程同时写入数据库缓存
- SqlSugar Singleton 连接冲突
修复:
- 添加静态 SemaphoreSlim(1, 1) 写入锁
- 所有数据库写入操作串行化
- 写入失败不影响主流程(内存缓存已生效)
流程:
1. 获取价格成功 → 写入内存缓存(ConcurrentDictionary,线程安全)
2. 后台线程排队等待写入锁
3. 串行写入数据库(避免连接冲突)
4. 写入失败只记录日志
2026-03-24 10:46:40 +00:00
OpenClaw Agent
e78d560f60
fix: 数据库缓存写入改为 Fire-and-Forget
...
问题:
- 多个股票并发写入数据库缓存
- ISqlSugarClient 是 Singleton,共享连接
- Storageable.ExecuteCommandAsync 并发时连接冲突
修复:
1. 先写入内存缓存(确保返回给调用者)
2. 数据库缓存写入改为 Task.Run(Fire-and-Forget)
3. 写入失败只记录日志,不影响主流程
优先级:内存缓存 > 数据库缓存
- 内存缓存:必须成功,直接影响用户体验
- 数据库缓存:可选,失败后下次重新获取
2026-03-24 10:33:37 +00:00
OpenClaw Agent
ad7761810d
fix: 缓存改为 static 解决跨请求并发问题
...
根因:
- MarketDataService 是 Scoped,每个 HTTP 请求有独立实例
- 两个请求同时查询同一股票,各自有自己的 _memoryCache 和 _pendingPriceRequests
- 导致重复查询数据库,连接冲突
修复:
1. _memoryCache 改为 static,跨请求共享
2. _pendingPriceRequests 改为 static,跨请求共享
3. GetOrAdd 正确模式:先创建 Lazy,再 GetOrAdd
流程:
请求1 → static _memoryCache 未命中 → static _pendingPriceRequests.GetOrAdd
请求2 → static _memoryCache 未命中 → static _pendingPriceRequests.GetOrAdd(复用请求1的 Lazy)
↓
只查一次数据库
2026-03-24 10:30:55 +00:00
OpenClaw Agent
5b546061c0
fix: 使用 Lazy 完全解决并发问题
...
问题分析:
1. GetOrAdd 在并发时可能创建多个 Value
2. 两个请求同时进入 GetPriceAsync,都检查内存缓存未命中
3. 然后同时查数据库,导致连接冲突
解决方案:
1. 使用 Lazy<Task<T>> 确保 Value Factory 只执行一次
2. 把整个流程(查数据库缓存 → 获取价格)放在 Lazy 里
3. 内存缓存命中时快速返回,不进入 Lazy
流程:
- 内存缓存命中 → 直接返回
- 内存缓存未命中 → 进入 Lazy(只执行一次)
- 查数据库缓存 → 命中则返回
- 数据库缓存未命中 → 从数据源获取
2026-03-24 10:17:53 +00:00
OpenClaw Agent
51b7372d81
fix: 优化 MySQL 连接池配置
...
问题:Cannot Open when State is Connecting
分析:
- SqlSugarScope 是线程安全的,使用 AsyncLocal 隔离上下文
- Singleton 注册符合官方建议
- 问题可能出在 MySQL 连接池配置
修复:
添加连接池参数:
- Pooling=true(开启连接池)
- MaximumPoolSize=100(最大连接数)
- MinimumPoolSize=5(最小连接数)
- ConnectionTimeout=30(连接超时)
- ConnectionIdleTimeout=180(空闲连接超时,避免使用陈旧连接)
配合内存缓存层,减少数据库查询次数
2026-03-24 10:08:35 +00:00
OpenClaw Agent
8d7e62f8af
fix: SqlSugar 注册改为 Scoped 避免并发冲突
...
问题:
- ISqlSugarClient 注册为 Singleton
- 多个请求共享同一个 SqlSugarScope 实例
- Task.WhenAll 并发查询时连接状态冲突
修复:
- Singleton → Scoped
- 每个 HTTP 请求独立的 SqlSugarScope 实例
- 避免跨请求共享连接对象
配合内存缓存层,双重保障:
1. 内存缓存:减少数据库查询次数
2. Scoped:隔离请求间的连接对象
2026-03-24 09:59:30 +00:00
OpenClaw Agent
9014363d6d
fix: 增强腾讯财经解析和日志
...
改进:
1. 修复引号解析逻辑(更健壮)
2. 减少字段验证从 36 到 5(只需价格字段)
3. 添加详细日志追踪解析过程
4. 记录请求 URL 和原始响应
日志关键词:
- [腾讯财经] 请求URL
- [腾讯财经] 原始响应
- [腾讯财经] 字段数量
- [腾讯财经] 成功
2026-03-24 09:52:25 +00:00
OpenClaw Agent
ec7ed6d686
fix: 添加内存缓存层解决数据库连接池冲突
...
根因:
- Task.WhenAll 并发获取多个股票价格
- 每个价格查询都访问数据库缓存
- SqlSugar 连接状态冲突:Cannot Open when State is Connecting
解决方案:
1. 添加 ConcurrentDictionary 内存缓存层
2. 先查内存缓存,命中则跳过数据库查询
3. 数据库缓存命中后写入内存缓存
4. API 获取成功后同时写入内存缓存
效果:
- 避免并发数据库查询
- 减少数据库连接压力
- 提高响应速度
2026-03-24 09:50:12 +00:00
OpenClaw Agent
89c6ca5397
fix: 增强批量获取价格日志级别
...
- LogWarning → LogError(价格获取失败是严重问题)
- 记录具体错误信息
- 记录 AssetType 用于排查数据源选择问题
2026-03-24 09:45:28 +00:00
OpenClaw Agent
8022731b34
fix: 增强价格获取降级链日志
...
记录每个数据源的尝试和结果:
- [数据源获取开始]
- [数据源] 尝试 Yahoo/腾讯/Tiingo
- [数据源] Yahoo/腾讯/Tiingo 成功/失败
- 具体错误信息
帮助定位 UPRO 等股票获取失败的原因
2026-03-24 09:44:15 +00:00
OpenClaw Agent
3fb2403e85
fix: 增强价格获取稳定性和日志追踪
...
问题排查:
1. 缓存命中时验证价格有效性(Price > 0)
2. 外部 API 返回无效价格时拒绝写入缓存
3. 缓存查询层添加详细日志
4. 捕获缓存查询异常并返回 null
改进:
- 缓存价格 <= 0 时忽略缓存重新获取
- 外部 API 价格 <= 0 时抛出异常,避免污染缓存
- 详细日志追踪价格获取全流程
2026-03-24 09:35:50 +00:00
OpenClaw Agent
3768f6e747
fix: 防止价格获取并发竞态条件
...
问题:多个并发请求同时获取同一股票价格,导致:
1. 外部 API 被重复调用(可能被限流)
2. 部分请求失败导致收益率显示 0%
解决方案:
- 使用 ConcurrentDictionary 存储进行中的请求
- GetOrAdd 模式确保同一时间只有一个请求在获取价格
- 其他并发请求等待第一个请求的结果
- 请求完成后移除 pending 任务
2026-03-24 08:57:45 +00:00
OpenClaw Agent
0579e2f47a
fix: 修复交易时间解析逻辑
...
- TransactionDate 和 TransactionTime 正确组合
- 移除 else if 改为独立 if,支持日期+时间
- CreateTransactionAsync 返回实际交易时间而非当前时间
2026-03-24 08:53:47 +00:00
OpenClaw Agent
7bf687323d
feat: 添加列表接口价格获取详细日志
...
定位组合收益率显示为 0 的问题:
- 记录每个股票的价格获取结果
- 记录价格字典查找情况
- 区分价格获取失败 vs 价格为 0 的情况
2026-03-24 08:39:49 +00:00
OpenClaw Agent
c0accdc903
feat: 用户访问时自动触发当日净值计算
...
修改 GetNavHistoryAsync:
- 检查是否存在今日净值记录
- 如果不存在,自动调用 CalculateAndSaveDailyNavAsync
- 捕获异常避免影响历史数据查询
收益:
- 用户每次访问收益曲线都能获取最新数据
- 无需手动触发或定时任务
2026-03-24 06:22:28 +00:00
OpenClaw Agent
1c3add27ef
fix: 修复收益曲线统计数据与组合详情不一致的问题
...
问题:
- 收益曲线接口 totalReturn 直接取净值历史最后一条的 cumulativeReturn
- 如果净值历史数据过时,统计信息会与当前持仓数据不一致
修复:
- 在 GetNavHistoryAsync 中检查净值历史最后一条是否为今天
- 如果不是,获取当前持仓数据实时计算累计收益率
- 更新 statistics.TotalReturn 为实时值
影响:
- 组合详情 historicalChange 和收益曲线 totalReturn 将保持一致
2026-03-24 06:14:27 +00:00
OpenClaw Agent
6b50d7fc50
refactor: SqlSugarScope注册为Singleton实现线程安全,移除手动信号量限制
2026-03-17 09:26:26 +00:00
OpenClaw Agent
d30c3076bd
fix: 添加数据库并发限制,解决SqlSugar连接状态冲突
2026-03-17 09:19:15 +00:00
OpenClaw Agent
7abb8796ec
fix: 恢复GetTotalAssets同步方法以满足接口定义
2026-03-17 08:23:31 +00:00
OpenClaw Agent
65abd50108
perf: 优化首页接口性能,批量并行获取价格
2026-03-17 08:18:56 +00:00
OpenClaw Agent
b39044bfe1
fix: 复用YahooQuotes实例,添加并发限制防止429错误
2026-03-17 06:56:42 +00:00
OpenClaw Agent
2edac30fd8
feat: Yahoo历史数据根据timeframe和limit动态计算开始日期
2026-03-17 06:48:27 +00:00
OpenClaw Agent
a91041e168
fix: Yahoo股票代码转换,BRK.B → BRK-B(类别股用连字符)
2026-03-17 05:47:27 +00:00
OpenClaw Agent
1830b93207
refactor: 移除腾讯历史K线降级逻辑,添加废弃标记和注释说明
2026-03-17 04:30:53 +00:00
OpenClaw Agent
aa4f63455b
refactor: 优化YahooMarketService,复用实例并改进异常类型
2026-03-17 04:22:00 +00:00
niannian zheng
2a6512ff48
feat(市场数据): 添加Yahoo财经服务并设为优先数据源
...
- 新增YahooMarketService实现股票实时价格和历史数据获取
- 更新MarketDataService优先使用Yahoo服务,腾讯财经降级为第二选择
- 添加YahooQuotesApi依赖并更新相关NuGet包版本
- 补充Yahoo服务测试用例
2026-03-17 12:06:47 +08:00
OpenClaw Agent
5bc318725d
feat: 添加组合更新接口,支持修改名称/策略/状态
2026-03-17 01:50:47 +00:00
OpenClaw Agent
9f82ad0a81
fix: 腾讯历史K线接口已废弃,改进错误处理优雅降级到Tiingo
2026-03-16 09:15:12 +00:00
OpenClaw Agent
79105c339d
fix: 买入后持仓数量和均价正确保存到数据库
2026-03-16 09:08:47 +00:00
OpenClaw Agent
71aa7211d6
fix: 首页组合收益率实时计算
2026-03-16 08:56:14 +00:00
OpenClaw Agent
c7712e57bb
feat: 组合列表增加今日涨跌额和持仓数量
...
- PortfolioListItem 新增 TodayProfit、TodayProfitCurrency 字段
- GetPortfolios 计算今日盈亏(从净值历史)
- Tags 显示持仓数量(如 '运行中 · USD · 3只')
2026-03-15 23:51:32 +00:00
OpenClaw Agent
dcd212efa7
feat: 自动触发净值计算
...
1. 创建组合后,如有持仓则自动计算净值
2. 创建交易后,自动更新当日净值
3. 异步执行,不阻塞主流程
2026-03-15 23:48:11 +00:00
OpenClaw Agent
74e5f85579
refactor: 移除 calculateDailyNav 接口
...
前端暂不需要,后续有需求再加
2026-03-15 14:43:35 +00:00
OpenClaw Agent
f7d346c0ae
fix: API路由重构以匹配前端期望
...
1. 创建交易: POST /api/v1/portfolio/{id}/transactions -> POST /api/v1/portfolio/transactions
- portfolioId 改为在 body 中传递
2. 新增计算净值端点: POST /api/v1/portfolio/{id}/nav-history/calculate
前端 API 格式已完全对齐
2026-03-15 14:35:26 +00:00
OpenClaw Agent
a0817081a8
refactor: 删除冗余的 /api/v1/portfolio/{id}/transactions 路由
...
保留 query parameter 方式: /api/v1/portfolio/transactions?portfolioId=xxx
2026-03-15 14:25:09 +00:00
OpenClaw Agent
7db098b5f9
fix: 添加 /api/v1/portfolio/transactions 路由支持 query parameter
...
前端请求格式: /api/v1/portfolio/transactions?portfolioId=xxx
后端原有路由: /api/v1/portfolio/{id}/transactions
添加 [HttpGet("transactions")] 支持 query parameter 方式
2026-03-15 14:20:11 +00:00
OpenClaw Agent
781f707419
fix: GetPortfolios 返回格式修正为 { items: [...] }
...
前端期望 data 是对象而非数组
2026-03-15 14:10:56 +00:00
OpenClaw Agent
d4c58c8a08
fix: 添加 /api/v1/portfolio/assets 端点
...
- IPortfolioFacade 添加 GetTotalAssetsAsync 方法
- PortfolioFacade 实现 GetTotalAssetsAsync
- PortfolioController 添加 [HttpGet("assets")] 端点(放在 {id} 路由之前)
2026-03-15 13:58:22 +00:00
OpenClaw Agent
41ff4a2135
fix: 修复 CurrentPrice 赋值错误
...
PortfolioService.cs: CurrentPrice 应该赋值为当前价格,而非今日盈亏
2026-03-15 13:49:54 +00:00
niannian zheng
49b9adfdab
fix 字段名称错误
2026-03-15 21:44:48 +08:00
OpenClaw Agent
60f3b487ff
fix: 修复 PortfolioService 和 PortfolioNavService 变量命名
...
- 统一局部变量使用 camelCase(C# 规范)
- 修复 DTO 属性名引用
- NavItem -> NavHistoryItem
- 修复 for 循环变量名
2026-03-15 13:31:49 +00:00
OpenClaw Agent
da12f6d163
fix: 修复编译错误和警告
...
- PortfolioFacade: 使用同步方法 GetStrategyById
- OkxMarketService: 响应模型属性改为可空类型
2026-03-15 13:18:24 +00:00