Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -408,4 +408,4 @@ FodyWeavers.xsd
.secrets
.env
*.pem
*.key
*.key.zcode/
58 changes: 58 additions & 0 deletions .zcode/plans/plan-sess_10d07dfd-a04e-4c14-94ad-f8d9cc4be040.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Func 注入收敛为具名工厂 + 无意义注释清理(已逐项验证)

## 已验证的事实基础

- 7 个 `Func<MarketType, T>` 注册(App/ServiceCollectionExtensions.cs:61-74),8 个 ViewModel 消费,调用点全部传 `_marketContext.CurrentMarket`。
- 4 个服务(HomeAsset/Favorite/AssetHistory/AssetCache)用 `[ServiceKey] MarketType` 按市场参数化实例(FavoriteService.cs:24 等 4 处),**必须保留按市场解析**——不能改直接注入。
- 页面 Func ×7(:85-91),MainWindowViewModel.cs:17-23 消费,`Func<TradingPageViewModel>? = null` 为坏味道。
- SettingsPageViewModel 注入 4 个懒加载 Func(:34-42),用于向量化与保存确认。
- `Func<MarketMonitor>`(App.Services:381)打破循环依赖,TradingEnvironmentService.cs:17 消费。
- Rag `Func<IRagIngestionService>`(Rag/Extensions:58)。
- 测试桩仅 tests/Application/HomeSearchViewModelTest.cs:30 一处。

---

## 第一部分:Func 注入收敛

### 1. `Func<MarketType, T>` ×7 → `IMarketServiceRegistry`
- 新建接口(App.Services Applications 层),7 个强类型方法,各带 `MarketType` 参数:`GetKLineService / GetAssetInfoService / GetNewsUpdateService / GetHomeAssetService / GetAssetHistoryService / GetFavoriteService / GetAssetCacheService`。
- 实现内部 `GetRequiredKeyedService<T>(marketType)`;注册到 `AddApplicationServices()`,删除 7 个 Func 注册。
- 8 个 ViewModel 的 `Func<MarketType, T>` 参数统一替换为该接口(AssetPageViewModel、AssetSelectionPageViewModel、PriceAlertPageViewModel、FavoritesPageViewModel、HomeSearchViewModel、HotAssetsViewModel、RecentAssetsViewModel、TelegraphNewsViewModel)。
- 更新 HomeSearchViewModelTest 的桩为假注册表。

### 2. 页面 ViewModel Func ×7 → `IPageViewModelFactory`
- 新建 `IPageViewModelFactory`(`T Create<T>() where T : ViewModelBase`),App 层实现。
- MainWindowViewModel 注入单一工厂,删 7 个 Func 字段与可选 Func 参数;`NavigationService.NavigateTo<TViewModel>` 改用该工厂(消除其 IServiceProvider 服务定位)。
- 删 `AddViewModels` 中 7 个 Func 注册,页面 Transient 注册不变。

### 3. SettingsPageViewModel 4 Func → 2 具名接口
- `IRagInfrastructureProvider`(GetEmbeddingFactory/GetVectorStore/GetIngestionService):实现保留延迟解析语义;删 `Func<IEmbeddingFactory>`、`Func<VectorStore>` 注册。
- `IMarketMonitorProvider`(GetMonitor):替换 `Func<MarketMonitor>`,TradingEnvironmentService 与 SettingsPageViewModel 切换;循环依赖打破语义不变。

### 4. 顺带修复服务定位器
- `ClipImageEmbeddingService.cs:68`:移除 IServiceProvider 依赖(实现时按 `_chat` 实际用法选聊天工厂或窄接口注入)。
- `InvestmentSelectionWorkflow.cs:86-92`:新建 `IInvestmentExecutorFactory`(按 MarketType 提供 CriteriaExecutor 及其余 3 个 Executor)替代每次 Run 的服务定位。
- 同步清理该文件的"【学习要点】【实现细节】"教学式注释。

### 5. 保留不动的合理 Func
重试包裹(GlobalExceptionHandler/ViewModelBase/ToolExecutor/SqliteServiceBase)、`ThrottledExecuteAsync`、`TradeExecutor.ConfirmationRequested` 事件、`TextChunkingService` 策略参数、测试桩。

---

## 第二部分:无意义注释清理(Func 收敛后进行)

按 AGENTS.md 规范(仅函数级文档注释 + 晦涩逻辑说明):

- **A. 方法名翻译式 /// summary(~200 处)**:`Agents/Tools/Abstractions/*.cs`(~30 处)、`AnalysisEnums.cs` 枚举成员单词翻译(~50 处)、`NewsEventAnalysisResult.cs`、9 处 `/// 构造函数`、各 ViewModel/Service 复述式 summary、`DocxMarkdownConverter.cs`。
- **B. 复述下一行代码的行注释(~70 处)**:`CryptoAssetInfoService.cs`(~10 处)、`KLineChartView.cs`(~8)、`TelegraphNewsViewModel/HotAssetsViewModel`(~11)、`BinanceMarketDataService`、`ScreenInvestmentTargetsExecutor` 等零散处。
- **C. 教学式注释**:`ClipImageEmbeddingService.cs` 的【学习要点】【实现细节】段。
- **保留**:并发/释放原理等高质量说明、设计依据注释(如"对齐设计系统裁决 #6")、Colors/TextStyles.axaml 设计系统注释;#region 本轮不动。

---

## 验证与执行顺序

1. Func 收敛分步(1→2→3→4),每步 `dotnet build MarketAssistant.slnx -c Debug`。
2. 注释清理按项目分批(Agents → App.Services → App → Rag/DataProviders),完成后再次 build。
3. DI 改动面大,最终跑一次 `dotnet test tests/TestMarketAssistant.csproj -c Debug`。
4. 不做 git 提交(未获要求)。
61 changes: 61 additions & 0 deletions .zcode/plans/plan-sess_8fc947a7-0f60-4bee-8091-04b858439e94.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# MarketAssistant 重构修复计划(全量 P0+P1+P2)

基于审计报告 + 三个并行源码核实。P0 16 条中 13 条完全属实、3 条部分准确(细节修正已并入下文)、无虚警;P1 抽查发现 2 条虚警(#17、#30,**从计划中剔除**);核实另发现 4 个报告遗漏的 sibling 问题,已纳入对应阶段。

**既有决策**(沿用):WebView HTML 渲染默认禁用;交易域死代码直接删除整块。

---

## 阶段 0 — 文档勘误(先落 docs/代码审计报告-2026-08-28.md)
- 修正 P0-3:并非零节流(`MarketMonitor.cs:272` 已有失败冷却);Paused 已正确写入(L447),缺的是用户可见通知。
- 修正 P0-16:泄漏根因是 P0-14 导致旧 VM 连 Dispose 都不发生,时序与报告所述相反。
- 修正 P0-13 严重度:模态框滞留为 UX 级问题。
- P1-17("批量嵌入"实为逐个)与 P1-30(停止按钮不可达)标记为虚警删除。
- 补充 4 个新 sibling:TradeExecutor.cs:247 stopPrice 硬编码 null;InvestmentSelection 三个 Executor 均吞取消;零向量破坏 `null=不可用` 契约;OnMarketChanged 需 disposed 防护的其余 4 个 VM。
- 修正路径偏差:Singleton 注册在 `App.Services/Services/ServiceCollectionExtensions.cs`。

## 阶段 1 — 资金安全(P0-1~5 + 放大器 + sibling #1)
1. **删除原生条件单整套**(按决策):`StrategyEngine.cs:484,491-559,564-582`、`TradingDataService.cs:297-307,1348`、`TradingStrategy.cs:56-60`、`strategies.native_order_id` 列及迁移;在合约止损策略创建路径加"客户端轮询执行、进程退出期间不生效"显式警示。
2. **TradeExecutor 重试循环**(P0-2):改为 `IsTransient(ex)` 递归判定(HttpRequestException/TimeoutException/SocketException/非取消 TaskCanceledException,含 InnerException 链),catch filter 用之;L320 失败分类复用同一函数。同时修 sibling:FriendlyException(inner=HttpRequestException)应归 Network。
3. **TradingCredentialStore._cache**(P0-5):改 `ConcurrentDictionary`,`SetCredentials`/`ClearCredentials` 用 `AddOrUpdate`/`TryRemove`。
4. **StrategyEngine/MarketMonitor 节流**(P0-3 修正版):触发成功后按策略类型统一加 `LastTriggeredAt` 间隔节流;`StopLoss/TakeProfit/TrailingStop` 触发成功置 `Completed`;`MaxExecutions==null` 兜底视为 1;`PauseStrategyAfterRejectionAsync` 发用户可见通知(复用 `_notificationService`)。
5. **环境切换竞态**(P0-4):`TradeExecutor` 入口把 `_exchangeClient` 快照为局部变量,风控/杠杆/下单/撤单全用快照;下单前再校验 mode 与快照一致,不一致拒绝;`_currentMode` 加 `volatile`;`ApplyModeAsync` 在 StopAsync 超时(`MarketMonitor.cs:184`)时中止切换并报错。
6. **风控路径禁用 3 秒缓存**(放大器):`RiskManager.cs:63`、`AISignalStrategyExecutor.cs:189` 改走实时查询(缓存仅 UI 展示用);`RiskManager.cs:138-142` fail-open 改 fail-closed。
7. **sibling**:若保留条件单的映射逻辑不再需要(已删除整块),`TradeExecutor.cs:247` 随删除一并清理。

## 阶段 2 — RAG 数据污染(P0-6~9 + sibling #3)
1. **TextCleaningService**(P0-6):删除 `RepeatingChars` 规则;`PhonePattern` 改为带边界断言的中国手机号语义 `(?<![\d,.])(?:\+?86[- ]?)?1[3-9]\d{9}(?![\d])`,删北美通用规则(保留现有 `ChinesePhonePattern` 合并);拆 `Clean()` → `Normalize()`(无损,摄取路径用)+ `Denoise()`(有损);摄取管线 `DocumentBlockMapper.cs:56/84/114` 只调 `Normalize()`;接上 `IsCleaningSuccessful` 做兜底,超阈值记 Error 并保留原文。
2. **回归测试**:`成交额1000000000元`、`营收10000000元`、`13800138000`、`哈哈哈哈`、`1,000,000`(应不变)。
3. **ClipImageEmbeddingService**(P0-9):`PreprocessToTensor` 的 catch 改为 `throw new InvalidOperationException($"图像预处理失败: {ex.Message}", ex)`;删除 L76/L312/L318 过期注释;确认零向量契约恢复(null=不可用)。
4. **CryptoMetricsTools**(P0-8):6 处 `decimal.Parse` 统一抽 `ParseDec(JsonNode?)` 带 `NumberStyles.Float, InvariantCulture`;`days = Math.Clamp(days, 3, 365)`;阈值改 `Candles.Count < 3`;方差前 `if (returns.Count < 2) throw new FriendlyException(...)`。
5. **AnalyzeAssetsExecutor + sibling**(P0-7):三个 Executor(AnalyzeAssets/ScreenInvestmentTargets/GenerateCriteria)统一加 `catch (OperationCanceledException) { throw; } catch (FriendlyException) { throw; }`,其余异常包装 `FriendlyException` 上抛(innerException 保留,对齐 `ScreenInvestmentTargetsExecutor.cs:68-72` 的正确写法);`InvestmentSelectionWorkflow.cs:150` 改 `throw new FriendlyException("工作流未返回结果,分析未完成")`。

## 阶段 3 — UI 生命周期与线程(P0-10~16 + sibling #4)
1. **App.axaml.cs**:`OnFrameworkInitializationCompleted` 显式 `GetRequiredService<TradeConfirmationService>()` 激活 HITL(P0-10)。
2. **确认对话框**(P0-13):`IDialogService.ShowConfirmationAsync` 增 `CancellationToken` 重载,内部 `ct.Register(() => Dispatcher.UIThread.Post(dialog.Close))`;TradeConfirmationService 用 `CancellationTokenSource(60s)`,catch OCE 走超时拒绝路径。
3. **AssetPageViewModel**(P0-15):删 `Task.Run`(L274 改 `_ = LoadAssetDataAsync(...)`),简化 L192 冗余 `InvokeAsync`。
4. **MainWindow**(P0-14):拆 `SelectedMainNavigationItem`/`SelectedBottomNavigationItem`;`SwitchToMarket` 先缓存 `currentTitle` 到局部变量,切完按 title 查找 item 并导航。
5. **SettingsPageViewModel**(P0-11):`InitializeAsync` 改 `UserSetting = CurrentSetting.Clone()` 草稿模式;`SaveAsync` 时 `UpdateSettings(UserSetting.Clone())` 提交;修正 L369-371/L388 注释;`UserSetting` 补 `Clone()` 方法。
6. **RichTextBlock**(P0-12,按决策默认禁用):`DetectContentFormat` 不再自动识别 HTML,仅 Markdown/PlainText;保留显式 `Format=Html` 分支入口但注释标注需白名单消毒+CSP 后才可启用。
7. **TelegraphNewsViewModel**(P0-16 修正版):`OnMarketChanged` lambda 首行 `if (_disposed) return;`;`Dispose()` 幂等退订;**联动阶段 3-4**——P0-14 修复后旧 HomePageViewModel 才会被真正 Dispose,需一并验证。
8. 全仓排查其余 4 个重写 `OnMarketChanged` 的 VM(AgentAnalysis/AssetSelection/Favorites/PriceAlert)补 disposed 防护与完整退订。

## 阶段 4 — P1 批量修复(37 条,剔除 2 虚警)
按第三节编号顺序,分组处理:
- **交易域 1-13**:#1 金额列读改 `ReadDecimal()`(strategies L1226-1239、trade_records L1268-1271);#2 `strategies` 表加 `order_type`/`slippage_tolerance` 列 + 迁移;#3 exchangeInfo filters 缓存 + 下单前本地取整(stepSize/tickSize);#4 `BinanceUserDataStreamService._ws` 数据竞争(局部快照引用);#5 与阶段 1-6 合并;#6 RiskManager 补买入余额校验;#7 回撤熔断改 30 天滚动窗口 + 重置入口;#8 `MarketContext._currentMarket` 加 volatile;#9 `PriceAlertService.Dispose` 改 IAsyncDisposable;#10 `IsClosePositionAsync` 改三态,查询失败中止下单;#11 AISignal 伴随策略去重 + `StopLossPrice` 写入(L287 去掉提前 return);#12 `MarketMonitor.Dispose` 先 await 在途任务再释放信号量;#13 `AnalysisReportCache` 显式传 MarketType。
- **Agent/RAG 14-26(去掉 #17)**:#14 CoinGecko 改 resilience RateLimiter 删手写限流;#15 哈希改 `SHA256.ComputeHash(File.OpenRead)`;#16 补传 cancellationToken;#18 同义词替换加词边界断言(拉丁键 `\b`);#19 `RetrievalOrchestrator.cs:93-104` 补条数校验;#20 `ClipImageEmbeddingService` 构造去掉服务定位器 + `_initAttempted` 加锁;#21 `DocxMarkdownConverter` 无状态化;#22 AITool 补 CT + WebSearch 独立兜底;#23 雪球请求传 CT;#24 GenerateCriteria 对齐 OCE 处理(阶段 2 已覆盖,确认即可);#25 runId 捕获进闭包,工具只暴露 analystName;#26 Executor 改每次 Run 新建。
- **UI/Core 27-39(去掉 #30)**:#27 `TradingPageViewModel.Dispose` 补退订 StrategyConfig;#28 `MCPConfigPageViewModel` ContinueWith 改 UI 线程调度;#29 `ChatMessageAdapter` 补 `OnPropertyChanged(nameof(AdaptiveCard))`;#31 TradeMonitorViewModel 补 UI 线程切换;#32/#33 FavoritesPageViewModel 定时器与 Dispatcher 修复;#34 `StockSymbolConverter` 收敛单一 `ResolveExchange()` + 北交所 8/43 段;#35 `CryptoSymbolConverter` 白名单基础币(WBTC/WETH/STETH/WBNB);#36 `NullabilityInfoContext` 改每次新建;#37 KLineChartView 加 SemaphoreSlim + 单次订阅;#38 testConfig 拷贝对齐 SaveUIToConfig;#39 `StrategyConfigViewModel` 走 `IDialogService`。
- **补测试**:`510300`、`WBTC`、`STETH`、`600519`、`113016`、`830799` 符号转换用例。

## 阶段 5 — 死代码删除与重复造轮子收敛(P2)
- **删除(按决策)**:P2-1(随阶段 1)、#2 RoutingExchangeClient 纯转发(需先完成阶段 1-5 快照方案改造,确认无引用后删)、#3/#4/#5/#6/#7/#9/#10/#11 死代码整块;#8 随阶段 2 接上后不再是死代码。
- **Directory.Build.props**(#12):重写为集中管理 `TargetFramework`/`Nullable`/`ImplicitUsings`,删除 WinUI 段。
- **收敛**:#13 抽 `MarketAssistant.Infrastructure.ITokenCounter` 单例(两处 Tiktoken 合一);#14 `SourceType` 统一从 documentUri 推断;#15 `AnalysisReportCache` 换 `IMemoryCache`;#16 Reranker 去重分词 + 常量经 DI。
- **其余 #17-30**:Console.WriteLine 清理、HttpClient.Timeout 覆盖问题、签名硬编码移配置、SqliteRagDocumentCatalog 开 WAL + 释放锁、PasswordChar 补齐、`ModelDiscoveryService` 每次 CreateClient、AboutPage 路径净化与空 catch、StackOverflow 分支删除 + depth 上限、PriceChangeColorConverter 补 InvariantCulture、KLineChartView CDN 兜底改本地资源、CTS 传递、`[NotifyPropertyChangedFor]` 改造。

## 验证(每阶段必做)
- 每阶段:`dotnet build MarketAssistant.slnx -c Debug` 通过;`dotnet format` 后 `--verify-no-changes`。
- 阶段 1/2 结束:`dotnet test tests/TestMarketAssistant.csproj -c Debug`(现有 395 个 MSTest 用例 + 本计划新增用例全绿)。
- 阶段 3 结束:手动冒烟——切市场导航刷新、交易确认弹窗超时关闭、聊天 Markdown 渲染、详情页骨架屏无跨线程异常。
- 每阶段一个 commit,格式 `[模块] 变更概要`;阶段 1 完成前不启用实盘模式。
- 执行顺序:阶段 0 → 1 → 2 → 3 → 4 → 5,各阶段独立可交付,中途可随时叫停。
20 changes: 6 additions & 14 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<Project>
<PropertyGroup>
<!-- 在非 Windows 平台上禁用 Windows 目标构建 -->
<EnableWindowsTargeting Condition="'$(OS)' != 'Windows_NT'">false</EnableWindowsTargeting>

<!-- 在 macOS 上明确禁用 Windows 目标 -->
<EnableWindowsTargeting Condition="'$(RuntimeIdentifier)' == 'maccatalyst-x64' OR '$(RuntimeIdentifier)' == 'maccatalyst-arm64'">false</EnableWindowsTargeting>

<!-- 排除 Windows 项目的构建 -->
<DefaultItemExcludes Condition="'$(OS)' != 'Windows_NT'">$(DefaultItemExcludes);**/*WinUI*/**</DefaultItemExcludes>
<!-- 集中管理所有工程的通用属性(原 WinUI/Windows TFM 相关段已移除:当前全部工程均为 net10.0,无 WinUI 目标) -->
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<RootNamespace>MarketAssistant</RootNamespace>

<!-- VS 隔离构建(Isolated MSBuild)会在 obj-isolated/bin-isolated 生成第二套编译产物,
默认 glob 会将其扫入 Compile 项,与 obj/bin 下的生成文件重复导致 CS0579。
Expand All @@ -18,9 +15,4 @@
<!-- 如需调整,使用分号分隔:例如 zh-Hans;en-US;ja -->
<SatelliteResourceLanguages>zh-Hans;en-us</SatelliteResourceLanguages>
</PropertyGroup>

<!-- 条件性排除 Windows 项目引用 -->
<ItemGroup Condition="'$(OS)' != 'Windows_NT'">
<ProjectReference Remove="**/*WinUI*/*.csproj" />
</ItemGroup>
</Project>
</Project>
1 change: 1 addition & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.9.0" />
<PackageVersion Include="System.Threading.RateLimiting" Version="10.0.11" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.11" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.87.0" />
<!-- Data / docs / parsing -->
Expand Down
Loading
Loading