AssetManager.API/AssetManager.Services/PortfolioService.cs

786 lines
31 KiB
C#
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using AssetManager.Data;
using AssetManager.Models.DTOs;
using AssetManager.Infrastructure.Services;
using SqlSugar;
using Microsoft.Extensions.Logging;
namespace AssetManager.Services;
public class PortfolioService : IPortfolioService
{
private readonly ISqlSugarClient _db;
private readonly IMarketDataService _marketDataService;
private readonly IExchangeRateService _exchangeRateService;
private readonly IPortfolioNavService _navService;
private readonly ILogger<PortfolioService> _logger;
public PortfolioService(
ISqlSugarClient db,
IMarketDataService marketDataService,
IExchangeRateService exchangeRateService,
IPortfolioNavService navService,
ILogger<PortfolioService> logger)
{
_db = db;
_marketDataService = marketDataService;
_exchangeRateService = exchangeRateService;
_navService = navService;
_logger = logger;
}
public CreatePortfolioResponse CreatePortfolio(CreatePortfolioRequest request, string userId)
{
var portfolio = new Portfolio
{
Id = "port-" + Guid.NewGuid().ToString().Substring(0, 8),
UserId = userId,
StrategyId = request.StrategyId,
Name = request.Name,
Currency = request.Currency,
TotalValue = (decimal)(request.Stocks?.Sum(s => s.Price * s.Amount) ?? 0),
ReturnRate = 0,
Status = "运行中",
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
_db.Insertable(portfolio).ExecuteCommand();
// 如果选择了策略,自动加载策略配置的标的作为初始持仓
var strategyStocks = new List<StockItem>();
if (!string.IsNullOrEmpty(request.StrategyId))
{
var Strategy = _db.Queryable<Strategy>()
.Where(s => s.Id == request.StrategyId && s.UserId == userId)
.First();
if (Strategy != null && !string.IsNullOrEmpty(Strategy.Config))
{
try
{
// 风险平价策略
if (Strategy.Type?.Equals("risk_parity", StringComparison.OrdinalIgnoreCase) == true)
{
// 处理可能的双层转义
string configJson = Strategy.Config;
if (configJson.StartsWith("\"") && configJson.EndsWith("\""))
{
// 去掉外层的引号和转义
configJson = System.Text.Json.JsonSerializer.Deserialize<string>(configJson);
}
var config = System.Text.Json.JsonSerializer.Deserialize<RiskParityConfig>(configJson);
if (config?.Assets != null)
{
foreach (var asset in config.Assets)
{
if (!string.IsNullOrEmpty(asset.Symbol))
{
strategyStocks.Add(new StockItem
{
Code = asset.Symbol,
Name = asset.Symbol,
Price = 0, // 价格留空,用户后续填写
Amount = 0, // 数量留空,用户后续填写
Currency = request.Currency,
AssetType = "Stock"
});
}
}
}
}
// 其他策略类型可以在这里扩展
}
catch (Exception ex)
{
_logger.LogError(ex, "解析策略配置失败策略ID: {StrategyId}", request.StrategyId);
}
}
}
// 合并用户传入的持仓和策略自动生成的持仓
var allStocks = (request.Stocks ?? new List<StockItem>()).Concat(strategyStocks).DistinctBy(s => s.Code).ToList();
// 创建初始持仓
foreach (var stock in allStocks)
{
if (stock.Code == null || stock.Name == null)
{
continue;
}
// 解析实际买入时间,如果解析失败则用当前时间
DateTime buyTime = DateTime.Now;
if (!string.IsNullOrEmpty(stock.Date))
{
if (DateTime.TryParse(stock.Date, out var parsedDate))
{
buyTime = parsedDate;
}
}
var position = new Position
{
Id = "pos-" + Guid.NewGuid().ToString().Substring(0, 8),
PortfolioId = portfolio.Id,
StockCode = stock.Code,
StockName = stock.Name,
AssetType = string.IsNullOrEmpty(stock.AssetType) ? "Stock" : stock.AssetType,
Shares = (decimal)stock.Amount,
AvgPrice = (decimal)stock.Price,
Currency = request.Currency,
CreatedAt = buyTime,
UpdatedAt = DateTime.Now
};
_db.Insertable(position).ExecuteCommand();
// 创建交易记录
var transaction = new Transaction
{
Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8),
PortfolioId = portfolio.Id,
Type = "buy",
StockCode = stock.Code,
AssetType = string.IsNullOrEmpty(stock.AssetType) ? "Stock" : stock.AssetType,
Title = "初始建仓",
Amount = (decimal)stock.Amount,
Price = (decimal)stock.Price,
TotalAmount = (decimal)(stock.Price * stock.Amount),
Currency = request.Currency,
Status = "completed",
Remark = "初始建仓",
TransactionTime = buyTime,
CreatedAt = DateTime.Now
};
_db.Insertable(transaction).ExecuteCommand();
}
return new CreatePortfolioResponse
{
Id = portfolio.Id,
TotalValue = (double)portfolio.TotalValue,
ReturnRate = 0,
Currency = portfolio.Currency,
CreatedAt = portfolio.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")
};
}
public List<PortfolioListItem> GetPortfolios(string userId)
{
var portfolios = _db.Queryable<Portfolio>()
.Where(p => p.UserId == userId)
.ToList();
var result = new List<PortfolioListItem>();
foreach (var p in portfolios)
{
// 获取该组合的所有持仓
var positions = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == p.Id)
.ToList();
int positionCount = positions.Count;
// 实时计算当前市值和总成本
decimal totalValue = 0;
decimal totalCost = 0;
decimal todayProfit = 0;
string portfolioCurrency = p.Currency ?? "CNY";
foreach (var pos in positions)
{
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格
decimal currentPrice = pos.AvgPrice;
decimal previousClose = pos.AvgPrice;
try
{
var priceResponse = _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock").GetAwaiter().GetResult();
if (priceResponse.Price > 0)
{
currentPrice = priceResponse.Price;
previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : currentPrice;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价", pos.StockCode);
}
decimal positionValue = pos.Shares * currentPrice;
decimal positionCost = pos.Shares * pos.AvgPrice;
decimal positionTodayProfit = previousClose > 0 ? pos.Shares * (currentPrice - previousClose) : 0;
// 汇率转换到组合本位币
var valueInTarget = _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, portfolioCurrency).GetAwaiter().GetResult();
var costInTarget = _exchangeRateService.ConvertAmountAsync(positionCost, pos.Currency, portfolioCurrency).GetAwaiter().GetResult();
var todayProfitInTarget = _exchangeRateService.ConvertAmountAsync(positionTodayProfit, pos.Currency, portfolioCurrency).GetAwaiter().GetResult();
totalValue += valueInTarget;
totalCost += costInTarget;
todayProfit += todayProfitInTarget;
}
// 计算收益率
double returnRate = totalCost > 0 ? (double)((totalValue - totalCost) / totalCost * 100) : 0;
returnRate = Math.Round(returnRate, 2);
result.Add(new PortfolioListItem
{
Id = p.Id,
Name = p.Name,
Tags = $"{p.Status} · {p.Currency}" + (positionCount > 0 ? $" · {positionCount}只" : ""),
Status = p.Status,
StatusType = p.Status == "运行中" ? "green" : p.Status == "已暂停" ? "yellow" : "gray",
IconChar = p.Name?.Substring(0, 1).ToUpper() ?? "P",
IconBgClass = "bg-blue-100",
IconTextClass = "text-blue-700",
Value = (double)totalValue,
Currency = p.Currency,
ReturnRate = returnRate,
ReturnType = returnRate >= 0 ? "positive" : "negative",
TodayProfit = (double)todayProfit,
TodayProfitCurrency = p.Currency
});
}
return result;
}
public async Task<TotalAssetsResponse> GetTotalAssetsAsync(string userId)
{
// 获取用户信息(包含默认本位币)
var user = _db.Queryable<User>()
.Where(u => u.Id == userId)
.First();
if (user == null)
{
throw new Exception("User not found");
}
string targetCurrency = !string.IsNullOrEmpty(user.DefaultCurrency) ? user.DefaultCurrency : "CNY";
_logger.LogInformation("用户 {UserId} 默认本位币: {Currency}", userId, targetCurrency);
decimal totalValueInTargetCurrency = 0;
decimal totalCostInTargetCurrency = 0;
decimal totalTodayProfitInTargetCurrency = 0;
// 获取用户所有投资组合
var portfolios = _db.Queryable<Portfolio>()
.Where(p => p.UserId == userId)
.ToList();
foreach (var portfolio in portfolios)
{
// 获取该组合的所有持仓
var Positions = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == portfolio.Id)
.ToList();
foreach (var pos in Positions)
{
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格(自动路由数据源),失败则降级使用成本价
decimal CurrentPrice = pos.AvgPrice;
decimal previousClose = pos.AvgPrice;
try
{
var priceResponse = await _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock");
if (priceResponse.Price > 0)
{
CurrentPrice = priceResponse.Price;
previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : CurrentPrice;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价作为当前价", pos.StockCode);
}
decimal currentPositionValue = pos.Shares * CurrentPrice;
decimal costPositionValue = pos.Shares * pos.AvgPrice;
decimal TodayProfit = previousClose > 0 ? pos.Shares * (CurrentPrice - previousClose) : 0;
// 换算到目标本位币
decimal currentInTarget = await _exchangeRateService.ConvertAmountAsync(currentPositionValue, pos.Currency, targetCurrency);
decimal costInTarget = await _exchangeRateService.ConvertAmountAsync(costPositionValue, pos.Currency, targetCurrency);
decimal todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(TodayProfit, pos.Currency, targetCurrency);
_logger.LogInformation("标的 {StockCode} 换算: {Amount} {From} → {Converted} {To},汇率: {Rate}",
pos.StockCode, currentPositionValue, pos.Currency, currentInTarget, targetCurrency,
currentPositionValue > 0 ? currentInTarget / currentPositionValue : 0);
totalValueInTargetCurrency += currentInTarget;
totalCostInTargetCurrency += costInTarget;
totalTodayProfitInTargetCurrency += todayProfitInTarget;
}
}
double totalReturnRate = totalCostInTargetCurrency > 0 ? (double)((totalValueInTargetCurrency - totalCostInTargetCurrency) / totalCostInTargetCurrency * 100) : 0;
return new TotalAssetsResponse
{
TotalValue = (double)totalValueInTargetCurrency,
Currency = targetCurrency,
TodayProfit = (double)totalTodayProfitInTargetCurrency,
TodayProfitCurrency = targetCurrency,
TotalReturnRate = Math.Round(totalReturnRate, 2)
};
}
// 保留同步方法作为兼容
public TotalAssetsResponse GetTotalAssets(string userId)
{
return GetTotalAssetsAsync(userId).GetAwaiter().GetResult();
}
public async Task<PortfolioDetailResponse> GetPortfolioByIdAsync(string id, string userId)
{
var portfolio = _db.Queryable<Portfolio>()
.Where(p => p.Id == id && p.UserId == userId)
.First();
if (portfolio == null)
{
throw new Exception("Portfolio not found or access denied");
}
var Positions = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == id)
.ToList();
// 获取每个持仓的实时价格并转换为组合本位币
string targetCurrency = portfolio.Currency ?? "CNY";
decimal totalPortfolioValue = 0;
decimal totalCost = 0;
decimal totalTodayProfit = 0;
var positionItems = new List<PositionItem>();
foreach (var pos in Positions)
{
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格(自动路由数据源),失败则降级使用成本价
decimal CurrentPrice = pos.AvgPrice;
decimal previousClose = pos.AvgPrice;
try
{
var priceResponse = await _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock");
if (priceResponse.Price > 0)
{
CurrentPrice = priceResponse.Price;
previousClose = priceResponse.PreviousClose > 0 ? priceResponse.PreviousClose : CurrentPrice;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价作为当前价", pos.StockCode);
}
decimal positionValue = pos.Shares * CurrentPrice;
decimal cost = pos.Shares * pos.AvgPrice;
decimal Profit = positionValue - cost;
double ProfitRate = cost > 0 ? (double)(Profit / cost * 100) : 0;
decimal TodayProfit = previousClose > 0 ? pos.Shares * (CurrentPrice - previousClose) : 0;
// 转换为组合本位币
decimal positionValueInTarget = await _exchangeRateService.ConvertAmountAsync(positionValue, pos.Currency, targetCurrency);
decimal costInTarget = await _exchangeRateService.ConvertAmountAsync(cost, pos.Currency, targetCurrency);
decimal todayProfitInTarget = await _exchangeRateService.ConvertAmountAsync(TodayProfit, pos.Currency, targetCurrency);
totalPortfolioValue += positionValueInTarget;
totalCost += costInTarget;
totalTodayProfit += todayProfitInTarget;
positionItems.Add(new PositionItem
{
Id = pos.Id,
StockCode = pos.StockCode,
StockName = pos.StockName,
Symbol = pos.StockCode,
Amount = (int)pos.Shares,
AveragePrice = (double)pos.AvgPrice,
CurrentPrice = (double)CurrentPrice,
TotalValue = (double)positionValueInTarget,
Profit = (double)(positionValueInTarget - costInTarget),
ProfitRate = ProfitRate,
ChangeAmount = (double)todayProfitInTarget,
Ratio = 0, // 后面统一计算比例
DeviationRatio = 0, // 后续实现
Currency = targetCurrency
});
}
// 计算每个持仓的比例
foreach (var item in positionItems)
{
item.Ratio = totalPortfolioValue > 0 ? (item.TotalValue / (double)totalPortfolioValue) * 100 : 0;
}
decimal TotalReturn = totalPortfolioValue - totalCost;
double totalReturnRate = totalCost > 0 ? (double)(TotalReturn / totalCost * 100) : 0;
return new PortfolioDetailResponse
{
Id = portfolio.Id,
Name = portfolio.Name,
Currency = targetCurrency,
Status = portfolio.Status,
Strategy = new StrategyInfo
{
Id = portfolio.StrategyId,
Name = "策略名称",
Description = "策略描述"
},
PortfolioValue = (double)totalPortfolioValue,
TotalReturn = (double)TotalReturn,
TodayProfit = (double)totalTodayProfit,
HistoricalChange = totalReturnRate,
DailyVolatility = 0, // 后续实现
TodayProfitCurrency = targetCurrency,
LogicModel = "HFEA 风险平价逻辑",
LogicModelStatus = "监控中",
LogicModelDescription = "目标权重 季度调仓",
TotalItems = Positions.Count,
TotalRatio = 100.0,
Positions = positionItems
};
}
// 保留同步方法作为兼容(内部调用异步)
public PortfolioDetailResponse GetPortfolioById(string id, string userId)
{
return GetPortfolioByIdAsync(id, userId).GetAwaiter().GetResult();
}
public GetTransactionsResponse GetTransactions(string portfolioId, string userId, int limit, int offset)
{
// 验证投资组合是否属于该用户
var portfolio = _db.Queryable<Portfolio>()
.Where(p => p.Id == portfolioId && p.UserId == userId)
.First();
if (portfolio == null)
{
throw new Exception("Portfolio not found or access denied");
}
var transactions = _db.Queryable<Transaction>()
.Where(t => t.PortfolioId == portfolioId)
.OrderByDescending(t => t.TransactionTime)
.Skip(offset)
.Take(limit)
.ToList();
var total = _db.Queryable<Transaction>()
.Where(t => t.PortfolioId == portfolioId)
.Count();
return new GetTransactionsResponse
{
Items = transactions.Select(t => new TransactionItem
{
Id = t.Id,
PortfolioId = t.PortfolioId,
Date = t.TransactionTime.ToString("yyyy-MM-dd"),
Time = t.TransactionTime.ToString("HH:mm:ss"),
Type = t.Type,
Title = t.Title,
StockCode = t.StockCode,
Amount = (double)t.TotalAmount,
Currency = t.Currency,
Status = t.Status,
Remark = t.Remark
}).ToList(),
Total = total,
Page = offset / limit + 1,
PageSize = limit
};
}
public async Task<CreateTransactionResponse> CreateTransaction(CreateTransactionRequest request, string userId)
{
// 验证投资组合是否属于该用户
var portfolio = _db.Queryable<Portfolio>()
.Where(p => p.Id == request.PortfolioId && p.UserId == userId)
.First();
if (portfolio == null)
{
throw new Exception("Portfolio not found or access denied");
}
// 校验交易币种必须和组合本位币一致(双重校验)
if (!string.IsNullOrEmpty(request.Currency) && !request.Currency.Equals(portfolio.Currency, StringComparison.OrdinalIgnoreCase))
{
throw new Exception($"该组合本位币为 {portfolio.Currency},只能添加相同币种的标的");
}
// 卖出操作校验
if (request.Type?.ToLower() == "sell")
{
// 校验是否有该持仓
var existingPosition = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == request.PortfolioId && pos.StockCode == request.StockCode)
.First();
if (existingPosition == null)
{
throw new Exception($"该组合中不存在标的 {request.StockCode},无法卖出");
}
// 校验卖出数量不超过持仓数量
if (request.Amount > (double)existingPosition.Shares)
{
throw new Exception($"卖出数量不能超过持仓数量,当前持仓 {existingPosition.Shares} 份");
}
}
// 解析实际交易时间,如果解析失败则用当前时间
DateTime transactionTime = DateTime.Now;
if (!string.IsNullOrEmpty(request.TransactionDate))
{
if (DateTime.TryParse(request.TransactionDate, out var parsedDate))
{
// 如果只传了日期,时间部分默认用当前时间
transactionTime = parsedDate.Date + DateTime.Now.TimeOfDay;
}
}
else if (!string.IsNullOrEmpty(request.TransactionTime))
{
if (DateTime.TryParse(request.TransactionTime, out var parsedTime))
{
transactionTime = parsedTime;
}
}
var transaction = new Transaction
{
Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8),
PortfolioId = request.PortfolioId,
Type = request.Type,
StockCode = request.StockCode,
AssetType = string.IsNullOrEmpty(request.AssetType) ? "Stock" : request.AssetType,
Title = request.Remark ?? "交易",
Amount = (decimal)request.Amount,
Price = (decimal)request.Price,
TotalAmount = (decimal)(request.Price * request.Amount),
Currency = request.Currency,
Status = "completed",
Remark = request.Remark ?? string.Empty,
TransactionTime = transactionTime,
CreatedAt = DateTime.Now
};
_db.Insertable(transaction).ExecuteCommand();
// 更新持仓
var position = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == request.PortfolioId && pos.StockCode == request.StockCode)
.First();
if (position != null)
{
if (request.Type == "buy")
{
// 计算新的平均价格
var newTotalShares = position.Shares + (decimal)request.Amount;
var newTotalCost = (position.Shares * position.AvgPrice) + ((decimal)request.Amount * (decimal)request.Price);
position.AvgPrice = newTotalCost / newTotalShares;
position.Shares = newTotalShares;
position.UpdatedAt = DateTime.Now;
_db.Updateable(position).ExecuteCommand();
}
else if (request.Type == "sell")
{
position.Shares -= (decimal)request.Amount;
position.UpdatedAt = DateTime.Now;
if (position.Shares <= 0)
{
_db.Deleteable(position).ExecuteCommand();
}
else
{
_db.Updateable(position).ExecuteCommand();
}
}
}
else if (request.Type == "buy")
{
// 创建新持仓
position = new Position
{
Id = "pos-" + Guid.NewGuid().ToString().Substring(0, 8),
PortfolioId = request.PortfolioId,
StockCode = request.StockCode,
StockName = request.Remark ?? request.StockCode,
AssetType = string.IsNullOrEmpty(request.AssetType) ? "Stock" : request.AssetType,
Shares = (decimal)request.Amount,
AvgPrice = (decimal)request.Price,
Currency = request.Currency,
CreatedAt = DateTime.Now,
UpdatedAt = DateTime.Now
};
_db.Insertable(position).ExecuteCommand();
}
// 更新投资组合总价值(使用实时市值而不是成本价)
var Positions = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == request.PortfolioId)
.ToList();
decimal totalPortfolioValue = 0;
foreach (var pos in Positions)
{
if (pos.StockCode == null)
{
continue;
}
// 获取实时价格(自动路由数据源),失败则降级使用成本价
decimal CurrentPrice = pos.AvgPrice;
try
{
var priceResponse = _marketDataService.GetPriceAsync(pos.StockCode, pos.AssetType ?? "Stock").GetAwaiter().GetResult();
if (priceResponse.Price > 0)
{
CurrentPrice = priceResponse.Price;
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "获取标的 {StockCode} 实时价格失败,使用成本价计算组合总价值", pos.StockCode);
}
totalPortfolioValue += pos.Shares * CurrentPrice;
}
portfolio.TotalValue = totalPortfolioValue;
portfolio.UpdatedAt = DateTime.Now;
_db.Updateable(portfolio).ExecuteCommand();
// 删除该交易日期之后的净值历史记录,下次请求收益曲线时会自动重新计算
try
{
var deletedCount = await _navService.DeleteNavHistoryAfterDateAsync(request.PortfolioId, transactionTime.Date);
if (deletedCount > 0)
{
_logger.LogInformation("交易创建后删除净值历史: PortfolioId={PortfolioId}, Date={Date}, Count={Count}",
request.PortfolioId, transactionTime.Date, deletedCount);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "删除净值历史失败,将在下次请求时重新计算");
}
return new CreateTransactionResponse
{
Id = transaction.Id,
TotalAmount = (double)transaction.TotalAmount,
Status = transaction.Status,
CreatedAt = transaction.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")
};
}
// ===== 异步方法实现 =====
public Task<CreatePortfolioResponse> CreatePortfolioAsync(CreatePortfolioRequest request, string userId)
{
return Task.FromResult(CreatePortfolio(request, userId));
}
public Task<List<PortfolioListItem>> GetPortfolioListAsync(string userId)
{
return Task.FromResult(GetPortfolios(userId));
}
public Task<PortfolioDetailResponse> GetPortfolioDetailAsync(string portfolioId, string userId)
{
return GetPortfolioByIdAsync(portfolioId, userId);
}
public Task<List<TransactionItem>> GetTransactionsAsync(string portfolioId, GetTransactionsRequest request, string userId)
{
var response = GetTransactions(portfolioId, userId, request.Limit, request.Offset);
return Task.FromResult(response.Items ?? new List<TransactionItem>());
}
public async Task<TransactionItem> CreateTransactionAsync(string portfolioId, CreateTransactionRequest request, string userId)
{
request.PortfolioId = portfolioId;
var response = await CreateTransaction(request, userId);
return new TransactionItem
{
Id = response.Id,
PortfolioId = portfolioId,
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
Type = request.Type,
StockCode = request.StockCode,
Amount = response.TotalAmount,
Currency = request.Currency,
Status = response.Status,
Remark = request.Remark
};
}
public async Task<bool> DeletePortfolioAsync(string portfolioId, string userId)
{
var portfolio = _db.Queryable<Portfolio>()
.Where(p => p.Id == portfolioId && p.UserId == userId)
.First();
if (portfolio == null)
{
return false;
}
// 删除相关持仓
var Positions = _db.Queryable<Position>()
.Where(pos => pos.PortfolioId == portfolioId)
.ToList();
if (Positions.Any())
{
_db.Deleteable(Positions).ExecuteCommand();
}
// 删除相关交易
var transactions = _db.Queryable<Transaction>()
.Where(t => t.PortfolioId == portfolioId)
.ToList();
if (transactions.Any())
{
_db.Deleteable(transactions).ExecuteCommand();
}
// 删除净值历史
var navHistory = _db.Queryable<PortfolioNavHistory>()
.Where(n => n.PortfolioId == portfolioId)
.ToList();
if (navHistory.Any())
{
_db.Deleteable(navHistory).ExecuteCommand();
}
// 删除组合
_db.Deleteable(portfolio).ExecuteCommand();
return true;
}
}