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