239 lines
8.8 KiB
C#
239 lines
8.8 KiB
C#
using AssetManager.Data;
|
|
using AssetManager.Infrastructure.StrategyEngine;
|
|
using AssetManager.Models;
|
|
using AssetManager.Models.DTOs;
|
|
using Microsoft.Extensions.Logging;
|
|
using SqlSugar;
|
|
|
|
namespace AssetManager.Services;
|
|
|
|
/// <summary>
|
|
/// 投资组合门面实现 - 整合多个服务
|
|
/// </summary>
|
|
public class PortfolioFacade : IPortfolioFacade
|
|
{
|
|
private readonly ILogger<PortfolioFacade> _logger;
|
|
private readonly IPortfolioService _portfolioService;
|
|
private readonly IPortfolioNavService _navService;
|
|
private readonly IStrategyService _strategyService;
|
|
private readonly IStrategyEngine _strategyEngine;
|
|
private readonly DatabaseService _databaseService;
|
|
private readonly ISqlSugarClient _db;
|
|
|
|
public PortfolioFacade(
|
|
ILogger<PortfolioFacade> logger,
|
|
IPortfolioService portfolioService,
|
|
IPortfolioNavService navService,
|
|
IStrategyService strategyService,
|
|
IStrategyEngine strategyEngine,
|
|
DatabaseService databaseService,
|
|
ISqlSugarClient db)
|
|
{
|
|
_logger = logger;
|
|
_portfolioService = portfolioService;
|
|
_navService = navService;
|
|
_strategyService = strategyService;
|
|
_strategyEngine = strategyEngine;
|
|
_databaseService = databaseService;
|
|
_db = db;
|
|
}
|
|
|
|
public async Task<CreatePortfolioResponse> CreatePortfolioAsync(CreatePortfolioRequest request, string userId)
|
|
{
|
|
_logger.LogInformation("创建投资组合: {Name}", request.Name);
|
|
return await _portfolioService.CreatePortfolioAsync(request, userId);
|
|
}
|
|
|
|
public async Task<PortfolioDetailResponse> GetPortfolioDetailAsync(string portfolioId, string userId)
|
|
{
|
|
_logger.LogInformation("获取组合详情: {PortfolioId}", portfolioId);
|
|
return await _portfolioService.GetPortfolioDetailAsync(portfolioId, userId);
|
|
}
|
|
|
|
public async Task<List<PortfolioListItem>> GetPortfolioListAsync(string userId)
|
|
{
|
|
_logger.LogInformation("获取组合列表: {UserId}", userId);
|
|
return await _portfolioService.GetPortfolioListAsync(userId);
|
|
}
|
|
|
|
public async Task<NavHistoryResponse> GetNavHistoryAsync(string portfolioId, NavHistoryRequest request, string userId)
|
|
{
|
|
_logger.LogInformation("获取净值历史: {PortfolioId}", portfolioId);
|
|
|
|
// 验证权限
|
|
var portfolio = _databaseService.GetPortfolioById(portfolioId, userId);
|
|
if (portfolio == null)
|
|
{
|
|
throw new UnauthorizedAccessException("组合不存在或无权访问");
|
|
}
|
|
|
|
return await _navService.GetNavHistoryAsync(portfolioId, userId, request);
|
|
}
|
|
|
|
public async Task<TransactionItem> CreateTransactionAsync(string portfolioId, CreateTransactionRequest request, string userId)
|
|
{
|
|
_logger.LogInformation("创建交易: {PortfolioId}, {Type}", portfolioId, request.Type);
|
|
|
|
// 验证权限
|
|
var portfolio = _databaseService.GetPortfolioById(portfolioId, userId);
|
|
if (portfolio == null)
|
|
{
|
|
throw new UnauthorizedAccessException("组合不存在或无权访问");
|
|
}
|
|
|
|
var transaction = await _portfolioService.CreateTransactionAsync(portfolioId, request, userId);
|
|
|
|
// 交易完成后,异步触发净值更新
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await _navService.CalculateAndSaveDailyNavAsync(portfolioId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "交易后净值更新失败: {PortfolioId}", portfolioId);
|
|
}
|
|
});
|
|
|
|
return transaction;
|
|
}
|
|
|
|
public async Task<List<TransactionItem>> GetTransactionsAsync(string portfolioId, GetTransactionsRequest request, string userId)
|
|
{
|
|
_logger.LogInformation("获取交易列表: {PortfolioId}", portfolioId);
|
|
return await _portfolioService.GetTransactionsAsync(portfolioId, request, userId);
|
|
}
|
|
|
|
public async Task<StrategySignalResponse> GetStrategySignalAsync(string portfolioId, string userId)
|
|
{
|
|
_logger.LogInformation("获取策略信号: {PortfolioId}", portfolioId);
|
|
|
|
// 获取组合
|
|
var portfolio = _databaseService.GetPortfolioById(portfolioId, userId);
|
|
if (portfolio == null || string.IsNullOrEmpty(portfolio.StrategyId))
|
|
{
|
|
return new StrategySignalResponse { Signal = "hold", Reason = "未绑定策略" };
|
|
}
|
|
|
|
// 获取策略(同步调用)
|
|
var strategy = _strategyService.GetStrategyById(portfolio.StrategyId, userId);
|
|
if (strategy == null)
|
|
{
|
|
return new StrategySignalResponse { Signal = "hold", Reason = "策略不存在" };
|
|
}
|
|
|
|
// 计算信号
|
|
var positions = _databaseService.GetPositionsByPortfolioId(portfolioId);
|
|
var signal = await _strategyEngine.CalculateSignalAsync(strategy, positions);
|
|
|
|
return new StrategySignalResponse
|
|
{
|
|
Signal = signal.Signal?.ToLower() ?? "hold",
|
|
Reason = signal.Reason,
|
|
Actions = signal.PositionSignals?.Select(p => new SignalAction
|
|
{
|
|
Symbol = p.Symbol,
|
|
Action = p.Signal?.ToLower(),
|
|
Target = (double?)p.TargetWeight
|
|
}).ToList()
|
|
};
|
|
}
|
|
|
|
public async Task<bool> DeletePortfolioAsync(string portfolioId, string userId)
|
|
{
|
|
_logger.LogInformation("删除组合: {PortfolioId}", portfolioId);
|
|
return await _portfolioService.DeletePortfolioAsync(portfolioId, userId);
|
|
}
|
|
|
|
public async Task<bool> UpdatePortfolioAsync(string portfolioId, UpdatePortfolioRequest request, string userId)
|
|
{
|
|
_logger.LogInformation("更新组合: {PortfolioId}", portfolioId);
|
|
return await _portfolioService.UpdatePortfolioAsync(portfolioId, request, userId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 创建投资组合(含用户验证和币种校验)
|
|
/// </summary>
|
|
public async Task<CreatePortfolioResponse> CreatePortfolioWithValidationAsync(CreatePortfolioRequest request, string userId)
|
|
{
|
|
_logger.LogInformation("创建投资组合(含验证): {Name}", request.Name);
|
|
|
|
// 获取用户默认币种
|
|
var user = _db.Queryable<User>().Where(u => u.Id == userId).First();
|
|
if (user == null)
|
|
{
|
|
throw new Exception("用户不存在");
|
|
}
|
|
|
|
// 校验组合币种
|
|
if (string.IsNullOrEmpty(request.Currency))
|
|
{
|
|
request.Currency = user.DefaultCurrency;
|
|
}
|
|
if (!CurrencyHelper.IsSupported(request.Currency))
|
|
{
|
|
throw new ArgumentException($"不支持的组合币种: {request.Currency}");
|
|
}
|
|
|
|
// 校验持仓币种
|
|
if (request.Stocks != null)
|
|
{
|
|
foreach (var stock in request.Stocks)
|
|
{
|
|
if (!string.IsNullOrEmpty(stock.Currency) && !CurrencyHelper.IsSupported(stock.Currency))
|
|
{
|
|
throw new ArgumentException($"不支持的持仓币种: {stock.Currency}");
|
|
}
|
|
}
|
|
}
|
|
|
|
var response = await _portfolioService.CreatePortfolioAsync(request, userId);
|
|
|
|
// 如果创建了持仓,自动触发净值计算
|
|
if (request.Stocks != null && request.Stocks.Any())
|
|
{
|
|
_logger.LogInformation("新组合包含持仓,触发净值计算: {PortfolioId}", response.Id);
|
|
_ = Task.Run(async () =>
|
|
{
|
|
try
|
|
{
|
|
await _navService.CalculateAndSaveDailyNavAsync(response.Id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogWarning(ex, "新组合净值计算失败: {PortfolioId}", response.Id);
|
|
}
|
|
});
|
|
}
|
|
|
|
return response;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 回填净值历史
|
|
/// </summary>
|
|
public async Task<BackfillNavResponse> BackfillNavHistoryAsync(string portfolioId, string userId, bool force = false)
|
|
{
|
|
_logger.LogInformation("回填净值历史: {PortfolioId}, Force={Force}", portfolioId, force);
|
|
|
|
// 验证权限
|
|
var portfolio = _databaseService.GetPortfolioById(portfolioId, userId);
|
|
if (portfolio == null)
|
|
{
|
|
throw new UnauthorizedAccessException("组合不存在或无权访问");
|
|
}
|
|
|
|
return await _navService.BackfillNavHistoryAsync(portfolioId, userId, force);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 获取用户总资产
|
|
/// </summary>
|
|
public async Task<TotalAssetsResponse> GetTotalAssetsAsync(string userId)
|
|
{
|
|
_logger.LogInformation("获取用户总资产: {UserId}", userId);
|
|
return await _portfolioService.GetTotalAssetsAsync(userId);
|
|
}
|
|
}
|