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");
// 用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);
if (authResult.Errcode != 0)
@ -93,7 +102,7 @@ public class AuthController : ControllerBase
}
// 生成真实的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
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ public class MarketPriceResponse
/// <summary>
/// 标的代码
/// </summary>
public string Symbol { get; set; }
public string? Symbol { get; set; }
/// <summary>
/// 价格
@ -28,7 +28,7 @@ public class MarketPriceResponse
/// <summary>
/// 资产类型
/// </summary>
public string AssetType { get; set; }
public string? AssetType { get; set; }
}
/// <summary>
@ -39,7 +39,7 @@ public class MarketDataResponse
/// <summary>
/// 标的代码
/// </summary>
public string Symbol { get; set; }
public string? Symbol { get; set; }
/// <summary>
/// 时间戳
@ -74,5 +74,5 @@ public class MarketDataResponse
/// <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 string name { get; set; }
public string strategyId { get; set; }
public string currency { get; set; }
public List<StockItem> stocks { get; set; }
public string? name { get; set; }
public string? strategyId { get; set; }
public string? currency { get; set; }
public List<StockItem>? stocks { get; set; }
}
public class StockItem
{
public string name { get; set; }
public string code { get; set; }
public string? name { get; set; }
public string? code { get; set; }
public double price { get; set; }
public int amount { get; set; }
public string date { get; set; }
public string currency { get; set; }
public string? date { get; set; }
public string? currency { get; set; }
public string assetType { get; set; } = "Stock"; // Stock / Crypto
}
public class CreatePortfolioResponse
{
public string id { get; set; }
public string? id { get; set; }
public double totalValue { get; set; }
public double returnRate { get; set; }
public string currency { get; set; }
public string createdAt { get; set; }
public string? currency { get; set; }
public string? createdAt { get; set; }
}
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 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 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<PositionItem> positions { get; set; }
public List<PositionItem>? positions { get; set; }
}
public class StrategyInfo
{
public string id { get; set; }
public string name { get; set; }
public string description { get; set; }
public string? id { get; set; }
public string? name { get; set; }
public string? description { get; set; }
}
public class PositionItem
{
public string id { get; set; }
public string stockCode { get; set; }
public string stockName { get; set; }
public string symbol { get; set; }
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; }
@ -71,33 +71,33 @@ public class PositionItem
public double changeAmount { get; set; }
public double ratio { get; set; }
public double deviationRatio { get; set; }
public string currency { get; set; }
public string? currency { get; set; }
}
public class TransactionItem
{
public string id { get; set; }
public string portfolioId { get; set; }
public string date { get; set; }
public string time { get; set; }
public string type { get; set; }
public string title { get; set; }
public string? id { get; set; }
public string? portfolioId { get; set; }
public string? date { get; set; }
public string? time { get; set; }
public string? type { get; set; }
public string? title { get; set; }
public double amount { get; set; }
public string currency { get; set; }
public string status { get; set; }
public string remark { get; set; }
public string? currency { get; set; }
public string? status { get; set; }
public string? remark { get; set; }
}
public class GetTransactionsRequest
{
public string portfolioId { get; set; }
public string? portfolioId { get; set; }
public int limit { get; set; }
public int offset { get; set; }
}
public class GetTransactionsResponse
{
public List<TransactionItem> items { get; set; }
public List<TransactionItem>? items { get; set; }
public int total { get; set; }
public int page { get; set; }
public int pageSize { get; set; }
@ -105,51 +105,51 @@ public class GetTransactionsResponse
public class CreateTransactionRequest
{
public string portfolioId { get; set; }
public string type { get; set; }
public string stockCode { get; set; }
public string? portfolioId { get; set; }
public string? type { get; set; }
public string? stockCode { get; set; }
public int amount { get; set; }
public double price { get; set; }
public string currency { get; set; }
public string remark { get; set; }
public string? currency { get; set; }
public string? remark { get; set; }
public string assetType { get; set; } = "Stock"; // Stock / Crypto
public string transactionTime { get; set; } // 实际交易时间,可选
public string? transactionTime { get; set; } // 实际交易时间,可选
}
public class CreateTransactionResponse
{
public string id { get; set; }
public string? id { get; set; }
public double totalAmount { get; set; }
public string status { get; set; }
public string createdAt { get; set; }
public string? status { get; set; }
public string? createdAt { get; set; }
}
public class PortfolioListItem
{
public string id { get; set; }
public string name { get; set; }
public string tags { get; set; }
public string status { get; set; }
public string statusType { get; set; }
public string iconChar { get; set; }
public string iconBgClass { get; set; }
public string iconTextClass { get; set; }
public string? id { get; set; }
public string? name { get; set; }
public string? tags { get; set; }
public string? status { get; set; }
public string? statusType { get; set; }
public string? iconChar { get; set; }
public string? iconBgClass { get; set; }
public string? iconTextClass { get; set; }
public double value { get; set; }
public string currency { get; set; }
public string? currency { get; set; }
public double returnRate { get; set; }
public string returnType { get; set; }
public string? returnType { get; set; }
}
public class GetPortfoliosResponse
{
public List<PortfolioListItem> items { get; set; }
public List<PortfolioListItem>? items { get; set; }
}
public class TotalAssetsResponse
{
public double totalValue { get; set; }
public string currency { get; set; }
public string? currency { get; set; }
public double todayProfit { get; set; }
public string todayProfitCurrency { get; set; }
public string? todayProfitCurrency { get; set; }
public double totalReturnRate { get; set; }
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ public class PortfolioService : IPortfolioService
StrategyId = request.strategyId,
Name = request.name,
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,
Status = "运行中",
CreatedAt = DateTime.Now,
@ -37,8 +37,13 @@ public class PortfolioService : IPortfolioService
_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;
if (!string.IsNullOrEmpty(stock.date))
@ -110,7 +115,7 @@ public class PortfolioService : IPortfolioService
tags = $"{p.Status} · {p.Currency}",
status = p.Status,
statusType = p.Status == "运行中" ? "green" : "gray",
iconChar = p.Name.Substring(0, 1).ToUpper(),
iconChar = p.Name?.Substring(0, 1).ToUpper() ?? "P",
iconBgClass = "bg-blue-100",
iconTextClass = "text-blue-700",
value = (double)p.TotalValue,
@ -132,7 +137,7 @@ public class PortfolioService : IPortfolioService
throw new Exception("User not found");
}
string targetCurrency = user.DefaultCurrency;
string targetCurrency = user.DefaultCurrency ?? "CNY";
decimal totalValueInTargetCurrency = 0;
decimal totalCostInTargetCurrency = 0;
decimal totalTodayProfitInTargetCurrency = 0;
@ -151,9 +156,14 @@ public class PortfolioService : IPortfolioService
foreach (var pos in positions)
{
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格
MarketPriceResponse priceResponse;
if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase))
if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{
priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode);
}
@ -220,9 +230,14 @@ public class PortfolioService : IPortfolioService
foreach (var pos in positions)
{
if (pos.StockCode == null || pos.Currency == null)
{
continue;
}
// 获取实时价格
MarketPriceResponse priceResponse;
if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase))
if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{
priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode);
}
@ -383,7 +398,7 @@ public class PortfolioService : IPortfolioService
TotalAmount = (decimal)(request.price * request.amount),
Currency = request.currency,
Status = "completed",
Remark = request.remark,
Remark = request.remark ?? string.Empty,
TransactionTime = transactionTime,
CreatedAt = DateTime.Now
};
@ -445,9 +460,14 @@ public class PortfolioService : IPortfolioService
decimal totalPortfolioValue = 0;
foreach (var pos in positions)
{
if (pos.StockCode == null)
{
continue;
}
// 获取实时价格
MarketPriceResponse priceResponse;
if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase))
if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true)
{
priceResponse = _marketDataService.GetCryptoPriceAsync(pos.StockCode).GetAwaiter().GetResult();
}

View File

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