From c092ee46e9f96077fd13c2072387a8e5b1164b09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A9=99=E5=AD=90?= <454313500@qq.com> Date: Wed, 5 Mar 2025 23:08:58 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=8C=E6=88=90ai=E7=94=9F=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dtos/StockMarket/BuyStockInputDto.cs | 20 ++ .../Dtos/StockMarket/SellStockInputDto.cs | 20 ++ .../IServices/IStockMarketService.cs | 14 + .../Services/StockMarketService.cs | 43 ++- .../Etos/StockTransactionEto.cs | 56 +++ .../Yi.Framework.Stock.Domain.Shared.csproj | 3 + .../Managers/NewsManager.cs | 23 ++ .../SemanticKernel/Plugins/NewsPlugins.cs | 25 ++ .../SemanticKernel/Plugins/StockPlugins.cs | 26 ++ .../SemanticKernel/SemanticKernelClient.cs | 88 +++++ .../SemanticKernel/SemanticKernelOptions.cs | 6 + .../Managers/StockMarketManager.cs | 323 ++++++++++++++++++ .../Yi.Framework.Stock.Domain.csproj | 2 +- Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj | 6 + 14 files changed, 653 insertions(+), 2 deletions(-) create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/BuyStockInputDto.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/SellStockInputDto.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Etos/StockTransactionEto.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/NewsManager.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/NewsPlugins.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/StockPlugins.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelClient.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelOptions.cs create mode 100644 Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/StockMarketManager.cs diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/BuyStockInputDto.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/BuyStockInputDto.cs new file mode 100644 index 00000000..cc154d9d --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/BuyStockInputDto.cs @@ -0,0 +1,20 @@ +using System; + +namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket +{ + /// + /// 买入股票输入DTO + /// + public class BuyStockInputDto + { + /// + /// 股票ID + /// + public Guid StockId { get; set; } + + /// + /// 买入数量 + /// + public int Quantity { get; set; } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/SellStockInputDto.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/SellStockInputDto.cs new file mode 100644 index 00000000..d552750e --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/Dtos/StockMarket/SellStockInputDto.cs @@ -0,0 +1,20 @@ +using System; + +namespace Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket +{ + /// + /// 卖出股票输入DTO + /// + public class SellStockInputDto + { + /// + /// 股票ID + /// + public Guid StockId { get; set; } + + /// + /// 卖出数量 + /// + public int Quantity { get; set; } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/IServices/IStockMarketService.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/IServices/IStockMarketService.cs index 9426870a..b788c998 100644 --- a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/IServices/IStockMarketService.cs +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application.Contracts/IServices/IStockMarketService.cs @@ -25,5 +25,19 @@ namespace Yi.Framework.Stock.Application.Contracts.IServices /// 查询条件 /// 股价记录列表 Task> GetStockPriceRecordListAsync(StockPriceRecordGetListInputDto input); + + /// + /// 买入股票 + /// + /// 买入股票参数 + /// 操作结果 + Task BuyStockAsync(BuyStockInputDto input); + + /// + /// 卖出股票 + /// + /// 卖出股票参数 + /// 操作结果 + Task SellStockAsync(SellStockInputDto input); } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application/Services/StockMarketService.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application/Services/StockMarketService.cs index 44b0b349..8d69ecc2 100644 --- a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application/Services/StockMarketService.cs +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Application/Services/StockMarketService.cs @@ -5,11 +5,13 @@ using Microsoft.AspNetCore.Mvc; using SqlSugar; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Users; using Yi.Framework.Stock.Application.Contracts.Dtos.StockMarket; using Yi.Framework.Stock.Application.Contracts.Dtos.StockPrice; using Yi.Framework.Stock.Application.Contracts.IServices; using Yi.Framework.Stock.Domain.Entities; using Yi.Framework.SqlSugarCore.Abstractions; +using Yi.Framework.Stock.Domain.Managers; namespace Yi.Framework.Stock.Application.Services { @@ -20,13 +22,16 @@ namespace Yi.Framework.Stock.Application.Services { private readonly ISqlSugarRepository _stockMarketRepository; private readonly ISqlSugarRepository _stockPriceRecordRepository; + private readonly StockMarketManager _stockMarketManager; public StockMarketService( ISqlSugarRepository stockMarketRepository, - ISqlSugarRepository stockPriceRecordRepository) + ISqlSugarRepository stockPriceRecordRepository, + StockMarketManager stockMarketManager) { _stockMarketRepository = stockMarketRepository; _stockPriceRecordRepository = stockPriceRecordRepository; + _stockMarketManager = stockMarketManager; } /// @@ -91,5 +96,41 @@ namespace Yi.Framework.Stock.Application.Services return new PagedResultDto(total, list); } + + /// + /// 买入股票 + /// + [HttpPost("stock/buy")] + [Authorize] + public async Task BuyStockAsync(BuyStockInputDto input) + { + // 获取当前登录用户ID + var userId = CurrentUser.GetId(); + + // 调用领域服务进行股票购买 + await _stockMarketManager.BuyStockAsync( + userId, + input.StockId, + input.Quantity + ); + } + + /// + /// 卖出股票 + /// + [HttpPost("stock/sell")] + [Authorize] + public async Task SellStockAsync(SellStockInputDto input) + { + // 获取当前登录用户ID + var userId = CurrentUser.GetId(); + + // 调用领域服务进行股票卖出 + await _stockMarketManager.SellStockAsync( + userId, + input.StockId, + input.Quantity + ); + } } } \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Etos/StockTransactionEto.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Etos/StockTransactionEto.cs new file mode 100644 index 00000000..e37f07d9 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Etos/StockTransactionEto.cs @@ -0,0 +1,56 @@ +using System; +using Yi.Framework.Stock.Domain.Shared; + +namespace Yi.Framework.Stock.Domain.Shared.Etos +{ + /// + /// 股票交易事件数据传输对象 + /// + public class StockTransactionEto + { + /// + /// 用户ID + /// + public Guid UserId { get; set; } + + /// + /// 股票ID + /// + public Guid StockId { get; set; } + + /// + /// 股票代码 + /// + public string StockCode { get; set; } + + /// + /// 股票名称 + /// + public string StockName { get; set; } + + /// + /// 交易类型 + /// + public TransactionTypeEnum TransactionType { get; set; } + + /// + /// 交易价格 + /// + public decimal Price { get; set; } + + /// + /// 交易数量 + /// + public int Quantity { get; set; } + + /// + /// 交易总额 + /// + public decimal TotalAmount { get; set; } + + /// + /// 交易费用 + /// + public decimal Fee { get; set; } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Yi.Framework.Stock.Domain.Shared.csproj b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Yi.Framework.Stock.Domain.Shared.csproj index 77927955..94311f04 100644 --- a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Yi.Framework.Stock.Domain.Shared.csproj +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain.Shared/Yi.Framework.Stock.Domain.Shared.csproj @@ -14,5 +14,8 @@ + + + diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/NewsManager.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/NewsManager.cs new file mode 100644 index 00000000..7d845d5f --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/NewsManager.cs @@ -0,0 +1,23 @@ +using Yi.Framework.Stock.Domain.Managers.Plugins; + +namespace Yi.Framework.Stock.Domain.Managers; + +public class NewsManager +{ + private SemanticKernelClient _skClient; + + public NewsManager(SemanticKernelClient skClient) + { + _skClient = skClient; + } + + /// + /// 生成一个新闻 + /// + /// + public async Task GenerateNewsAsync() + { + _skClient.RegisterPlugins("news"); + await _skClient.ChatCompletionAsync("帮我生成一个新闻"); + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/NewsPlugins.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/NewsPlugins.cs new file mode 100644 index 00000000..fa02ad6c --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/NewsPlugins.cs @@ -0,0 +1,25 @@ +using System.ComponentModel; +using System.Text.Json.Serialization; +using Microsoft.SemanticKernel; + +namespace Yi.Framework.Stock.Domain.Managers.Plugins; + +public class NewsPlugins +{ + [KernelFunction("save_news"), Description("生成并且保存一个新闻")] + public async Task SaveAsync(NewModel news) + { + return "成功"; + } +} + +public class NewModel +{ + [JsonPropertyName("title")] + [DisplayName("新闻标题")] + public string Title { get; set; } + + [JsonPropertyName("content")] + [DisplayName("新闻内容")] + public string? Content { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/StockPlugins.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/StockPlugins.cs new file mode 100644 index 00000000..ee53a073 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/Plugins/StockPlugins.cs @@ -0,0 +1,26 @@ +using System.ComponentModel; +using System.Text.Json.Serialization; +using Microsoft.SemanticKernel; + +namespace Yi.Framework.Stock.Domain.Managers.Plugins; + +public class StockPlugins +{ + [KernelFunction("save_stocks"), Description("生成并且保存多个股票记录")] + public async Task SaveAsync(List stockModels) + { + return "成功"; + } +} + +public class StockModel +{ + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("is_on")] + public bool? IsOn { get; set; } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelClient.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelClient.cs new file mode 100644 index 00000000..a9f7eb34 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelClient.cs @@ -0,0 +1,88 @@ +using Microsoft.Extensions.Options; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.OpenAI; + +namespace Yi.Framework.Stock.Domain.Managers; + +public class SemanticKernelClient +{ + private Kernel Kernel { get; } + private readonly IKernelBuilder _kernelBuilder; + private SemanticKernelOptions Options { get; } + + public SemanticKernelClient(IOptions semanticKernelOption) + { + Options = semanticKernelOption.Value; + _kernelBuilder = Kernel.CreateBuilder(); + RegisterChatCompletion(); + Kernel = _kernelBuilder.Build(); + RegisterDefautlPlugins(); + } + + /// + /// 注册 + /// + private void RegisterChatCompletion() + { + _kernelBuilder.AddOpenAIChatCompletion( + modelId: "", + apiKey: "", + httpClient: new HttpClient() { BaseAddress = new Uri("") }); + } + + /// + /// 插件注册 + /// + private void RegisterDefautlPlugins() + { + //动态导入插件 + // this.Kernel.ImportPluginFromPromptDirectory(System.IO.Path.Combine("wwwroot", "plugin","stock"),"stock"); + } + + /// + /// 自定义插件 + /// + /// + /// + public void RegisterPlugins(string pluginName) + { + this.Kernel.Plugins.AddFromType(pluginName); + } + + + /// + /// 执行插件 + /// + /// + /// + /// + /// + public async Task InovkerFunctionAsync(string input, string pluginName, string functionName) + { + KernelFunction jsonFun = this.Kernel.Plugins.GetFunction(pluginName, functionName); + var result = await this.Kernel.InvokeAsync(function: jsonFun, new KernelArguments() { ["input"] = input }); + return result.GetValue(); + } + + /// + /// 聊天对话,调用方法 + /// + /// + public async Task> ChatCompletionAsync(string question) + { + OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new() + { + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), + MaxTokens = 1000 + }; + + var chatCompletionService = this.Kernel.GetRequiredService(); + + var results =await chatCompletionService.GetChatMessageContentsAsync( + question, + executionSettings: openAIPromptExecutionSettings, + kernel: Kernel); + return results; + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelOptions.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelOptions.cs new file mode 100644 index 00000000..95edd997 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/SemanticKernel/SemanticKernelOptions.cs @@ -0,0 +1,6 @@ +namespace Yi.Framework.Stock.Domain.Managers; + +public class SemanticKernelOptions +{ + +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/StockMarketManager.cs b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/StockMarketManager.cs new file mode 100644 index 00000000..dbe00433 --- /dev/null +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Managers/StockMarketManager.cs @@ -0,0 +1,323 @@ +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Domain.Services; +using Volo.Abp.EventBus.Local; +using Yi.Framework.Bbs.Domain.Shared.Etos; +using Yi.Framework.Stock.Domain.Entities; +using Yi.Framework.Stock.Domain.Shared; +using Yi.Framework.Stock.Domain.Shared.Etos; +using Yi.Framework.SqlSugarCore.Abstractions; +using Yi.Framework.Stock.Domain.Managers.Plugins; + +namespace Yi.Framework.Stock.Domain.Managers +{ + /// + /// 股市领域服务 + /// + /// + /// 处理股票交易相关业务,例如买入、卖出等 + /// + public class StockMarketManager : DomainService + { + private readonly ISqlSugarRepository _stockHoldingRepository; + private readonly ISqlSugarRepository _stockTransactionRepository; + private readonly ISqlSugarRepository _stockPriceRecordRepository; + private readonly ISqlSugarRepository _stockMarketRepository; + private readonly ILocalEventBus _localEventBus; + private readonly SemanticKernelClient _skClient; + public StockMarketManager( + ISqlSugarRepository stockHoldingRepository, + ISqlSugarRepository stockTransactionRepository, + ISqlSugarRepository stockPriceRecordRepository, + ISqlSugarRepository stockMarketRepository, + ILocalEventBus localEventBus, SemanticKernelClient skClient) + { + _stockHoldingRepository = stockHoldingRepository; + _stockTransactionRepository = stockTransactionRepository; + _stockPriceRecordRepository = stockPriceRecordRepository; + _stockMarketRepository = stockMarketRepository; + _localEventBus = localEventBus; + _skClient = skClient; + } + + /// + /// 购买股票 + /// + /// 用户ID + /// 股票ID + /// 购买数量 + /// + public async Task BuyStockAsync(Guid userId, Guid stockId, int quantity) + { + if (quantity <= 0) + { + throw new UserFriendlyException("购买数量必须大于0"); + } + + // 通过stockId查询获取股票信息 + var stockInfo = await _stockMarketRepository.GetFirstAsync(s => s.Id == stockId); + if (stockInfo == null) + { + throw new UserFriendlyException("找不到指定的股票"); + } + + string stockCode = stockInfo.MarketCode; // 根据实际字段调整 + string stockName = stockInfo.MarketName; // 根据实际字段调整 + + // 获取当前股票价格 + decimal currentPrice = await GetCurrentStockPriceAsync(stockId); + + // 计算总金额和手续费 + decimal totalAmount = currentPrice * quantity; + decimal fee = CalculateTradingFee(totalAmount, TransactionTypeEnum.Buy); + decimal totalCost = totalAmount + fee; + + // 扣减用户资金 + await _localEventBus.PublishAsync( + new MoneyChangeEventArgs { UserId = userId, Number = -totalCost }, false); + + // 更新或创建用户持仓 + var holding = await _stockHoldingRepository.GetFirstAsync(h => + h.UserId == userId && + h.StockId == stockId && + !h.IsDeleted); + + if (holding == null) + { + // 创建新持仓 + holding = new StockHoldingAggregateRoot( + userId, + stockId, + stockCode, + stockName, + quantity, + currentPrice); + + await _stockHoldingRepository.InsertAsync(holding); + } + else + { + // 更新现有持仓 + holding.AddQuantity(quantity, currentPrice); + await _stockHoldingRepository.UpdateAsync(holding); + } + + // 创建交易记录 + var transaction = new StockTransactionEntity( + userId, + stockId, + stockCode, + stockName, + TransactionTypeEnum.Buy, + currentPrice, + quantity, + fee); + + await _stockTransactionRepository.InsertAsync(transaction); + + // 发布交易事件 + await _localEventBus.PublishAsync(new StockTransactionEto + { + UserId = userId, + StockId = stockId, + StockCode = stockCode, + StockName = stockName, + TransactionType = TransactionTypeEnum.Buy, + Price = currentPrice, + Quantity = quantity, + TotalAmount = totalAmount, + Fee = fee + }, false); + } + + /// + /// 卖出股票 + /// + /// 用户ID + /// 股票ID + /// 卖出数量 + /// + public async Task SellStockAsync(Guid userId, Guid stockId, int quantity) + { + // 验证卖出时间 + VerifySellTime(); + + if (quantity <= 0) + { + throw new UserFriendlyException("卖出数量必须大于0"); + } + + // 获取用户持仓 + var holding = await _stockHoldingRepository.GetFirstAsync(h => + h.UserId == userId && + h.StockId == stockId && + !h.IsDeleted); + + if (holding == null) + { + throw new UserFriendlyException("您没有持有该股票"); + } + + if (holding.Quantity < quantity) + { + throw new UserFriendlyException("持仓数量不足"); + } + + // 获取当前股票价格 + decimal currentPrice = await GetCurrentStockPriceAsync(stockId); + + // 计算总金额和手续费 + decimal totalAmount = currentPrice * quantity; + decimal fee = CalculateTradingFee(totalAmount, TransactionTypeEnum.Sell); + decimal actualIncome = totalAmount - fee; + + // 增加用户资金 + await _localEventBus.PublishAsync( + new MoneyChangeEventArgs { UserId = userId, Number = actualIncome }, false); + + // 更新用户持仓 + holding.ReduceQuantity(quantity); + + if (holding.Quantity > 0) + { + await _stockHoldingRepository.UpdateAsync(holding); + } + else + { + await _stockHoldingRepository.DeleteAsync(holding); + } + + // 创建交易记录 + var transaction = new StockTransactionEntity( + userId, + stockId, + holding.StockCode, + holding.StockName, + TransactionTypeEnum.Sell, + currentPrice, + quantity, + fee); + + await _stockTransactionRepository.InsertAsync(transaction); + + // 发布交易事件 + await _localEventBus.PublishAsync(new StockTransactionEto + { + UserId = userId, + StockId = stockId, + StockCode = holding.StockCode, + StockName = holding.StockName, + TransactionType = TransactionTypeEnum.Sell, + Price = currentPrice, + Quantity = quantity, + TotalAmount = totalAmount, + Fee = fee + }, false); + } + + /// + /// 获取股票当前价格 + /// + /// 股票ID + /// 当前价格 + public async Task GetCurrentStockPriceAsync(Guid stockId) + { + // 获取最新的价格记录 + var latestPriceRecord = await _stockPriceRecordRepository._DbQueryable + .Where(p => p.StockId == stockId) + .OrderByDescending(p => p.CreationTime) + .FirstAsync(); + + if (latestPriceRecord == null) + { + throw new UserFriendlyException("无法获取股票价格信息"); + } + + return latestPriceRecord.CurrentPrice; + } + + /// + /// 计算交易手续费 + /// + /// 交易金额 + /// 交易类型 + /// 手续费 + private decimal CalculateTradingFee(decimal amount, TransactionTypeEnum transactionType) + { + // 示例费率:买入0.1%,卖出0.2% + decimal feeRate = transactionType == TransactionTypeEnum.Buy ? 0.001m : 0.002m; + return amount * feeRate; + } + + /// + /// 验证卖出时间 + /// + /// 如果不在允许卖出的时间范围内 + private void VerifySellTime() + { + DateTime now = DateTime.Now; + + // 检查是否为工作日(周一到周五) + if (now.DayOfWeek == DayOfWeek.Saturday || now.DayOfWeek == DayOfWeek.Sunday) + { + throw new UserFriendlyException("股票只能在工作日(周一至周五)卖出"); + } + + // 检查是否在下午5点到6点之间 + if (now.Hour < 17 || now.Hour >= 18) + { + throw new UserFriendlyException("股票只能在下午5点到6点之间卖出"); + } + } + + + /// + /// 批量保存多个股票的最新价格记录 + /// + /// 价格记录列表 + /// 保存的记录数量 + public async Task BatchSaveStockPriceRecordsAsync(List priceRecords) + { + if (priceRecords == null || !priceRecords.Any()) + { + return 0; + } + + // 验证数据 + foreach (var record in priceRecords) + { + if (record.CurrentPrice <= 0) + { + throw new UserFriendlyException($"股票ID {record.StockId} 的价格必须大于0"); + } + + // 设置创建时间为当前时间(如果未设置) + if (record.CreationTime == default) + { + record.CreationTime = DateTime.Now; + } + + // 计算交易额(如果未设置) + if (record.Turnover == 0 && record.Volume > 0) + { + record.Turnover = record.CurrentPrice * record.Volume; + } + } + + await _stockPriceRecordRepository.InsertManyAsync(priceRecords); + return priceRecords.Count; + } + + + /// + /// 生成最新股票记录 + /// + /// + public async Task GenerateStocksAsync() + { + _skClient.RegisterPlugins("stock"); + await _skClient.ChatCompletionAsync("帮我生成多个股市内容"); + } + } +} \ No newline at end of file diff --git a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj index e65240f4..55c6a318 100644 --- a/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj +++ b/Yi.Abp.Net8/module/ai-stock/Yi.Framework.Stock.Domain/Yi.Framework.Stock.Domain.csproj @@ -1,6 +1,7 @@ + @@ -14,7 +15,6 @@ - diff --git a/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj b/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj index d85ce09d..eb81b112 100644 --- a/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj +++ b/Yi.Abp.Net8/src/Yi.Abp.Web/Yi.Abp.Web.csproj @@ -47,6 +47,12 @@ Always + + Always + + + Always +