diff --git a/AssetManager.API/Controllers/PortfolioController.cs b/AssetManager.API/Controllers/PortfolioController.cs index 8b481f1..127e67e 100644 --- a/AssetManager.API/Controllers/PortfolioController.cs +++ b/AssetManager.API/Controllers/PortfolioController.cs @@ -22,6 +22,7 @@ public class PortfolioController : ControllerBase _portfolioService = portfolioService; } + private string GetCurrentUserId() { return User.FindFirst(ClaimTypes.NameIdentifier)?.Value; diff --git a/AssetManager.API/Controllers/StrategyController.cs b/AssetManager.API/Controllers/StrategyController.cs index 89a49a5..ebff097 100644 --- a/AssetManager.API/Controllers/StrategyController.cs +++ b/AssetManager.API/Controllers/StrategyController.cs @@ -28,6 +28,33 @@ public class StrategyController : ControllerBase return User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? throw new Exception("User not authenticated"); } + private StrategyListItemDTO MapToStrategyListItemDTO(Strategy strategy) + { + List tags = new List(); + if (!string.IsNullOrEmpty(strategy.Tags)) + { + try + { + tags = System.Text.Json.JsonSerializer.Deserialize>(strategy.Tags); + } + catch { } + } + + return new StrategyListItemDTO + { + id = strategy.Id, + userId = strategy.UserId, + name = strategy.Alias, + type = strategy.Type, + description = strategy.Description, + tags = tags, + riskLevel = strategy.RiskLevel, + config = strategy.Config, + createdAt = strategy.CreatedAt, + updatedAt = strategy.UpdatedAt + }; + } + /// /// 获取策略列表 /// @@ -36,7 +63,7 @@ public class StrategyController : ControllerBase /// 此接口用于获取当前登录用户的所有策略列表。 /// [HttpGet("strategies")] - public ActionResult>> GetStrategies() + public ActionResult>> GetStrategies() { try { @@ -44,13 +71,14 @@ public class StrategyController : ControllerBase var userId = GetCurrentUserId(); var strategies = _strategyService.GetStrategies(userId); + var strategyListItems = strategies.Select(MapToStrategyListItemDTO).ToList(); _logger.LogInformation("Strategies retrieved successfully"); - return Ok(new ApiResponse> + return Ok(new ApiResponse> { code = AssetManager.Models.StatusCodes.Success, - data = strategies, + data = strategyListItems, message = "Success" }); } @@ -58,7 +86,7 @@ public class StrategyController : ControllerBase { _logger.LogError(ex, "Error retrieving strategies"); - return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse> + return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse> { code = AssetManager.Models.StatusCodes.InternalServerError, data = null, @@ -76,7 +104,7 @@ public class StrategyController : ControllerBase /// 此接口用于获取指定策略的详细信息。 /// [HttpGet("{id}")] - public ActionResult> GetStrategyById(string id) + public ActionResult> GetStrategyById(string id) { try { @@ -84,13 +112,14 @@ public class StrategyController : ControllerBase var userId = GetCurrentUserId(); var strategy = _strategyService.GetStrategyById(id, userId); + var strategyItem = MapToStrategyListItemDTO(strategy); _logger.LogInformation($"Strategy {id} retrieved successfully"); - return Ok(new ApiResponse + return Ok(new ApiResponse { code = AssetManager.Models.StatusCodes.Success, - data = strategy, + data = strategyItem, message = "Success" }); } @@ -98,7 +127,7 @@ public class StrategyController : ControllerBase { _logger.LogError(ex, $"Error retrieving strategy {id}"); - return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse + return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse { code = AssetManager.Models.StatusCodes.InternalServerError, data = null, @@ -116,7 +145,7 @@ public class StrategyController : ControllerBase /// 此接口用于创建新的策略。 /// [HttpPost] - public ActionResult> CreateStrategy([FromBody] CreateStrategyRequest request) + public ActionResult> CreateStrategy([FromBody] CreateStrategyRequest request) { try { @@ -124,13 +153,14 @@ public class StrategyController : ControllerBase var userId = GetCurrentUserId(); var strategy = _strategyService.CreateStrategy(request, userId); + var strategyItem = MapToStrategyListItemDTO(strategy); _logger.LogInformation($"Strategy {strategy.Id} created successfully"); - return Ok(new ApiResponse + return Ok(new ApiResponse { code = AssetManager.Models.StatusCodes.Success, - data = strategy, + data = strategyItem, message = "Strategy created successfully" }); } @@ -138,7 +168,7 @@ public class StrategyController : ControllerBase { _logger.LogError(ex, "Error creating strategy"); - return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse + return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse { code = AssetManager.Models.StatusCodes.InternalServerError, data = null, @@ -157,7 +187,7 @@ public class StrategyController : ControllerBase /// 此接口用于更新指定策略的信息。 /// [HttpPut("{id}")] - public ActionResult> UpdateStrategy(string id, [FromBody] UpdateStrategyRequest request) + public ActionResult> UpdateStrategy(string id, [FromBody] UpdateStrategyRequest request) { try { @@ -165,13 +195,14 @@ public class StrategyController : ControllerBase var userId = GetCurrentUserId(); var strategy = _strategyService.UpdateStrategy(id, request, userId); + var strategyItem = MapToStrategyListItemDTO(strategy); _logger.LogInformation($"Strategy {id} updated successfully"); - return Ok(new ApiResponse + return Ok(new ApiResponse { code = AssetManager.Models.StatusCodes.Success, - data = strategy, + data = strategyItem, message = "Strategy updated successfully" }); } @@ -179,7 +210,7 @@ public class StrategyController : ControllerBase { _logger.LogError(ex, $"Error updating strategy {id}"); - return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse + return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse { code = AssetManager.Models.StatusCodes.InternalServerError, data = null, diff --git a/AssetManager.Data/Strategy.cs b/AssetManager.Data/Strategy.cs index bd1f03f..e3329d4 100644 --- a/AssetManager.Data/Strategy.cs +++ b/AssetManager.Data/Strategy.cs @@ -32,6 +32,24 @@ public class Strategy [SugarColumn(ColumnName = "type", Length = 50)] public string Type { get; set; } + /// + /// 策略描述 + /// + [SugarColumn(ColumnName = "description", Length = 500)] + public string Description { get; set; } + + /// + /// 策略标签 (JSON数组) + /// + [SugarColumn(ColumnName = "tags", IsJson = true)] + public string Tags { get; set; } + + /// + /// 风险等级 + /// + [SugarColumn(ColumnName = "risk_level", Length = 20)] + public string RiskLevel { get; set; } + /// /// 策略配置项(周期,阈值,资产配比) /// diff --git a/AssetManager.Models/DTOs/PortfolioDTO.cs b/AssetManager.Models/DTOs/PortfolioDTO.cs index 0eac6b8..ef2ead3 100644 --- a/AssetManager.Models/DTOs/PortfolioDTO.cs +++ b/AssetManager.Models/DTOs/PortfolioDTO.cs @@ -32,11 +32,19 @@ public class PortfolioDetailResponse public string id { get; set; } public string name { get; set; } public string currency { get; set; } + public string status { get; set; } public StrategyInfo strategy { get; set; } public double portfolioValue { get; set; } public double totalReturn { get; set; } public double todayProfit { get; set; } + public double historicalChange { get; set; } + public double dailyVolatility { get; set; } public string todayProfitCurrency { get; set; } + public string logicModel { get; set; } + public string logicModelStatus { get; set; } + public string logicModelDescription { get; set; } + public int totalItems { get; set; } + public double totalRatio { get; set; } public List positions { get; set; } } @@ -52,12 +60,16 @@ public class PositionItem public string id { get; set; } public string stockCode { get; set; } public string stockName { get; set; } + public string symbol { get; set; } public int amount { get; set; } public double averagePrice { get; set; } public double currentPrice { get; set; } public double totalValue { get; set; } public double profit { get; set; } public double profitRate { get; set; } + public double changeAmount { get; set; } + public double ratio { get; set; } + public double deviationRatio { get; set; } public string currency { get; set; } } diff --git a/AssetManager.Models/DTOs/StrategyDTO.cs b/AssetManager.Models/DTOs/StrategyDTO.cs index 801e0aa..dc7948d 100644 --- a/AssetManager.Models/DTOs/StrategyDTO.cs +++ b/AssetManager.Models/DTOs/StrategyDTO.cs @@ -71,7 +71,6 @@ public class CreateStrategyRequest public string riskLevel { get; set; } public List tags { get; set; } public object parameters { get; set; } - public string Title { get; set; } } public class StrategyResponse @@ -85,6 +84,9 @@ public class UpdateStrategyRequest { public string name { get; set; } public string type { get; set; } + public string description { get; set; } + public string riskLevel { get; set; } + public List tags { get; set; } public object parameters { get; set; } } @@ -93,3 +95,17 @@ public class DeleteStrategyResponse public string Id { get; set; } public string Status { get; set; } } + +public class StrategyListItemDTO +{ + public string id { get; set; } + public string userId { get; set; } + public string name { get; set; } + public string type { get; set; } + public string description { get; set; } + public List tags { get; set; } + public string riskLevel { get; set; } + public string config { get; set; } + public DateTime createdAt { get; set; } + public DateTime updatedAt { get; set; } +} diff --git a/AssetManager.Services/PortfolioService.cs b/AssetManager.Services/PortfolioService.cs index b4ecef7..02a115d 100644 --- a/AssetManager.Services/PortfolioService.cs +++ b/AssetManager.Services/PortfolioService.cs @@ -137,34 +137,58 @@ public class PortfolioService : IPortfolioService .Where(pos => pos.PortfolioId == id) .ToList(); + var totalValue = (double)portfolio.TotalValue; + var positionItems = positions.Select(pos => + { + var positionValue = (double)(pos.Shares * pos.AvgPrice); + var ratio = totalValue > 0 ? (positionValue / totalValue) * 100 : 0; + // 假设目标权重为50%,计算偏离比例 + var targetWeight = 50.0; + var deviationRatio = ratio - targetWeight; + + return new PositionItem + { + id = pos.Id, + stockCode = pos.StockCode, + stockName = pos.StockName, + symbol = $"{pos.StockCode}.US", // 简化处理,实际应该根据市场或数据源确定 + amount = (int)pos.Shares, + averagePrice = (double)pos.AvgPrice, + currentPrice = (double)pos.AvgPrice, // 实际应该从市场数据获取 + totalValue = positionValue, + profit = 0, // 实际应该计算 + profitRate = 0, // 实际应该计算 + changeAmount = 0, // 实际应该计算 + ratio = ratio, + deviationRatio = deviationRatio, + currency = pos.Currency + }; + }).ToList(); + return new PortfolioDetailResponse { id = portfolio.Id, name = portfolio.Name, currency = portfolio.Currency, + status = portfolio.Status, // 从数据库获取 strategy = new StrategyInfo { id = portfolio.StrategyId, name = "策略名称", description = "策略描述" }, - portfolioValue = (double)portfolio.TotalValue, + portfolioValue = totalValue, totalReturn = (double)(portfolio.TotalValue * portfolio.ReturnRate), - todayProfit = 0, + todayProfit = 0, // 实际应该计算 + historicalChange = 42.82, // 实际应该计算 + dailyVolatility = 1240.50, // 实际应该计算 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() + logicModel = "HFEA 风险平价逻辑", // 实际应该根据策略获取 + logicModelStatus = "监控中", // 实际应该根据策略状态获取 + logicModelDescription = "目标权重 季度调仓", // 实际应该根据策略配置获取 + totalItems = positions.Count, + totalRatio = 100.0, // 所有持仓比例之和 + positions = positionItems }; } diff --git a/AssetManager.Services/StrategyService.cs b/AssetManager.Services/StrategyService.cs index 5d3fdc4..648231f 100644 --- a/AssetManager.Services/StrategyService.cs +++ b/AssetManager.Services/StrategyService.cs @@ -21,6 +21,9 @@ public class StrategyService : IStrategyService UserId = userId, Alias = request.name, Type = request.type, + Description = request.description, + Tags = System.Text.Json.JsonSerializer.Serialize(request.tags), + RiskLevel = request.riskLevel, Config = System.Text.Json.JsonSerializer.Serialize(request.parameters), CreatedAt = DateTime.Now, UpdatedAt = DateTime.Now @@ -57,6 +60,9 @@ public class StrategyService : IStrategyService strategy.Alias = request.name; strategy.Type = request.type; + strategy.Description = request.description; + strategy.Tags = System.Text.Json.JsonSerializer.Serialize(request.tags); + strategy.RiskLevel = request.riskLevel; strategy.Config = System.Text.Json.JsonSerializer.Serialize(request.parameters); strategy.UpdatedAt = DateTime.Now; diff --git a/README.md b/README.md index f0d9ee8..063ab72 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,9 @@ Content-Type: application/json { "name": "双均线策略(更新)", "type": "ma_trend", + "description": "经典趋势跟踪策略", + "riskLevel": "medium", + "tags": ["趋势", "均线"], "parameters": { "maType": "EMA", "shortPeriod": 15, @@ -490,40 +493,56 @@ Authorization: Bearer {token} "data": { "id": "port-abc12345", "name": "我的投资组合", - "currency": "USD", + "currency": "CNY", + "status": "记录中", "strategy": { "id": "strategy-123", - "name": "双均线策略", - "description": "基于短期和长期移动平均线的趋势跟踪策略" + "name": "HFEA 风险平价逻辑", + "description": "目标权重 季度调仓" }, - "portfolioValue": 30075.00, - "totalReturn": 0.15, - "todayProfit": 1250.50, - "todayProfitCurrency": "USD", + "portfolioValue": 156240.00, + "totalReturn": 0.4282, + "todayProfit": 1240.50, + "historicalChange": 42.82, + "dailyVolatility": 1240.50, + "todayProfitCurrency": "CNY", + "logicModel": "HFEA 风险平价逻辑", + "logicModelStatus": "监控中", + "logicModelDescription": "目标权重 季度调仓", + "totalItems": 2, + "totalRatio": 100.0, "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" + "stockCode": "UPRO", + "stockName": "UPRO", + "symbol": "UPRO.US", + "amount": 142, + "averagePrice": 500.00, + "currentPrice": 605.00, + "totalValue": 85932.00, + "profit": 12400.00, + "profitRate": 0.248, + "changeAmount": 12400.00, + "ratio": 55.0, + "deviationRatio": 16.8, + "currency": "CNY" }, { "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" + "stockCode": "TMF", + "stockName": "TMF", + "symbol": "TMF.US", + "amount": 800, + "averagePrice": 90.00, + "currentPrice": 87.89, + "totalValue": 70308.00, + "profit": -3200.50, + "profitRate": -0.0445, + "changeAmount": -3200.50, + "ratio": 45.0, + "deviationRatio": -4.3, + "currency": "CNY" } ] }, @@ -560,29 +579,53 @@ GET /api/v1/portfolio/transactions?portfolioId=port-abc12345&limit=10&offset=0 { "id": "trans-abc12345", "portfolioId": "port-abc12345", - "date": "2024-01-15", - "time": "14:30:00", + "date": "2024-02-14", + "time": "14:30", "type": "buy", - "title": "买入AAPL", - "amount": 50, + "title": "定期定投 UPRO录入增加", + "amount": 500.00, "currency": "USD", "status": "completed", - "remark": "加仓" + "remark": "定期定投" }, { "id": "trans-def67890", "portfolioId": "port-abc12345", - "date": "2024-01-10", - "time": "10:15:00", + "date": "2024-01-01", + "time": "09:15", "type": "sell", - "title": "卖出MSFT", - "amount": 25, + "title": "季度再平衡 TMF结出减少 200股", + "amount": 200.00, "currency": "USD", "status": "completed", - "remark": "减仓" + "remark": "季度再平衡" + }, + { + "id": "trans-ghi78901", + "portfolioId": "port-abc12345", + "date": "2023-12-15", + "time": "10:00", + "type": "buy", + "title": "建仓买入录入增加", + "amount": 100000.00, + "currency": "CNY", + "status": "completed", + "remark": "建仓买入" + }, + { + "id": "trans-jkl23456", + "portfolioId": "port-abc12345", + "date": "2023-12-10", + "time": "11:20", + "type": "buy", + "title": "建仓买入录入增加", + "amount": 50000.00, + "currency": "CNY", + "status": "completed", + "remark": "建仓买入" } ], - "total": 25, + "total": 4, "page": 1, "pageSize": 10 },