refactor: 将模型属性改为可为空类型以增强健壮性

- 修改ApiResponse、RiskParityConfig等DTO类的属性为可空类型
- 在策略计算器中添加空值检查逻辑
- 更新服务层代码处理可能的空值情况
- 添加发布配置文件FolderProfile.pubxml
This commit is contained in:
niannian zheng 2026-03-06 15:51:59 +08:00
parent c994a5bb76
commit b5499ef7fe
21 changed files with 295 additions and 226 deletions

View File

@ -44,6 +44,15 @@ public class AuthController : ControllerBase
_logger.LogInformation("Wechat login attempt"); _logger.LogInformation("Wechat login attempt");
// 用code换取openid // 用code换取openid
if (request.Code == null)
{
return BadRequest(new ApiResponse<WechatLoginResponse>
{
code = AssetManager.Models.StatusCodes.BadRequest,
data = null,
message = "Code is required"
});
}
var authResult = await _wechatService.GetOpenIdAsync(request.Code); var authResult = await _wechatService.GetOpenIdAsync(request.Code);
if (authResult.Errcode != 0) if (authResult.Errcode != 0)
@ -93,7 +102,7 @@ public class AuthController : ControllerBase
} }
// 生成真实的JWT令牌 // 生成真实的JWT令牌
var token = _jwtService.GenerateToken(user.Id, user.UserName, user.Email); var token = _jwtService.GenerateToken(user.Id ?? "", user.UserName ?? "", user.Email ?? "");
var response = new WechatLoginResponse var response = new WechatLoginResponse
{ {

View File

@ -41,7 +41,7 @@ public class PortfolioController : ControllerBase
/// </summary> /// </summary>
private string GetCurrentUserId() private string GetCurrentUserId()
{ {
return User.FindFirst(ClaimTypes.NameIdentifier)?.Value; return User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? throw new Exception("User not authenticated");
} }
/// <summary> /// <summary>
@ -433,6 +433,15 @@ public class PortfolioController : ControllerBase
} }
// 2. 获取策略 // 2. 获取策略
if (portfolio.StrategyId == null)
{
return NotFound(new ApiResponse<StrategySignal>
{
code = AssetManager.Models.StatusCodes.NotFound,
data = null,
message = "策略ID不存在"
});
}
var strategy = _strategyService.GetStrategyById(portfolio.StrategyId, userId); var strategy = _strategyService.GetStrategyById(portfolio.StrategyId, userId);
if (strategy == null) if (strategy == null)
{ {

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<DeleteExistingFiles>false</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net8.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>

View File

@ -12,31 +12,31 @@ public class Portfolio
/// 主键 /// 主键
/// </summary> /// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = false)] [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
public string Id { get; set; } public string? Id { get; set; }
/// <summary> /// <summary>
/// 所属用户ID /// 所属用户ID
/// </summary> /// </summary>
[SugarColumn(ColumnName = "user_id", IndexGroupNameList = new string[] { "idx_user_id" })] [SugarColumn(ColumnName = "user_id", IndexGroupNameList = new string[] { "idx_user_id" })]
public string UserId { get; set; } public string? UserId { get; set; }
/// <summary> /// <summary>
/// 所用策略ID /// 所用策略ID
/// </summary> /// </summary>
[SugarColumn(ColumnName = "strategy_id", IndexGroupNameList = new string[] { "idx_strategy_id" })] [SugarColumn(ColumnName = "strategy_id", IndexGroupNameList = new string[] { "idx_strategy_id" })]
public string StrategyId { get; set; } public string? StrategyId { get; set; }
/// <summary> /// <summary>
/// 组合名称 /// 组合名称
/// </summary> /// </summary>
[SugarColumn(ColumnName = "name", Length = 200)] [SugarColumn(ColumnName = "name", Length = 200)]
public string Name { get; set; } public string? Name { get; set; }
/// <summary> /// <summary>
/// 组合币种 (USD/CNY等) /// 组合币种 (USD/CNY等)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "currency", Length = 10)] [SugarColumn(ColumnName = "currency", Length = 10)]
public string Currency { get; set; } public string? Currency { get; set; }
/// <summary> /// <summary>
/// 当前总市值 (可冗余或实时计算) /// 当前总市值 (可冗余或实时计算)
@ -54,7 +54,7 @@ public class Portfolio
/// 运行状态 (运行中/监控中) /// 运行状态 (运行中/监控中)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "status", Length = 50)] [SugarColumn(ColumnName = "status", Length = 50)]
public string Status { get; set; } public string? Status { get; set; }
/// <summary> /// <summary>
/// 创建时间 /// 创建时间
@ -79,31 +79,31 @@ public class Position
/// 主键 /// 主键
/// </summary> /// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = false)] [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
public string Id { get; set; } public string? Id { get; set; }
/// <summary> /// <summary>
/// 所属组合ID /// 所属组合ID
/// </summary> /// </summary>
[SugarColumn(ColumnName = "portfolio_id", IndexGroupNameList = new string[] { "idx_portfolio_id" })] [SugarColumn(ColumnName = "portfolio_id", IndexGroupNameList = new string[] { "idx_portfolio_id" })]
public string PortfolioId { get; set; } public string? PortfolioId { get; set; }
/// <summary> /// <summary>
/// 标的代码 (如: UPRO.US) /// 标的代码 (如: UPRO.US)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "stock_code", Length = 50)] [SugarColumn(ColumnName = "stock_code", Length = 50)]
public string StockCode { get; set; } public string? StockCode { get; set; }
/// <summary> /// <summary>
/// 标的名称 /// 标的名称
/// </summary> /// </summary>
[SugarColumn(ColumnName = "stock_name", Length = 200)] [SugarColumn(ColumnName = "stock_name", Length = 200)]
public string StockName { get; set; } public string? StockName { get; set; }
/// <summary> /// <summary>
/// 资产类型 (Stock/Crypto) /// 资产类型 (Stock/Crypto)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "asset_type", Length = 20)] [SugarColumn(ColumnName = "asset_type", Length = 20)]
public string AssetType { get; set; } public string? AssetType { get; set; }
/// <summary> /// <summary>
/// 持有数量 /// 持有数量
@ -121,7 +121,7 @@ public class Position
/// 标的币种 /// 标的币种
/// </summary> /// </summary>
[SugarColumn(ColumnName = "currency", Length = 10)] [SugarColumn(ColumnName = "currency", Length = 10)]
public string Currency { get; set; } public string? Currency { get; set; }
/// <summary> /// <summary>
/// 建仓时间 /// 建仓时间
@ -146,37 +146,37 @@ public class Transaction
/// 主键 /// 主键
/// </summary> /// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = false)] [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
public string Id { get; set; } public string? Id { get; set; }
/// <summary> /// <summary>
/// 所属组合ID /// 所属组合ID
/// </summary> /// </summary>
[SugarColumn(ColumnName = "portfolio_id", IndexGroupNameList = new string[] { "idx_portfolio_id" })] [SugarColumn(ColumnName = "portfolio_id", IndexGroupNameList = new string[] { "idx_portfolio_id" })]
public string PortfolioId { get; set; } public string? PortfolioId { get; set; }
/// <summary> /// <summary>
/// 交易类型 (buy/sell) /// 交易类型 (buy/sell)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "type", Length = 20)] [SugarColumn(ColumnName = "type", Length = 20)]
public string Type { get; set; } public string? Type { get; set; }
/// <summary> /// <summary>
/// 标的代码 /// 标的代码
/// </summary> /// </summary>
[SugarColumn(ColumnName = "stock_code", Length = 50)] [SugarColumn(ColumnName = "stock_code", Length = 50)]
public string StockCode { get; set; } public string? StockCode { get; set; }
/// <summary> /// <summary>
/// 资产类型 (Stock/Crypto) /// 资产类型 (Stock/Crypto)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "asset_type", Length = 20)] [SugarColumn(ColumnName = "asset_type", Length = 20)]
public string AssetType { get; set; } public string? AssetType { get; set; }
/// <summary> /// <summary>
/// 交易标题 (如: 定期定投) /// 交易标题 (如: 定期定投)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "title", Length = 200)] [SugarColumn(ColumnName = "title", Length = 200)]
public string Title { get; set; } public string? Title { get; set; }
/// <summary> /// <summary>
/// 交易数量 /// 交易数量
@ -200,19 +200,19 @@ public class Transaction
/// 交易币种 /// 交易币种
/// </summary> /// </summary>
[SugarColumn(ColumnName = "currency", Length = 10)] [SugarColumn(ColumnName = "currency", Length = 10)]
public string Currency { get; set; } public string? Currency { get; set; }
/// <summary> /// <summary>
/// 交易状态 (processing/completed) /// 交易状态 (processing/completed)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "status", Length = 50)] [SugarColumn(ColumnName = "status", Length = 50)]
public string Status { get; set; } public string? Status { get; set; }
/// <summary> /// <summary>
/// 交易备注 /// 交易备注
/// </summary> /// </summary>
[SugarColumn(ColumnName = "remark", Length = 500, IsNullable = true)] [SugarColumn(ColumnName = "remark", Length = 500, IsNullable = true)]
public string Remark { get; set; } public string? Remark { get; set; }
/// <summary> /// <summary>
/// 交易发生时间 /// 交易发生时间

View File

@ -12,49 +12,49 @@ public class Strategy
/// 主键 /// 主键
/// </summary> /// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = false)] [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
public string Id { get; set; } public string? Id { get; set; }
/// <summary> /// <summary>
/// 所属用户ID /// 所属用户ID
/// </summary> /// </summary>
[SugarColumn(ColumnName = "user_id", Length = 100, IndexGroupNameList = new string[] { "idx_user_id" })] [SugarColumn(ColumnName = "user_id", Length = 100, IndexGroupNameList = new string[] { "idx_user_id" })]
public string UserId { get; set; } public string? UserId { get; set; }
/// <summary> /// <summary>
/// 策略别名/名称 /// 策略别名/名称
/// </summary> /// </summary>
[SugarColumn(ColumnName = "alias", Length = 200)] [SugarColumn(ColumnName = "alias", Length = 200)]
public string Alias { get; set; } public string? Alias { get; set; }
/// <summary> /// <summary>
/// 策略类型 (如: ma_trend, chandelier_exit, risk_parity) /// 策略类型 (如: ma_trend, chandelier_exit, risk_parity)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "type", Length = 50)] [SugarColumn(ColumnName = "type", Length = 50)]
public string Type { get; set; } public string? Type { get; set; }
/// <summary> /// <summary>
/// 策略描述 /// 策略描述
/// </summary> /// </summary>
[SugarColumn(ColumnName = "description", Length = 500)] [SugarColumn(ColumnName = "description", Length = 500)]
public string Description { get; set; } public string? Description { get; set; }
/// <summary> /// <summary>
/// 策略标签 (逗号分隔的字符串) /// 策略标签 (逗号分隔的字符串)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "tags")] [SugarColumn(ColumnName = "tags")]
public string Tags { get; set; } public string? Tags { get; set; }
/// <summary> /// <summary>
/// 风险等级 /// 风险等级
/// </summary> /// </summary>
[SugarColumn(ColumnName = "risk_level", Length = 20)] [SugarColumn(ColumnName = "risk_level", Length = 20)]
public string RiskLevel { get; set; } public string? RiskLevel { get; set; }
/// <summary> /// <summary>
/// 策略配置项(周期,阈值,资产配比) /// 策略配置项(周期,阈值,资产配比)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "config", IsJson = true)] [SugarColumn(ColumnName = "config", IsJson = true)]
public string Config { get; set; } public string? Config { get; set; }
/// <summary> /// <summary>
/// 创建时间 /// 创建时间

View File

@ -12,37 +12,37 @@ public class User
/// 主键 /// 主键
/// </summary> /// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = false)] [SugarColumn(IsPrimaryKey = true, IsIdentity = false)]
public string Id { get; set; } public string? Id { get; set; }
/// <summary> /// <summary>
/// 微信OpenID (UK) /// 微信OpenID (UK)
/// </summary> /// </summary>
[SugarColumn(ColumnName = "open_id", Length = 100, IsNullable = true)] [SugarColumn(ColumnName = "open_id", Length = 100, IsNullable = true)]
public string OpenId { get; set; } public string? OpenId { get; set; }
/// <summary> /// <summary>
/// 用户名 /// 用户名
/// </summary> /// </summary>
[SugarColumn(ColumnName = "user_name", Length = 100)] [SugarColumn(ColumnName = "user_name", Length = 100)]
public string UserName { get; set; } public string? UserName { get; set; }
/// <summary> /// <summary>
/// 邮箱 /// 邮箱
/// </summary> /// </summary>
[SugarColumn(ColumnName = "email", Length = 255, IsNullable = true)] [SugarColumn(ColumnName = "email", Length = 255, IsNullable = true)]
public string Email { get; set; } public string? Email { get; set; }
/// <summary> /// <summary>
/// 头像URL /// 头像URL
/// </summary> /// </summary>
[SugarColumn(ColumnName = "avatar", Length = 500, IsNullable = true)] [SugarColumn(ColumnName = "avatar", Length = 500, IsNullable = true)]
public string Avatar { get; set; } public string? Avatar { get; set; }
/// <summary> /// <summary>
/// 会员等级 /// 会员等级
/// </summary> /// </summary>
[SugarColumn(ColumnName = "member_level", Length = 50)] [SugarColumn(ColumnName = "member_level", Length = 50)]
public string MemberLevel { get; set; } public string? MemberLevel { get; set; }
/// <summary> /// <summary>
/// 用户默认本位币 (CNY/USD/HKD) /// 用户默认本位币 (CNY/USD/HKD)

View File

@ -55,6 +55,11 @@ public class ChandelierExitCalculator : IStrategyCalculator
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (position.StockCode == null)
{
continue;
}
try try
{ {
// 获取历史 K 线数据(需要 period + 1 根来计算 ATR // 获取历史 K 线数据(需要 period + 1 根来计算 ATR

View File

@ -55,6 +55,11 @@ public class MaTrendCalculator : IStrategyCalculator
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (position.StockCode == null)
{
continue;
}
try try
{ {
// 获取历史 K 线数据(需要 longPeriod + 1 根) // 获取历史 K 线数据(需要 longPeriod + 1 根)

View File

@ -36,14 +36,14 @@ public class RiskParityCalculator : IStrategyCalculator
var targetAssets = new List<string>(); var targetAssets = new List<string>();
var userDefinedWeights = new Dictionary<string, decimal>(); var userDefinedWeights = new Dictionary<string, decimal>();
if (config.Assets?.Count > 0) if (config.Assets?.Count >0)
{ {
targetAssets = config.Assets.Select(a => a.Symbol).ToList(); targetAssets = config.Assets.Where(a => a.Symbol != null).Select(a => a.Symbol!).ToList();
userDefinedWeights = config.Assets.ToDictionary(a => a.Symbol, a => a.TargetWeight); userDefinedWeights = config.Assets.Where(a => a.Symbol != null).ToDictionary(a => a.Symbol!, a => a.TargetWeight);
} }
else else
{ {
targetAssets = positions.Select(p => p.StockCode).ToList(); targetAssets = positions.Where(p => p.StockCode != null).Select(p => p.StockCode!).ToList();
} }
if (targetAssets.Count == 0) if (targetAssets.Count == 0)
@ -219,8 +219,13 @@ public class RiskParityCalculator : IStrategyCalculator
{ {
cancellationToken.ThrowIfCancellationRequested(); cancellationToken.ThrowIfCancellationRequested();
if (position.StockCode == null)
{
continue;
}
MarketPriceResponse priceResponse; MarketPriceResponse priceResponse;
if (position.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) if (position.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{ {
priceResponse = await _marketDataService.GetCryptoPriceAsync(position.StockCode); priceResponse = await _marketDataService.GetCryptoPriceAsync(position.StockCode);
} }

View File

@ -40,7 +40,7 @@ public class StrategyEngine : IStrategyEngine
}; };
} }
if (!_calculators.TryGetValue(strategy.Type, out var calculator)) if (strategy.Type == null || !_calculators.TryGetValue(strategy.Type, out var calculator))
{ {
_logger.LogWarning("未找到策略类型 {StrategyType} 的计算器", strategy.Type); _logger.LogWarning("未找到策略类型 {StrategyType} 的计算器", strategy.Type);
return new StrategySignal return new StrategySignal

View File

@ -3,8 +3,8 @@ namespace AssetManager.Models;
public class ApiResponse<T> public class ApiResponse<T>
{ {
public int code { get; set; } public int code { get; set; }
public T data { get; set; } public T? data { get; set; }
public string message { get; set; } public string? message { get; set; }
} }
public static class StatusCodes public static class StatusCodes

View File

@ -2,35 +2,35 @@ namespace AssetManager.Models.DTOs;
public class LoginRequest public class LoginRequest
{ {
public string Email { get; set; } public string? Email { get; set; }
public string Password { get; set; } public string? Password { get; set; }
} }
public class LoginResponse public class LoginResponse
{ {
public string Token { get; set; } public string? Token { get; set; }
public string ExpireAt { get; set; } public string? ExpireAt { get; set; }
public UserBasicInfo User { get; set; } public UserBasicInfo? User { get; set; }
public string UserId { get; set; } public string? UserId { get; set; }
} }
public class UserBasicInfo public class UserBasicInfo
{ {
public string UserName { get; set; } public string? UserName { get; set; }
public string Email { get; set; } public string? Email { get; set; }
} }
public class WechatLoginResponse public class WechatLoginResponse
{ {
public string Token { get; set; } public string? Token { get; set; }
public string ExpireAt { get; set; } public string? ExpireAt { get; set; }
public UserBasicInfo User { get; set; } public UserBasicInfo? User { get; set; }
public string OpenId { get; set; } public string? OpenId { get; set; }
public string UserId { get; set; } public string? UserId { get; set; }
} }
public class WechatLoginRequest public class WechatLoginRequest
{ {
public string Code { get; set; } public string? Code { get; set; }
public string? NickName { get; set; } public string? NickName { get; set; }
} }

View File

@ -8,7 +8,7 @@ public class MarketPriceResponse
/// <summary> /// <summary>
/// 标的代码 /// 标的代码
/// </summary> /// </summary>
public string Symbol { get; set; } public string? Symbol { get; set; }
/// <summary> /// <summary>
/// 价格 /// 价格
@ -28,7 +28,7 @@ public class MarketPriceResponse
/// <summary> /// <summary>
/// 资产类型 /// 资产类型
/// </summary> /// </summary>
public string AssetType { get; set; } public string? AssetType { get; set; }
} }
/// <summary> /// <summary>
@ -39,7 +39,7 @@ public class MarketDataResponse
/// <summary> /// <summary>
/// 标的代码 /// 标的代码
/// </summary> /// </summary>
public string Symbol { get; set; } public string? Symbol { get; set; }
/// <summary> /// <summary>
/// 时间戳 /// 时间戳
@ -74,5 +74,5 @@ public class MarketDataResponse
/// <summary> /// <summary>
/// 资产类型 /// 资产类型
/// </summary> /// </summary>
public string AssetType { get; set; } public string? AssetType { get; set; }
} }

View File

@ -2,66 +2,66 @@ namespace AssetManager.Models.DTOs;
public class CreatePortfolioRequest public class CreatePortfolioRequest
{ {
public string name { get; set; } public string? name { get; set; }
public string strategyId { get; set; } public string? strategyId { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public List<StockItem> stocks { get; set; } public List<StockItem>? stocks { get; set; }
} }
public class StockItem public class StockItem
{ {
public string name { get; set; } public string? name { get; set; }
public string code { get; set; } public string? code { get; set; }
public double price { get; set; } public double price { get; set; }
public int amount { get; set; } public int amount { get; set; }
public string date { get; set; } public string? date { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public string assetType { get; set; } = "Stock"; // Stock / Crypto public string assetType { get; set; } = "Stock"; // Stock / Crypto
} }
public class CreatePortfolioResponse public class CreatePortfolioResponse
{ {
public string id { get; set; } public string? id { get; set; }
public double totalValue { get; set; } public double totalValue { get; set; }
public double returnRate { get; set; } public double returnRate { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public string createdAt { get; set; } public string? createdAt { get; set; }
} }
public class PortfolioDetailResponse public class PortfolioDetailResponse
{ {
public string id { get; set; } public string? id { get; set; }
public string name { get; set; } public string? name { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public string status { get; set; } public string? status { get; set; }
public StrategyInfo strategy { get; set; } public StrategyInfo? strategy { get; set; }
public double portfolioValue { get; set; } public double portfolioValue { get; set; }
public double totalReturn { get; set; } public double totalReturn { get; set; }
public double todayProfit { get; set; } public double todayProfit { get; set; }
public double historicalChange { get; set; } public double historicalChange { get; set; }
public double dailyVolatility { get; set; } public double dailyVolatility { get; set; }
public string todayProfitCurrency { get; set; } public string? todayProfitCurrency { get; set; }
public string logicModel { get; set; } public string? logicModel { get; set; }
public string logicModelStatus { get; set; } public string? logicModelStatus { get; set; }
public string logicModelDescription { get; set; } public string? logicModelDescription { get; set; }
public int totalItems { get; set; } public int totalItems { get; set; }
public double totalRatio { get; set; } public double totalRatio { get; set; }
public List<PositionItem> positions { get; set; } public List<PositionItem>? positions { get; set; }
} }
public class StrategyInfo public class StrategyInfo
{ {
public string id { get; set; } public string? id { get; set; }
public string name { get; set; } public string? name { get; set; }
public string description { get; set; } public string? description { get; set; }
} }
public class PositionItem public class PositionItem
{ {
public string id { get; set; } public string? id { get; set; }
public string stockCode { get; set; } public string? stockCode { get; set; }
public string stockName { get; set; } public string? stockName { get; set; }
public string symbol { get; set; } public string? symbol { get; set; }
public int amount { get; set; } public int amount { get; set; }
public double averagePrice { get; set; } public double averagePrice { get; set; }
public double currentPrice { get; set; } public double currentPrice { get; set; }
@ -71,33 +71,33 @@ public class PositionItem
public double changeAmount { get; set; } public double changeAmount { get; set; }
public double ratio { get; set; } public double ratio { get; set; }
public double deviationRatio { get; set; } public double deviationRatio { get; set; }
public string currency { get; set; } public string? currency { get; set; }
} }
public class TransactionItem public class TransactionItem
{ {
public string id { get; set; } public string? id { get; set; }
public string portfolioId { get; set; } public string? portfolioId { get; set; }
public string date { get; set; } public string? date { get; set; }
public string time { get; set; } public string? time { get; set; }
public string type { get; set; } public string? type { get; set; }
public string title { get; set; } public string? title { get; set; }
public double amount { get; set; } public double amount { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public string status { get; set; } public string? status { get; set; }
public string remark { get; set; } public string? remark { get; set; }
} }
public class GetTransactionsRequest public class GetTransactionsRequest
{ {
public string portfolioId { get; set; } public string? portfolioId { get; set; }
public int limit { get; set; } public int limit { get; set; }
public int offset { get; set; } public int offset { get; set; }
} }
public class GetTransactionsResponse public class GetTransactionsResponse
{ {
public List<TransactionItem> items { get; set; } public List<TransactionItem>? items { get; set; }
public int total { get; set; } public int total { get; set; }
public int page { get; set; } public int page { get; set; }
public int pageSize { get; set; } public int pageSize { get; set; }
@ -105,51 +105,51 @@ public class GetTransactionsResponse
public class CreateTransactionRequest public class CreateTransactionRequest
{ {
public string portfolioId { get; set; } public string? portfolioId { get; set; }
public string type { get; set; } public string? type { get; set; }
public string stockCode { get; set; } public string? stockCode { get; set; }
public int amount { get; set; } public int amount { get; set; }
public double price { get; set; } public double price { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public string remark { get; set; } public string? remark { get; set; }
public string assetType { get; set; } = "Stock"; // Stock / Crypto public string assetType { get; set; } = "Stock"; // Stock / Crypto
public string transactionTime { get; set; } // 实际交易时间,可选 public string? transactionTime { get; set; } // 实际交易时间,可选
} }
public class CreateTransactionResponse public class CreateTransactionResponse
{ {
public string id { get; set; } public string? id { get; set; }
public double totalAmount { get; set; } public double totalAmount { get; set; }
public string status { get; set; } public string? status { get; set; }
public string createdAt { get; set; } public string? createdAt { get; set; }
} }
public class PortfolioListItem public class PortfolioListItem
{ {
public string id { get; set; } public string? id { get; set; }
public string name { get; set; } public string? name { get; set; }
public string tags { get; set; } public string? tags { get; set; }
public string status { get; set; } public string? status { get; set; }
public string statusType { get; set; } public string? statusType { get; set; }
public string iconChar { get; set; } public string? iconChar { get; set; }
public string iconBgClass { get; set; } public string? iconBgClass { get; set; }
public string iconTextClass { get; set; } public string? iconTextClass { get; set; }
public double value { get; set; } public double value { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public double returnRate { get; set; } public double returnRate { get; set; }
public string returnType { get; set; } public string? returnType { get; set; }
} }
public class GetPortfoliosResponse public class GetPortfoliosResponse
{ {
public List<PortfolioListItem> items { get; set; } public List<PortfolioListItem>? items { get; set; }
} }
public class TotalAssetsResponse public class TotalAssetsResponse
{ {
public double totalValue { get; set; } public double totalValue { get; set; }
public string currency { get; set; } public string? currency { get; set; }
public double todayProfit { get; set; } public double todayProfit { get; set; }
public string todayProfitCurrency { get; set; } public string? todayProfitCurrency { get; set; }
public double totalReturnRate { get; set; } public double totalReturnRate { get; set; }
} }

View File

@ -18,7 +18,7 @@ public class RiskParityConfig
/// <summary> /// <summary>
/// 目标资产列表(可选,不指定则使用当前持仓) /// 目标资产列表(可选,不指定则使用当前持仓)
/// </summary> /// </summary>
public List<AssetAllocation> Assets { get; set; } public List<AssetAllocation>? Assets { get; set; }
} }
/// <summary> /// <summary>
@ -29,7 +29,7 @@ public class AssetAllocation
/// <summary> /// <summary>
/// 标的代码 /// 标的代码
/// </summary> /// </summary>
public string Symbol { get; set; } public string? Symbol { get; set; }
/// <summary> /// <summary>
/// 目标权重 /// 目标权重

View File

@ -2,110 +2,110 @@ namespace AssetManager.Models.DTOs;
public class StrategyListResponse public class StrategyListResponse
{ {
public List<StrategyItem> items { get; set; } public List<StrategyItem>? items { get; set; }
} }
public class StrategyItem public class StrategyItem
{ {
public string id { get; set; } public string? id { get; set; }
public string iconChar { get; set; } public string? iconChar { get; set; }
public string title { get; set; } public string? title { get; set; }
public string tag { get; set; } public string? tag { get; set; }
public string desc { get; set; } public string? desc { get; set; }
public string bgClass { get; set; } public string? bgClass { get; set; }
public string tagClass { get; set; } public string? tagClass { get; set; }
public string btnText { get; set; } public string? btnText { get; set; }
public string btnClass { get; set; } public string? btnClass { get; set; }
public string[] Tags { get; set; } public string[]? Tags { get; set; }
public string Id { get; set; } public string? Id { get; set; }
public string IconChar { get; set; } public string? IconChar { get; set; }
public string Title { get; set; } public string? Title { get; set; }
public string Tag { get; set; } public string? Tag { get; set; }
public string Desc { get; set; } public string? Desc { get; set; }
public string BgClass { get; set; } public string? BgClass { get; set; }
public string TagClass { get; set; } public string? TagClass { get; set; }
public string BtnText { get; set; } public string? BtnText { get; set; }
public string BtnClass { get; set; } public string? BtnClass { get; set; }
} }
public class StrategyDetail public class StrategyDetail
{ {
public string Id { get; set; } public string? Id { get; set; }
public string IconChar { get; set; } public string? IconChar { get; set; }
public string Title { get; set; } public string? Title { get; set; }
public string Tag { get; set; } public string? Tag { get; set; }
public string Desc { get; set; } public string? Desc { get; set; }
public string BgClass { get; set; } public string? BgClass { get; set; }
public string TagClass { get; set; } public string? TagClass { get; set; }
public string[] Tags { get; set; } public string[]? Tags { get; set; }
public string BtnText { get; set; } public string? BtnText { get; set; }
public string BtnClass { get; set; } public string? BtnClass { get; set; }
public object Parameters { get; set; } public object? Parameters { get; set; }
public object Backtest { get; set; } public object? Backtest { get; set; }
} }
public class StrategyDetailResponse public class StrategyDetailResponse
{ {
public string id { get; set; } public string? id { get; set; }
public string iconChar { get; set; } public string? iconChar { get; set; }
public string title { get; set; } public string? title { get; set; }
public string riskLevel { get; set; } public string? riskLevel { get; set; }
public string description { get; set; } public string? description { get; set; }
public List<string> tags { get; set; } public List<string>? tags { get; set; }
public List<ParameterItem> parameters { get; set; } public List<ParameterItem>? parameters { get; set; }
} }
public class ParameterItem public class ParameterItem
{ {
public string name { get; set; } public string? name { get; set; }
public string displayName { get; set; } public string? displayName { get; set; }
public string type { get; set; } public string? type { get; set; }
public string value { get; set; } public string? value { get; set; }
} }
public class CreateStrategyRequest public class CreateStrategyRequest
{ {
public string name { get; set; } public string? name { get; set; }
public string type { get; set; } public string? type { get; set; }
public string description { get; set; } public string? description { get; set; }
public string riskLevel { get; set; } public string? riskLevel { get; set; }
public List<string> tags { get; set; } public List<string>? tags { get; set; }
public object parameters { get; set; } public object? parameters { get; set; }
} }
public class StrategyResponse public class StrategyResponse
{ {
public string Id { get; set; } public string? Id { get; set; }
public string Title { get; set; } public string? Title { get; set; }
public string Status { get; set; } public string? Status { get; set; }
} }
public class UpdateStrategyRequest public class UpdateStrategyRequest
{ {
public string name { get; set; } public string? name { get; set; }
public string type { get; set; } public string? type { get; set; }
public string description { get; set; } public string? description { get; set; }
public string riskLevel { get; set; } public string? riskLevel { get; set; }
public List<string> tags { get; set; } public List<string>? tags { get; set; }
public object parameters { get; set; } public object? parameters { get; set; }
} }
public class DeleteStrategyResponse public class DeleteStrategyResponse
{ {
public string Id { get; set; } public string? Id { get; set; }
public string Status { get; set; } public string? Status { get; set; }
} }
public class StrategyListItemDTO public class StrategyListItemDTO
{ {
public string id { get; set; } public string? id { get; set; }
public string userId { get; set; } public string? userId { get; set; }
public string name { get; set; } public string? name { get; set; }
public string type { get; set; } public string? type { get; set; }
public string description { get; set; } public string? description { get; set; }
public List<string> tags { get; set; } public List<string>? tags { get; set; }
public string riskLevel { get; set; } public string? riskLevel { get; set; }
public string config { get; set; } public string? config { get; set; }
public DateTime createdAt { get; set; } public DateTime createdAt { get; set; }
public DateTime updatedAt { get; set; } public DateTime updatedAt { get; set; }
} }

View File

@ -8,17 +8,17 @@ public class StrategySignal
/// <summary> /// <summary>
/// 策略类型: ma_trend / chandelier_exit / risk_parity /// 策略类型: ma_trend / chandelier_exit / risk_parity
/// </summary> /// </summary>
public string StrategyType { get; set; } public string? StrategyType { get; set; }
/// <summary> /// <summary>
/// 信号: BUY / SELL / HOLD / REBALANCE /// 信号: BUY / SELL / HOLD / REBALANCE
/// </summary> /// </summary>
public string Signal { get; set; } public string? Signal { get; set; }
/// <summary> /// <summary>
/// 标的代码 /// 标的代码
/// </summary> /// </summary>
public string Symbol { get; set; } public string? Symbol { get; set; }
/// <summary> /// <summary>
/// 信号强度 (0-1) /// 信号强度 (0-1)
@ -28,7 +28,7 @@ public class StrategySignal
/// <summary> /// <summary>
/// 信号原因 /// 信号原因
/// </summary> /// </summary>
public string Reason { get; set; } public string? Reason { get; set; }
/// <summary> /// <summary>
/// 建议价格 /// 建议价格
@ -59,12 +59,12 @@ public class PositionSignal
/// <summary> /// <summary>
/// 标的代码 /// 标的代码
/// </summary> /// </summary>
public string Symbol { get; set; } public string? Symbol { get; set; }
/// <summary> /// <summary>
/// 该标的的信号 /// 该标的的信号
/// </summary> /// </summary>
public string Signal { get; set; } public string? Signal { get; set; }
/// <summary> /// <summary>
/// 建议数量 /// 建议数量
@ -74,7 +74,7 @@ public class PositionSignal
/// <summary> /// <summary>
/// 信号原因 /// 信号原因
/// </summary> /// </summary>
public string Reason { get; set; } public string? Reason { get; set; }
/// <summary> /// <summary>
/// 目标权重(风险平价策略用) /// 目标权重(风险平价策略用)

View File

@ -2,20 +2,20 @@ namespace AssetManager.Models.DTOs;
public class UserInfoResponse public class UserInfoResponse
{ {
public string UserName { get; set; } public string? UserName { get; set; }
public string MemberLevel { get; set; } public string? MemberLevel { get; set; }
public int RunningDays { get; set; } public int RunningDays { get; set; }
public string Avatar { get; set; } public string? Avatar { get; set; }
public string Email { get; set; } public string? Email { get; set; }
public string DefaultCurrency { get; set; } public string? DefaultCurrency { get; set; }
} }
public class UpdateUserRequest public class UpdateUserRequest
{ {
public string UserName { get; set; } public string? UserName { get; set; }
public string Avatar { get; set; } public string? Avatar { get; set; }
public string Email { get; set; } public string? Email { get; set; }
public string DefaultCurrency { get; set; } public string? DefaultCurrency { get; set; }
} }
public class UserStatsResponse public class UserStatsResponse
@ -28,6 +28,6 @@ public class UserStatsResponse
public class UpdateUserResponse public class UpdateUserResponse
{ {
public string Status { get; set; } public string? Status { get; set; }
public string UserName { get; set; } public string? UserName { get; set; }
} }

View File

@ -2,6 +2,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\AssetManager.Data\AssetManager.Data.csproj" /> <ProjectReference Include="..\AssetManager.Data\AssetManager.Data.csproj" />
<ProjectReference Include="..\AssetManager.Infrastructure\AssetManager.Infrastructure.csproj" />
<ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" /> <ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -27,7 +27,7 @@ public class PortfolioService : IPortfolioService
StrategyId = request.strategyId, StrategyId = request.strategyId,
Name = request.name, Name = request.name,
Currency = request.currency, Currency = request.currency,
TotalValue = (decimal)request.stocks.Sum(s => s.price * s.amount), TotalValue = (decimal)(request.stocks?.Sum(s => s.price * s.amount) ?? 0),
ReturnRate = 0, ReturnRate = 0,
Status = "运行中", Status = "运行中",
CreatedAt = DateTime.Now, CreatedAt = DateTime.Now,
@ -37,8 +37,13 @@ public class PortfolioService : IPortfolioService
_db.Insertable(portfolio).ExecuteCommand(); _db.Insertable(portfolio).ExecuteCommand();
// 创建初始持仓 // 创建初始持仓
foreach (var stock in request.stocks) foreach (var stock in request.stocks ?? new List<StockItem>())
{ {
if (stock.code == null || stock.name == null)
{
continue;
}
// 解析实际买入时间,如果解析失败则用当前时间 // 解析实际买入时间,如果解析失败则用当前时间
DateTime buyTime = DateTime.Now; DateTime buyTime = DateTime.Now;
if (!string.IsNullOrEmpty(stock.date)) if (!string.IsNullOrEmpty(stock.date))
@ -110,7 +115,7 @@ public class PortfolioService : IPortfolioService
tags = $"{p.Status} · {p.Currency}", tags = $"{p.Status} · {p.Currency}",
status = p.Status, status = p.Status,
statusType = p.Status == "运行中" ? "green" : "gray", statusType = p.Status == "运行中" ? "green" : "gray",
iconChar = p.Name.Substring(0, 1).ToUpper(), iconChar = p.Name?.Substring(0, 1).ToUpper() ?? "P",
iconBgClass = "bg-blue-100", iconBgClass = "bg-blue-100",
iconTextClass = "text-blue-700", iconTextClass = "text-blue-700",
value = (double)p.TotalValue, value = (double)p.TotalValue,
@ -132,7 +137,7 @@ public class PortfolioService : IPortfolioService
throw new Exception("User not found"); throw new Exception("User not found");
} }
string targetCurrency = user.DefaultCurrency; string targetCurrency = user.DefaultCurrency ?? "CNY";
decimal totalValueInTargetCurrency = 0; decimal totalValueInTargetCurrency = 0;
decimal totalCostInTargetCurrency = 0; decimal totalCostInTargetCurrency = 0;
decimal totalTodayProfitInTargetCurrency = 0; decimal totalTodayProfitInTargetCurrency = 0;
@ -151,9 +156,14 @@ public class PortfolioService : IPortfolioService
foreach (var pos in positions) foreach (var pos in positions)
{ {
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格 // 获取实时价格
MarketPriceResponse priceResponse; MarketPriceResponse priceResponse;
if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{ {
priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode); priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode);
} }
@ -220,9 +230,14 @@ public class PortfolioService : IPortfolioService
foreach (var pos in positions) foreach (var pos in positions)
{ {
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格 // 获取实时价格
MarketPriceResponse priceResponse; MarketPriceResponse priceResponse;
if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{ {
priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode); priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode);
} }
@ -383,7 +398,7 @@ public class PortfolioService : IPortfolioService
TotalAmount = (decimal)(request.price * request.amount), TotalAmount = (decimal)(request.price * request.amount),
Currency = request.currency, Currency = request.currency,
Status = "completed", Status = "completed",
Remark = request.remark, Remark = request.remark ?? string.Empty,
TransactionTime = transactionTime, TransactionTime = transactionTime,
CreatedAt = DateTime.Now CreatedAt = DateTime.Now
}; };
@ -445,9 +460,14 @@ public class PortfolioService : IPortfolioService
decimal totalPortfolioValue = 0; decimal totalPortfolioValue = 0;
foreach (var pos in positions) foreach (var pos in positions)
{ {
if (pos.StockCode == null)
{
continue;
}
// 获取实时价格 // 获取实时价格
MarketPriceResponse priceResponse; MarketPriceResponse priceResponse;
if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{ {
priceResponse = _marketDataService.GetCryptoPriceAsync(pos.StockCode).GetAwaiter().GetResult(); priceResponse = _marketDataService.GetCryptoPriceAsync(pos.StockCode).GetAwaiter().GetResult();
} }

View File

@ -23,15 +23,15 @@ public class WechatService
var response = await _httpClient.GetAsync(url); var response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<WechatAuthResult>(content); return JsonSerializer.Deserialize<WechatAuthResult>(content) ?? new WechatAuthResult();
} }
} }
public class WechatAuthResult public class WechatAuthResult
{ {
public string OpenId { get; set; } public string? OpenId { get; set; }
public string SessionKey { get; set; } public string? SessionKey { get; set; }
public string UnionId { get; set; } public string? UnionId { get; set; }
public int Errcode { get; set; } public int Errcode { get; set; }
public string Errmsg { get; set; } public string? Errmsg { get; set; }
} }