refactor: 将模型属性改为可为空类型以增强健壮性
- 修改ApiResponse、RiskParityConfig等DTO类的属性为可空类型 - 在策略计算器中添加空值检查逻辑 - 更新服务层代码处理可能的空值情况 - 添加发布配置文件FolderProfile.pubxml
This commit is contained in:
parent
c994a5bb76
commit
b5499ef7fe
@ -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
|
||||
{
|
||||
|
||||
@ -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)
|
||||
{
|
||||
|
||||
@ -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>
|
||||
@ -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>
|
||||
/// 交易发生时间
|
||||
|
||||
@ -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>
|
||||
/// 创建时间
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -55,6 +55,11 @@ public class ChandelierExitCalculator : IStrategyCalculator
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (position.StockCode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 获取历史 K 线数据(需要 period + 1 根来计算 ATR)
|
||||
|
||||
@ -55,6 +55,11 @@ public class MaTrendCalculator : IStrategyCalculator
|
||||
{
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
if (position.StockCode == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// 获取历史 K 线数据(需要 longPeriod + 1 根)
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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>
|
||||
/// 目标权重
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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>
|
||||
/// 目标权重(风险平价策略用)
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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; }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user