diff --git a/AssetManager.API/Controllers/PortfolioController.cs b/AssetManager.API/Controllers/PortfolioController.cs index 24810cc..8b481f1 100644 --- a/AssetManager.API/Controllers/PortfolioController.cs +++ b/AssetManager.API/Controllers/PortfolioController.cs @@ -4,6 +4,7 @@ using AssetManager.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using System.Security.Claims; namespace AssetManager.API.Controllers; @@ -21,22 +22,30 @@ public class PortfolioController : ControllerBase _portfolioService = portfolioService; } - /// - /// 创建新投资组合 - /// - /// 投资组合创建请求参数 - /// 创建的投资组合详情 - /// - /// 此接口用于创建新的投资组合。 - /// + private string GetCurrentUserId() + { + return User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + } + [HttpPost] public ActionResult> CreatePortfolio([FromBody] CreatePortfolioRequest request) { try { + var userId = GetCurrentUserId(); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "用户未授权" + }); + } + _logger.LogInformation("Request to create portfolio"); - var response = _portfolioService.CreatePortfolio(request); + var response = _portfolioService.CreatePortfolio(request, userId); _logger.LogInformation("Portfolio created successfully"); @@ -60,28 +69,32 @@ public class PortfolioController : ControllerBase } } - /// - /// 获取投资组合列表 - /// - /// 投资组合列表 - /// - /// 此接口用于获取投资组合列表。 - /// [HttpGet] public ActionResult> GetPortfolios() { try { + var userId = GetCurrentUserId(); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "用户未授权" + }); + } + _logger.LogInformation("Request to get portfolios"); - var response = _portfolioService.GetPortfolios(); + var response = _portfolioService.GetPortfolios(userId); _logger.LogInformation("Portfolios retrieved successfully"); return Ok(new ApiResponse { code = AssetManager.Models.StatusCodes.Success, - data = response, + data = new GetPortfoliosResponse { items = response }, message = "success" }); } @@ -98,21 +111,25 @@ public class PortfolioController : ControllerBase } } - /// - /// 获取总资产情况 - /// - /// 总资产情况 - /// - /// 此接口用于获取用户的总资产情况。 - /// [HttpGet("assets")] public ActionResult> GetTotalAssets() { try { + var userId = GetCurrentUserId(); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "用户未授权" + }); + } + _logger.LogInformation("Request to get total assets"); - var response = _portfolioService.GetTotalAssets(); + var response = _portfolioService.GetTotalAssets(userId); _logger.LogInformation("Total assets retrieved successfully"); @@ -136,22 +153,25 @@ public class PortfolioController : ControllerBase } } - /// - /// 获取单个投资组合详情 - /// - /// 投资组合ID - /// 投资组合详情 - /// - /// 此接口用于获取指定投资组合的详细信息。 - /// [HttpGet("{id}")] public ActionResult> GetPortfolioById(string id) { try { + var userId = GetCurrentUserId(); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "用户未授权" + }); + } + _logger.LogInformation($"Request to get portfolio by id: {id}"); - var response = _portfolioService.GetPortfolioById(id); + var response = _portfolioService.GetPortfolioById(id, userId); _logger.LogInformation("Portfolio retrieved successfully"); @@ -175,24 +195,25 @@ public class PortfolioController : ControllerBase } } - /// - /// 获取交易记录 - /// - /// 投资组合ID - /// 每页记录数 - /// 偏移量 - /// 交易记录列表 - /// - /// 此接口用于获取指定投资组合的交易记录。 - /// [HttpGet("transactions")] public ActionResult> GetTransactions([FromQuery] string portfolioId, [FromQuery] int limit = 10, [FromQuery] int offset = 0) { try { + var userId = GetCurrentUserId(); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "用户未授权" + }); + } + _logger.LogInformation($"Request to get transactions for portfolio: {portfolioId}"); - var response = _portfolioService.GetTransactions(portfolioId, limit, offset); + var response = _portfolioService.GetTransactions(portfolioId, userId, limit, offset); _logger.LogInformation("Transactions retrieved successfully"); @@ -216,22 +237,25 @@ public class PortfolioController : ControllerBase } } - /// - /// 创建新交易 - /// - /// 交易创建请求参数 - /// 创建的交易详情 - /// - /// 此接口用于创建新的交易。 - /// [HttpPost("transactions")] public ActionResult> CreateTransaction([FromBody] CreateTransactionRequest request) { try { + var userId = GetCurrentUserId(); + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "用户未授权" + }); + } + _logger.LogInformation("Request to create transaction"); - var response = _portfolioService.CreateTransaction(request); + var response = _portfolioService.CreateTransaction(request, userId); _logger.LogInformation("Transaction created successfully"); diff --git a/AssetManager.Services/IPortfolioService.cs b/AssetManager.Services/IPortfolioService.cs index 8f8618b..f686d0f 100644 --- a/AssetManager.Services/IPortfolioService.cs +++ b/AssetManager.Services/IPortfolioService.cs @@ -4,10 +4,10 @@ namespace AssetManager.Services; public interface IPortfolioService { - CreatePortfolioResponse CreatePortfolio(CreatePortfolioRequest request); - PortfolioDetailResponse GetPortfolioById(string id); - GetTransactionsResponse GetTransactions(string portfolioId, int limit, int offset); - CreateTransactionResponse CreateTransaction(CreateTransactionRequest request); - GetPortfoliosResponse GetPortfolios(); - TotalAssetsResponse GetTotalAssets(); -} + CreatePortfolioResponse CreatePortfolio(CreatePortfolioRequest request, string userId); + List GetPortfolios(string userId); + TotalAssetsResponse GetTotalAssets(string userId); + PortfolioDetailResponse GetPortfolioById(string id, string userId); + GetTransactionsResponse GetTransactions(string portfolioId, string userId, int limit, int offset); + CreateTransactionResponse CreateTransaction(CreateTransactionRequest request, string userId); +} \ No newline at end of file diff --git a/AssetManager.Services/IStrategyService.cs b/AssetManager.Services/IStrategyService.cs new file mode 100644 index 0000000..5d3ea68 --- /dev/null +++ b/AssetManager.Services/IStrategyService.cs @@ -0,0 +1,13 @@ +using AssetManager.Data; +using AssetManager.Models.DTOs; + +namespace AssetManager.Services; + +public interface IStrategyService +{ + Strategy CreateStrategy(CreateStrategyRequest request, string userId); + List GetStrategies(string userId); + Strategy GetStrategyById(string id, string userId); + Strategy UpdateStrategy(string id, UpdateStrategyRequest request, string userId); + bool DeleteStrategy(string id, string userId); +} \ No newline at end of file diff --git a/AssetManager.Services/PortfolioService.cs b/AssetManager.Services/PortfolioService.cs index a0e5a48..3d59494 100644 --- a/AssetManager.Services/PortfolioService.cs +++ b/AssetManager.Services/PortfolioService.cs @@ -1,180 +1,330 @@ +using AssetManager.Data; using AssetManager.Models.DTOs; +using SqlSugar; namespace AssetManager.Services; public class PortfolioService : IPortfolioService { - public CreatePortfolioResponse CreatePortfolio(CreatePortfolioRequest request) + 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 = "port-" + Guid.NewGuid().ToString().Substring(0, 8), - totalValue = request.stocks.Sum(s => s.price * s.amount), + id = portfolio.Id, + totalValue = (double)portfolio.TotalValue, returnRate = 0, - currency = request.currency, - createdAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + currency = portfolio.Currency, + createdAt = portfolio.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss") }; } - public PortfolioDetailResponse GetPortfolioById(string id) + public List GetPortfolios(string userId) { - // 模拟获取投资组合详情 + var portfolios = _db.Queryable() + .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() + .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() + .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() + .Where(pos => pos.PortfolioId == id) + .ToList(); + + var transactions = _db.Queryable() + .Where(t => t.PortfolioId == id) + .OrderByDescending(t => t.TransactionTime) + .Take(5) + .ToList(); + return new PortfolioDetailResponse { - id = id, - name = "我的投资组合", - currency = "CNY", + id = portfolio.Id, + name = portfolio.Name, + currency = portfolio.Currency, strategy = new StrategyInfo { - id = "hfea", - name = "HFEA 风险平价策略", - description = "高风险高收益策略" + id = portfolio.StrategyId, + name = "策略名称", // 实际应该从策略表获取 + description = "策略描述" }, - portfolioValue = 125000, - totalReturn = 12500, - todayProfit = 2500, - todayProfitCurrency = "CNY", - positions = new List + portfolioValue = (double)portfolio.TotalValue, + totalReturn = (double)(portfolio.TotalValue * portfolio.ReturnRate), + todayProfit = 0, + todayProfitCurrency = portfolio.Currency, + positions = positions.Select(pos => new PositionItem { - new PositionItem - { - id = "pos-001", - stockCode = "UPRO", - stockName = "标普500三倍杠杆ETF", - amount = 100, - averagePrice = 50, - currentPrice = 55, - totalValue = 5500, - profit = 500, - profitRate = 10, - currency = "USD" - } - }, - transactions = new List + 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 { - new TransactionItem - { - id = "trans-001", - portfolioId = id, - date = "2026-02-24", - time = "14:30:00", - type = "buy", - title = "购买 UPRO", - amount = 5000, - currency = "USD", - status = "completed", - remark = "初始建仓" - } - } + 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, int limit, int offset) + public GetTransactionsResponse GetTransactions(string portfolioId, string userId, int limit, int offset) { - // 模拟获取交易记录 + // 验证投资组合是否属于该用户 + var portfolio = _db.Queryable() + .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() + .Where(t => t.PortfolioId == portfolioId) + .OrderByDescending(t => t.TransactionTime) + .Skip(offset) + .Take(limit) + .ToList(); + + var total = _db.Queryable() + .Where(t => t.PortfolioId == portfolioId) + .Count(); + return new GetTransactionsResponse { - items = new List + items = transactions.Select(t => new TransactionItem { - new TransactionItem - { - id = "trans-001", - portfolioId = portfolioId, - date = "2026-02-24", - time = "14:30:00", - type = "buy", - title = "购买 UPRO", - amount = 5000, - currency = "USD", - status = "completed", - remark = "初始建仓" - } - }, - total = 1, + 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) + public CreateTransactionResponse CreateTransaction(CreateTransactionRequest request, string userId) { - // 模拟执行交易 - return new CreateTransactionResponse - { - id = "trans-" + Guid.NewGuid().ToString().Substring(0, 8), - totalAmount = request.price * request.amount, - status = "processing", - createdAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") - }; - } + // 验证投资组合是否属于该用户 + var portfolio = _db.Queryable() + .Where(p => p.Id == request.portfolioId && p.UserId == userId) + .First(); - public GetPortfoliosResponse GetPortfolios() - { - // 模拟获取投资组合列表 - return new GetPortfoliosResponse + if (portfolio == null) { - items = new List + 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() + .Where(pos => pos.PortfolioId == request.portfolioId && pos.StockCode == request.stockCode) + .First(); + + if (position != null) + { + if (request.type == "buy") { - new PortfolioListItem + // 计算新的平均价格 + 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) { - id = "hfea-001", - name = "美股全天候杠杆", - tags = "HFEA · 季度调仓", - status = "监控中", - statusType = "green", - iconChar = "H", - iconBgClass = "bg-green-100", - iconTextClass = "text-green-700", - value = 156240, - currency = "USD", - returnRate = 42.82, - returnType = "positive" - }, - new PortfolioListItem + _db.Deleteable(position).ExecuteCommand(); + } + else { - id = "ma-002", - name = "纳指双均线趋势", - tags = "趋势跟踪 · 日线", - status = "等待信号", - statusType = "gray", - iconChar = "T", - iconBgClass = "bg-blue-100", - iconTextClass = "text-blue-700", - value = 412500, - currency = "USD", - returnRate = -1.79, - returnType = "negative" - }, - new PortfolioListItem - { - id = "hk-003", - name = "港股价值投资", - tags = "价值投资 · 蓝筹", - status = "持有中", - statusType = "green", - iconChar = "H", - iconBgClass = "bg-green-100", - iconTextClass = "text-green-700", - value = 896000, - currency = "HKD", - returnRate = 12.56, - returnType = "positive" + _db.Updateable(position).ExecuteCommand(); } } - }; - } - - public TotalAssetsResponse GetTotalAssets() - { - // 模拟获取总资产情况 - return new TotalAssetsResponse + } + else if (request.type == "buy") { - totalValue = 1284592.4, - currency = "CNY", - todayProfit = 12482, - todayProfitCurrency = "CNY", - totalReturnRate = 24.82 + // 创建新持仓 + 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() + .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") }; } } diff --git a/AssetManager.Services/StrategyService.cs b/AssetManager.Services/StrategyService.cs index 096d27f..5d3fdc4 100644 --- a/AssetManager.Services/StrategyService.cs +++ b/AssetManager.Services/StrategyService.cs @@ -4,15 +4,6 @@ using SqlSugar; namespace AssetManager.Services; -public interface IStrategyService -{ - Strategy CreateStrategy(CreateStrategyRequest request, string userId); - List GetStrategies(string userId); - Strategy GetStrategyById(string id, string userId); - Strategy UpdateStrategy(string id, UpdateStrategyRequest request, string userId); - bool DeleteStrategy(string id, string userId); -} - public class StrategyService : IStrategyService { private readonly ISqlSugarClient _db; diff --git a/README.md b/README.md index 276a3c7..a629ad8 100644 --- a/README.md +++ b/README.md @@ -140,4 +140,321 @@ AssetManager "Title": "双均线策略", "Status": "created" } -``` \ No newline at end of file +``` + +## 💼 投资组合 API (Portfolio API) + +### 1. 创建投资组合 + +**请求 URL**: `POST /api/v1/portfolio` + +**请求头**: +``` +Authorization: Bearer {token} +Content-Type: application/json +``` + +**请求体示例**: + +```json +{ + "name": "我的投资组合", + "strategyId": "strategy-123", + "currency": "USD", + "stocks": [ + { + "name": "Apple Inc.", + "code": "AAPL", + "price": 150.50, + "amount": 100, + "date": "2024-01-01", + "currency": "USD" + }, + { + "name": "Microsoft Corp.", + "code": "MSFT", + "price": 300.25, + "amount": 50, + "date": "2024-01-01", + "currency": "USD" + } + ] +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "data": { + "id": "port-abc12345", + "totalValue": 30075.00, + "returnRate": 0.0, + "currency": "USD", + "createdAt": "2024-01-01 10:30:00" + }, + "message": "success" +} +``` + +### 2. 获取投资组合列表 + +**请求 URL**: `GET /api/v1/portfolio` + +**请求头**: +``` +Authorization: Bearer {token} +``` + +**响应示例**: + +```json +{ + "code": 200, + "data": { + "items": [ + { + "id": "port-abc12345", + "name": "我的投资组合", + "tags": "科技股", + "status": "运行中", + "statusType": "active", + "iconChar": "P", + "iconBgClass": "bg-blue-500", + "iconTextClass": "text-white", + "value": 30075.00, + "currency": "USD", + "returnRate": 0.15, + "returnType": "positive" + }, + { + "id": "port-def67890", + "name": "保守组合", + "tags": "债券", + "status": "运行中", + "statusType": "active", + "iconChar": "C", + "iconBgClass": "bg-green-500", + "iconTextClass": "text-white", + "value": 50000.00, + "currency": "USD", + "returnRate": 0.08, + "returnType": "positive" + } + ] + }, + "message": "success" +} +``` + +### 3. 获取总资产情况 + +**请求 URL**: `GET /api/v1/portfolio/assets` + +**请求头**: +``` +Authorization: Bearer {token} +``` + +**响应示例**: + +```json +{ + "code": 200, + "data": { + "totalValue": 80075.00, + "currency": "USD", + "todayProfit": 1250.50, + "todayProfitCurrency": "USD", + "totalReturnRate": 0.12 + }, + "message": "success" +} +``` + +### 4. 获取单个投资组合详情 + +**请求 URL**: `GET /api/v1/portfolio/{id}` + +**请求头**: +``` +Authorization: Bearer {token} +``` + +**响应示例**: + +```json +{ + "code": 200, + "data": { + "id": "port-abc12345", + "name": "我的投资组合", + "currency": "USD", + "strategy": { + "id": "strategy-123", + "name": "双均线策略", + "description": "基于短期和长期移动平均线的趋势跟踪策略" + }, + "portfolioValue": 30075.00, + "totalReturn": 0.15, + "todayProfit": 1250.50, + "todayProfitCurrency": "USD", + "positions": [ + { + "id": "pos-xyz12345", + "stockCode": "AAPL", + "stockName": "Apple Inc.", + "amount": 100, + "averagePrice": 150.50, + "currentPrice": 155.00, + "totalValue": 15500.00, + "profit": 450.00, + "profitRate": 0.03, + "currency": "USD" + }, + { + "id": "pos-xyz67890", + "stockCode": "MSFT", + "stockName": "Microsoft Corp.", + "amount": 50, + "averagePrice": 300.25, + "currentPrice": 291.50, + "totalValue": 14575.00, + "profit": -437.50, + "profitRate": -0.03, + "currency": "USD" + } + ], + "transactions": [ + { + "id": "trans-abc12345", + "portfolioId": "port-abc12345", + "date": "2024-01-01", + "time": "10:30:00", + "type": "buy", + "title": "初始建仓", + "amount": 100, + "currency": "USD", + "status": "completed", + "remark": "初始建仓" + } + ] + }, + "message": "success" +} +``` + +### 5. 获取交易记录 + +**请求 URL**: `GET /api/v1/portfolio/transactions` + +**请求头**: +``` +Authorization: Bearer {token} +``` + +**查询参数**: +- `portfolioId` (必填): 投资组合ID +- `limit` (可选): 每页记录数,默认 10 +- `offset` (可选): 偏移量,默认 0 + +**请求示例**: +``` +GET /api/v1/portfolio/transactions?portfolioId=port-abc12345&limit=10&offset=0 +``` + +**响应示例**: + +```json +{ + "code": 200, + "data": { + "items": [ + { + "id": "trans-abc12345", + "portfolioId": "port-abc12345", + "date": "2024-01-15", + "time": "14:30:00", + "type": "buy", + "title": "买入AAPL", + "amount": 50, + "currency": "USD", + "status": "completed", + "remark": "加仓" + }, + { + "id": "trans-def67890", + "portfolioId": "port-abc12345", + "date": "2024-01-10", + "time": "10:15:00", + "type": "sell", + "title": "卖出MSFT", + "amount": 25, + "currency": "USD", + "status": "completed", + "remark": "减仓" + } + ], + "total": 25, + "page": 1, + "pageSize": 10 + }, + "message": "success" +} +``` + +### 6. 创建交易 + +**请求 URL**: `POST /api/v1/portfolio/transactions` + +**请求头**: +``` +Authorization: Bearer {token} +Content-Type: application/json +``` + +**请求体示例**: + +```json +{ + "portfolioId": "port-abc12345", + "type": "buy", + "stockCode": "AAPL", + "amount": 50, + "price": 155.00, + "currency": "USD", + "remark": "加仓" +} +``` + +**响应示例**: + +```json +{ + "code": 200, + "data": { + "id": "trans-xyz12345", + "totalAmount": 7750.00, + "status": "processing", + "createdAt": "2024-01-15 14:30:00" + }, + "message": "success" +} +``` + +### 错误响应格式 + +所有接口在发生错误时都会返回统一的错误格式: + +```json +{ + "code": 401, + "data": null, + "message": "用户未授权" +} +``` + +**常见错误码**: +- `401`: 用户未授权 +- `500`: 服务器内部错误 \ No newline at end of file