重构组合服务接口和实现,添加用户ID参数以实现多租户隔离 更新组合控制器,从JWT令牌中提取用户ID并验证权限 完善组合服务数据库操作,包括组合创建、查询和交易处理 更新README文档,补充组合API详细说明
331 lines
11 KiB
C#
331 lines
11 KiB
C#
using AssetManager.Data;
|
|
using AssetManager.Models.DTOs;
|
|
using SqlSugar;
|
|
|
|
namespace AssetManager.Services;
|
|
|
|
public class PortfolioService : IPortfolioService
|
|
{
|
|
private readonly ISqlSugarClient _db;
|
|
|
|
public PortfolioService(ISqlSugarClient db)
|
|
{
|
|
_db = db;
|
|
}
|
|
|
|
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),
|
|
ReturnRate = 0,
|
|
Status = "运行中",
|
|
CreatedAt = DateTime.Now,
|
|
UpdatedAt = DateTime.Now
|
|
};
|
|
|
|
_db.Insertable(portfolio).ExecuteCommand();
|
|
|
|
// 创建初始持仓
|
|
foreach (var stock in request.stocks)
|
|
{
|
|
var position = new Position
|
|
{
|
|
Id = "pos-" + Guid.NewGuid().ToString().Substring(0, 8),
|
|
PortfolioId = portfolio.Id,
|
|
StockCode = stock.code,
|
|
StockName = stock.name,
|
|
AssetType = "Stock",
|
|
Shares = (decimal)stock.amount,
|
|
AvgPrice = (decimal)stock.price,
|
|
Currency = request.currency,
|
|
CreatedAt = DateTime.Now,
|
|
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 = "Stock",
|
|
Title = "初始建仓",
|
|
Amount = (decimal)stock.amount,
|
|
Price = (decimal)stock.price,
|
|
TotalAmount = (decimal)(stock.price * stock.amount),
|
|
Currency = request.currency,
|
|
Status = "completed",
|
|
Remark = "初始建仓",
|
|
TransactionTime = DateTime.Now,
|
|
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();
|
|
|
|
return portfolios.Select(p => new PortfolioListItem
|
|
{
|
|
id = p.Id,
|
|
name = p.Name,
|
|
tags = $"{p.Status} · {p.Currency}",
|
|
status = p.Status,
|
|
statusType = p.Status == "运行中" ? "green" : "gray",
|
|
iconChar = p.Name.Substring(0, 1).ToUpper(),
|
|
iconBgClass = "bg-blue-100",
|
|
iconTextClass = "text-blue-700",
|
|
value = (double)p.TotalValue,
|
|
currency = p.Currency,
|
|
returnRate = (double)p.ReturnRate,
|
|
returnType = p.ReturnRate >= 0 ? "positive" : "negative"
|
|
}).ToList();
|
|
}
|
|
|
|
public TotalAssetsResponse GetTotalAssets(string userId)
|
|
{
|
|
var totalValue = _db.Queryable<Portfolio>()
|
|
.Where(p => p.UserId == userId)
|
|
.Sum(p => p.TotalValue);
|
|
|
|
// 这里简化处理,实际应该计算今日盈亏和总收益率
|
|
return new TotalAssetsResponse
|
|
{
|
|
totalValue = (double)totalValue,
|
|
currency = "CNY", // 假设统一转换为CNY
|
|
todayProfit = 0,
|
|
todayProfitCurrency = "CNY",
|
|
totalReturnRate = 0
|
|
};
|
|
}
|
|
|
|
public PortfolioDetailResponse GetPortfolioById(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();
|
|
|
|
var transactions = _db.Queryable<Transaction>()
|
|
.Where(t => t.PortfolioId == id)
|
|
.OrderByDescending(t => t.TransactionTime)
|
|
.Take(5)
|
|
.ToList();
|
|
|
|
return new PortfolioDetailResponse
|
|
{
|
|
id = portfolio.Id,
|
|
name = portfolio.Name,
|
|
currency = portfolio.Currency,
|
|
strategy = new StrategyInfo
|
|
{
|
|
id = portfolio.StrategyId,
|
|
name = "策略名称", // 实际应该从策略表获取
|
|
description = "策略描述"
|
|
},
|
|
portfolioValue = (double)portfolio.TotalValue,
|
|
totalReturn = (double)(portfolio.TotalValue * portfolio.ReturnRate),
|
|
todayProfit = 0,
|
|
todayProfitCurrency = portfolio.Currency,
|
|
positions = positions.Select(pos => new PositionItem
|
|
{
|
|
id = pos.Id,
|
|
stockCode = pos.StockCode,
|
|
stockName = pos.StockName,
|
|
amount = (int)pos.Shares,
|
|
averagePrice = (double)pos.AvgPrice,
|
|
currentPrice = (double)pos.AvgPrice, // 实际应该从市场数据获取
|
|
totalValue = (double)(pos.Shares * pos.AvgPrice),
|
|
profit = 0,
|
|
profitRate = 0,
|
|
currency = pos.Currency
|
|
}).ToList(),
|
|
transactions = 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,
|
|
amount = (double)t.TotalAmount,
|
|
currency = t.Currency,
|
|
status = t.Status,
|
|
remark = t.Remark
|
|
}).ToList()
|
|
};
|
|
}
|
|
|
|
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,
|
|
amount = (double)t.TotalAmount,
|
|
currency = t.Currency,
|
|
status = t.Status,
|
|
remark = t.Remark
|
|
}).ToList(),
|
|
total = total,
|
|
page = offset / limit + 1,
|
|
pageSize = limit
|
|
};
|
|
}
|
|
|
|
public 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");
|
|
}
|
|
|
|
var transaction = new Transaction
|
|
{
|
|
Id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8),
|
|
PortfolioId = request.portfolioId,
|
|
Type = request.type,
|
|
StockCode = request.stockCode,
|
|
AssetType = "Stock",
|
|
Title = request.remark ?? "交易",
|
|
Amount = (decimal)request.amount,
|
|
Price = (decimal)request.price,
|
|
TotalAmount = (decimal)(request.price * request.amount),
|
|
Currency = request.currency,
|
|
Status = "processing",
|
|
Remark = request.remark,
|
|
TransactionTime = DateTime.Now,
|
|
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;
|
|
}
|
|
else if (request.type == "sell")
|
|
{
|
|
position.Shares -= (decimal)request.amount;
|
|
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 = "Stock",
|
|
Shares = (decimal)request.amount,
|
|
AvgPrice = (decimal)request.price,
|
|
Currency = request.currency,
|
|
CreatedAt = DateTime.Now,
|
|
UpdatedAt = DateTime.Now
|
|
};
|
|
_db.Insertable(position).ExecuteCommand();
|
|
}
|
|
|
|
// 更新投资组合总价值
|
|
var totalValue = _db.Queryable<Position>()
|
|
.Where(pos => pos.PortfolioId == request.portfolioId)
|
|
.Sum(pos => pos.Shares * pos.AvgPrice);
|
|
|
|
portfolio.TotalValue = totalValue;
|
|
portfolio.UpdatedAt = DateTime.Now;
|
|
_db.Updateable(portfolio).ExecuteCommand();
|
|
|
|
return new CreateTransactionResponse
|
|
{
|
|
id = transaction.Id,
|
|
totalAmount = (double)transaction.TotalAmount,
|
|
status = transaction.Status,
|
|
createdAt = transaction.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss")
|
|
};
|
|
}
|
|
}
|