feat: 添加策略引擎实现及相关组件
实现策略引擎核心功能,包括三种策略计算器和相关DTO定义: 1. 添加双均线策略(ma_trend)计算器 2. 添加吊灯止损策略(chandelier_exit)计算器 3. 添加风险平价策略(risk_parity)计算器 4. 定义策略类型常量类和策略配置DTO 5. 实现策略引擎服务接口和扩展方法 6. 更新项目引用和README文档
This commit is contained in:
parent
8e75b894ad
commit
2d1fbd37d8
@ -1,7 +1,8 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" />
|
<ProjectReference Include="..\AssetManager.Models\AssetManager.Models.csproj" />
|
||||||
|
<ProjectReference Include="..\AssetManager.Data\AssetManager.Data.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -0,0 +1,55 @@
|
|||||||
|
using AssetManager.Data;
|
||||||
|
using AssetManager.Models.DTOs;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine.Calculators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 吊灯止损策略计算器
|
||||||
|
/// </summary>
|
||||||
|
public class ChandelierExitCalculator : IStrategyCalculator
|
||||||
|
{
|
||||||
|
private readonly ILogger<ChandelierExitCalculator> _logger;
|
||||||
|
|
||||||
|
public string StrategyType => AssetManager.Models.DTOs.StrategyType.ChandelierExit;
|
||||||
|
|
||||||
|
public ChandelierExitCalculator(ILogger<ChandelierExitCalculator> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StrategySignal> CalculateAsync(
|
||||||
|
string configJson,
|
||||||
|
List<Position> positions,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("计算吊灯止损策略信号");
|
||||||
|
|
||||||
|
var config = System.Text.Json.JsonSerializer.Deserialize<ChandelierExitConfig>(configJson, new System.Text.Json.JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
}) ?? new ChandelierExitConfig();
|
||||||
|
|
||||||
|
if (positions.Count == 0)
|
||||||
|
{
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = StrategyType,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = "无持仓",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单实现:返回持有信号
|
||||||
|
await Task.Delay(1, cancellationToken);
|
||||||
|
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = StrategyType,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = "吊灯止损策略暂未实现",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,55 @@
|
|||||||
|
using AssetManager.Data;
|
||||||
|
using AssetManager.Models.DTOs;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine.Calculators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 均线趋势策略计算器
|
||||||
|
/// </summary>
|
||||||
|
public class MaTrendCalculator : IStrategyCalculator
|
||||||
|
{
|
||||||
|
private readonly ILogger<MaTrendCalculator> _logger;
|
||||||
|
|
||||||
|
public string StrategyType => AssetManager.Models.DTOs.StrategyType.MaTrend;
|
||||||
|
|
||||||
|
public MaTrendCalculator(ILogger<MaTrendCalculator> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StrategySignal> CalculateAsync(
|
||||||
|
string configJson,
|
||||||
|
List<Position> positions,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("计算均线趋势策略信号");
|
||||||
|
|
||||||
|
var config = System.Text.Json.JsonSerializer.Deserialize<MaTrendConfig>(configJson, new System.Text.Json.JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
}) ?? new MaTrendConfig();
|
||||||
|
|
||||||
|
if (positions.Count == 0)
|
||||||
|
{
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = StrategyType,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = "无持仓",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单实现:返回持有信号
|
||||||
|
await Task.Delay(1, cancellationToken);
|
||||||
|
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = StrategyType,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = "均线趋势策略暂未实现",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,230 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
using AssetManager.Data;
|
||||||
|
using AssetManager.Infrastructure.Services;
|
||||||
|
using AssetManager.Models.DTOs;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine.Calculators;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 风险平价策略计算器
|
||||||
|
/// </summary>
|
||||||
|
public class RiskParityCalculator : IStrategyCalculator
|
||||||
|
{
|
||||||
|
private readonly IMarketDataService _marketDataService;
|
||||||
|
private readonly ILogger<RiskParityCalculator> _logger;
|
||||||
|
|
||||||
|
public string StrategyType => AssetManager.Models.DTOs.StrategyType.RiskParity;
|
||||||
|
|
||||||
|
public RiskParityCalculator(IMarketDataService marketDataService, ILogger<RiskParityCalculator> logger)
|
||||||
|
{
|
||||||
|
_marketDataService = marketDataService;
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StrategySignal> CalculateAsync(
|
||||||
|
string configJson,
|
||||||
|
List<Position> positions,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var config = JsonSerializer.Deserialize<RiskParityConfig>(configJson, new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
PropertyNameCaseInsensitive = true
|
||||||
|
}) ?? new RiskParityConfig();
|
||||||
|
|
||||||
|
// 确定目标资产列表
|
||||||
|
var targetAssets = new List<string>();
|
||||||
|
var userDefinedWeights = new Dictionary<string, decimal>();
|
||||||
|
|
||||||
|
if (config.Assets?.Count > 0)
|
||||||
|
{
|
||||||
|
targetAssets = config.Assets.Select(a => a.Symbol).ToList();
|
||||||
|
userDefinedWeights = config.Assets.ToDictionary(a => a.Symbol, a => a.TargetWeight);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
targetAssets = positions.Select(p => p.StockCode).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetAssets.Count == 0)
|
||||||
|
{
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = StrategyType,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = "无目标资产",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算资产的波动率和目标权重
|
||||||
|
var assetVolatilities = new Dictionary<string, decimal>();
|
||||||
|
var positionSignals = new List<PositionSignal>();
|
||||||
|
|
||||||
|
foreach (var symbol in targetAssets)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var volatility = await CalculateVolatilityAsync(symbol, config.LookbackPeriod, cancellationToken);
|
||||||
|
assetVolatilities[symbol] = volatility;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "计算 {Symbol} 的波动率失败", symbol);
|
||||||
|
assetVolatilities[symbol] = 0.2m; // 默认波动率20%
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算风险平价的目标权重,若无则等权初始化
|
||||||
|
var targetWeights = CalculateRiskParityWeights(assetVolatilities);
|
||||||
|
|
||||||
|
// 如果用户定义了权重,则使用用户定义的权重
|
||||||
|
foreach (var kvp in userDefinedWeights)
|
||||||
|
{
|
||||||
|
targetWeights[kvp.Key] = kvp.Value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算当前权重
|
||||||
|
var currentWeights = CalculateCurrentWeights(positions, targetAssets);
|
||||||
|
|
||||||
|
// 根据偏差决定是否再平衡
|
||||||
|
var maxDeviation = 0m;
|
||||||
|
var needRebalance = false;
|
||||||
|
var reasons = new List<string>();
|
||||||
|
|
||||||
|
foreach (var symbol in targetAssets)
|
||||||
|
{
|
||||||
|
var targetWeight = targetWeights.GetValueOrDefault(symbol, 0m);
|
||||||
|
var currentWeight = currentWeights.GetValueOrDefault(symbol, 0m);
|
||||||
|
var deviation = Math.Abs(targetWeight - currentWeight);
|
||||||
|
|
||||||
|
if (deviation > config.RebalanceThreshold)
|
||||||
|
{
|
||||||
|
needRebalance = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxDeviation = Math.Max(maxDeviation, deviation);
|
||||||
|
|
||||||
|
var positionSignal = new PositionSignal
|
||||||
|
{
|
||||||
|
Symbol = symbol,
|
||||||
|
TargetWeight = targetWeight,
|
||||||
|
Signal = deviation > config.RebalanceThreshold
|
||||||
|
? (targetWeight > currentWeight ? "BUY" : "SELL")
|
||||||
|
: "HOLD",
|
||||||
|
Reason = $"目标权重: {targetWeight:P2}, 当前权重: {currentWeight:P2}, 偏差: {deviation:P2}"
|
||||||
|
};
|
||||||
|
|
||||||
|
positionSignals.Add(positionSignal);
|
||||||
|
|
||||||
|
if (deviation > config.RebalanceThreshold)
|
||||||
|
{
|
||||||
|
reasons.Add($"{symbol}: 调整 {(targetWeight > currentWeight ? "+" : "")}{(targetWeight - currentWeight):P2}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = StrategyType,
|
||||||
|
Signal = needRebalance ? "REBALANCE" : "HOLD",
|
||||||
|
PositionSignals = positionSignals,
|
||||||
|
Reason = needRebalance
|
||||||
|
? $"最大偏差 {maxDeviation:P2} 超过阈值 {config.RebalanceThreshold:P2}。{string.Join("; ", reasons)}"
|
||||||
|
: $"资产偏差在阈值范围内。(最大偏差: {maxDeviation:P2})",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算资产波动率(年化标准差)
|
||||||
|
/// </summary>
|
||||||
|
private async Task<decimal> CalculateVolatilityAsync(
|
||||||
|
string symbol,
|
||||||
|
int lookbackPeriod,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var historicalData = await _marketDataService.GetStockHistoricalDataAsync(
|
||||||
|
symbol, "1d", lookbackPeriod + 1);
|
||||||
|
|
||||||
|
if (historicalData.Count < 2)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"历史数据不足以计算波动率");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按时间排序并计算日收益率
|
||||||
|
var sortedData = historicalData.OrderBy(d => d.Timestamp).ToList();
|
||||||
|
var returns = new List<decimal>();
|
||||||
|
|
||||||
|
for (int i = 1; i < sortedData.Count; i++)
|
||||||
|
{
|
||||||
|
var dailyReturn = (sortedData[i].Close - sortedData[i - 1].Close) / sortedData[i - 1].Close;
|
||||||
|
returns.Add(dailyReturn);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算标准差
|
||||||
|
var avgReturn = returns.Average();
|
||||||
|
var variance = returns.Sum(r => (r - avgReturn) * (r - avgReturn)) / returns.Count;
|
||||||
|
var dailyStdDev = (decimal)Math.Sqrt((double)variance);
|
||||||
|
|
||||||
|
// 年化波动率,假设252个交易日
|
||||||
|
return dailyStdDev * (decimal)Math.Sqrt(252);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算风险平价权重,若无则等权初始化
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<string, decimal> CalculateRiskParityWeights(Dictionary<string, decimal> volatilities)
|
||||||
|
{
|
||||||
|
var weights = new Dictionary<string, decimal>();
|
||||||
|
|
||||||
|
// 计算倒数波动率之和
|
||||||
|
var inverseVolSum = volatilities.Values.Where(v => v > 0).Sum(v => 1m / v);
|
||||||
|
|
||||||
|
if (inverseVolSum == 0)
|
||||||
|
{
|
||||||
|
// 如果无法计算,采用等权分配
|
||||||
|
var equalWeight = 1m / volatilities.Count;
|
||||||
|
foreach (var symbol in volatilities.Keys)
|
||||||
|
{
|
||||||
|
weights[symbol] = equalWeight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var kvp in volatilities)
|
||||||
|
{
|
||||||
|
weights[kvp.Key] = kvp.Value > 0 ? (1m / kvp.Value) / inverseVolSum : 0m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weights;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算当前持仓权重
|
||||||
|
/// </summary>
|
||||||
|
private Dictionary<string, decimal> CalculateCurrentWeights(List<Position> positions, List<string> targetAssets)
|
||||||
|
{
|
||||||
|
var weights = new Dictionary<string, decimal>();
|
||||||
|
|
||||||
|
// 计算总价值
|
||||||
|
var totalValue = positions.Sum(p => p.Shares * p.AvgPrice);
|
||||||
|
|
||||||
|
foreach (var symbol in targetAssets)
|
||||||
|
{
|
||||||
|
var position = positions.FirstOrDefault(p => p.StockCode == symbol);
|
||||||
|
if (position != null && totalValue > 0)
|
||||||
|
{
|
||||||
|
weights[symbol] = (position.Shares * position.AvgPrice) / totalValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
weights[symbol] = 0m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return weights;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
using AssetManager.Data;
|
||||||
|
using AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略计算器接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IStrategyCalculator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 策略类型标识
|
||||||
|
/// </summary>
|
||||||
|
string StrategyType { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 计算策略信号
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="configJson">策略配置JSON</param>
|
||||||
|
/// <param name="positions">持仓列表</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||||
|
/// <returns>策略信号</returns>
|
||||||
|
Task<StrategySignal> CalculateAsync(
|
||||||
|
string configJson,
|
||||||
|
List<Position> positions,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
using AssetManager.Data;
|
||||||
|
using AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略引擎接口
|
||||||
|
/// </summary>
|
||||||
|
public interface IStrategyEngine
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 计算策略信号
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="strategy">策略实例</param>
|
||||||
|
/// <param name="positions">持仓列表</param>
|
||||||
|
/// <param name="cancellationToken">取消令牌</param>
|
||||||
|
/// <returns>策略信号</returns>
|
||||||
|
Task<StrategySignal> CalculateSignalAsync(
|
||||||
|
Strategy strategy,
|
||||||
|
List<Position> positions,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
85
AssetManager.Infrastructure/StrategyEngine/StrategyEngine.cs
Normal file
85
AssetManager.Infrastructure/StrategyEngine/StrategyEngine.cs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
using AssetManager.Data;
|
||||||
|
using AssetManager.Models.DTOs;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略引擎实现
|
||||||
|
/// </summary>
|
||||||
|
public class StrategyEngine : IStrategyEngine
|
||||||
|
{
|
||||||
|
private readonly Dictionary<string, IStrategyCalculator> _calculators;
|
||||||
|
private readonly ILogger<StrategyEngine> _logger;
|
||||||
|
|
||||||
|
public StrategyEngine(
|
||||||
|
IEnumerable<IStrategyCalculator> calculators,
|
||||||
|
ILogger<StrategyEngine> logger)
|
||||||
|
{
|
||||||
|
_calculators = calculators.ToDictionary(c => c.StrategyType, c => c);
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<StrategySignal> CalculateSignalAsync(
|
||||||
|
Strategy strategy,
|
||||||
|
List<Position> positions,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
_logger.LogInformation(
|
||||||
|
"开始计算策略信号, 策略ID: {StrategyId}, 类型: {StrategyType}, 持仓数: {PositionCount}",
|
||||||
|
strategy.Id, strategy.Type, positions.Count);
|
||||||
|
|
||||||
|
if (positions.Count == 0)
|
||||||
|
{
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = strategy.Type,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = "无持仓",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_calculators.TryGetValue(strategy.Type, out var calculator))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("未找到策略类型 {StrategyType} 的计算器", strategy.Type);
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = strategy.Type,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = $"不支持的策略类型: {strategy.Type}",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var signal = await calculator.CalculateAsync(
|
||||||
|
strategy.Config ?? "{}",
|
||||||
|
positions,
|
||||||
|
cancellationToken);
|
||||||
|
|
||||||
|
_logger.LogInformation(
|
||||||
|
"策略信号计算完成, 策略ID: {StrategyId}, 信号: {Signal}",
|
||||||
|
strategy.Id, signal.Signal);
|
||||||
|
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("策略信号计算被取消, 策略ID: {StrategyId}", strategy.Id);
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "策略信号计算失败, 策略ID: {StrategyId}", strategy.Id);
|
||||||
|
return new StrategySignal
|
||||||
|
{
|
||||||
|
StrategyType = strategy.Type,
|
||||||
|
Signal = "HOLD",
|
||||||
|
Reason = $"计算失败: {ex.Message}",
|
||||||
|
GeneratedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
using AssetManager.Infrastructure.StrategyEngine.Calculators;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
|
||||||
|
namespace AssetManager.Infrastructure.StrategyEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略引擎服务注册扩展
|
||||||
|
/// </summary>
|
||||||
|
public static class StrategyEngineExtensions
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 注册策略引擎及所有计算器
|
||||||
|
/// </summary>
|
||||||
|
public static IServiceCollection AddStrategyEngine(this IServiceCollection services)
|
||||||
|
{
|
||||||
|
// 注册策略计算器
|
||||||
|
services.AddScoped<IStrategyCalculator, MaTrendCalculator>();
|
||||||
|
services.AddScoped<IStrategyCalculator, ChandelierExitCalculator>();
|
||||||
|
services.AddScoped<IStrategyCalculator, RiskParityCalculator>();
|
||||||
|
|
||||||
|
// 注册策略引擎
|
||||||
|
services.AddScoped<IStrategyEngine, StrategyEngine>();
|
||||||
|
|
||||||
|
return services;
|
||||||
|
}
|
||||||
|
}
|
||||||
22
AssetManager.Models/DTOs/ChandelierExitConfig.cs
Normal file
22
AssetManager.Models/DTOs/ChandelierExitConfig.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 吊灯止损策略配置
|
||||||
|
/// </summary>
|
||||||
|
public class ChandelierExitConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 周期(通常为 22)
|
||||||
|
/// </summary>
|
||||||
|
public int Period { get; set; } = 22;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ATR 倍数(通常为 3.0)
|
||||||
|
/// </summary>
|
||||||
|
public decimal Multiplier { get; set; } = 3.0m;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否使用收盘价计算(false 表示用最高价/最低价)
|
||||||
|
/// </summary>
|
||||||
|
public bool UseClose { get; set; } = false;
|
||||||
|
}
|
||||||
22
AssetManager.Models/DTOs/MaTrendConfig.cs
Normal file
22
AssetManager.Models/DTOs/MaTrendConfig.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 均线趋势策略配置
|
||||||
|
/// </summary>
|
||||||
|
public class MaTrendConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 均线类型:SMA(简单移动平均) / EMA(指数移动平均)
|
||||||
|
/// </summary>
|
||||||
|
public string MaType { get; set; } = "SMA";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 短期均线周期
|
||||||
|
/// </summary>
|
||||||
|
public int ShortPeriod { get; set; } = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 长期均线周期
|
||||||
|
/// </summary>
|
||||||
|
public int LongPeriod { get; set; } = 60;
|
||||||
|
}
|
||||||
38
AssetManager.Models/DTOs/RiskParityConfig.cs
Normal file
38
AssetManager.Models/DTOs/RiskParityConfig.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
namespace AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 风险平价策略配置
|
||||||
|
/// </summary>
|
||||||
|
public class RiskParityConfig
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 历史数据回看周期
|
||||||
|
/// </summary>
|
||||||
|
public int LookbackPeriod { get; set; } = 60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 再平衡阈值(偏离度超过 5% 触发再平衡)
|
||||||
|
/// </summary>
|
||||||
|
public decimal RebalanceThreshold { get; set; } = 0.05m;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标资产列表(可选,不指定则使用当前持仓)
|
||||||
|
/// </summary>
|
||||||
|
public List<AssetAllocation> Assets { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 资产配置项
|
||||||
|
/// </summary>
|
||||||
|
public class AssetAllocation
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标的代码
|
||||||
|
/// </summary>
|
||||||
|
public string Symbol { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标权重
|
||||||
|
/// </summary>
|
||||||
|
public decimal TargetWeight { get; set; }
|
||||||
|
}
|
||||||
@ -70,7 +70,7 @@ public class CreateStrategyRequest
|
|||||||
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 List<ParameterItem> parameters { get; set; }
|
public object parameters { get; set; }
|
||||||
public string Title { get; set; }
|
public string Title { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ public class UpdateStrategyRequest
|
|||||||
{
|
{
|
||||||
public string name { get; set; }
|
public string name { get; set; }
|
||||||
public string type { get; set; }
|
public string type { get; set; }
|
||||||
public List<ParameterItem> parameters { get; set; }
|
public object parameters { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class DeleteStrategyResponse
|
public class DeleteStrategyResponse
|
||||||
|
|||||||
83
AssetManager.Models/DTOs/StrategySignal.cs
Normal file
83
AssetManager.Models/DTOs/StrategySignal.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
namespace AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略信号
|
||||||
|
/// </summary>
|
||||||
|
public class StrategySignal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 策略类型: ma_trend / chandelier_exit / risk_parity
|
||||||
|
/// </summary>
|
||||||
|
public string StrategyType { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信号: BUY / SELL / HOLD / REBALANCE
|
||||||
|
/// </summary>
|
||||||
|
public string Signal { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 标的代码
|
||||||
|
/// </summary>
|
||||||
|
public string Symbol { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信号强度 (0-1)
|
||||||
|
/// </summary>
|
||||||
|
public decimal Strength { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信号原因
|
||||||
|
/// </summary>
|
||||||
|
public string Reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 建议价格
|
||||||
|
/// </summary>
|
||||||
|
public decimal? SuggestedPrice { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 建议数量
|
||||||
|
/// </summary>
|
||||||
|
public decimal? SuggestedQuantity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信号生成时间
|
||||||
|
/// </summary>
|
||||||
|
public DateTime GeneratedAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单个标的的信号
|
||||||
|
/// </summary>
|
||||||
|
public List<PositionSignal>? PositionSignals { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 持仓信号
|
||||||
|
/// </summary>
|
||||||
|
public class PositionSignal
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 标的代码
|
||||||
|
/// </summary>
|
||||||
|
public string Symbol { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 该标的的信号
|
||||||
|
/// </summary>
|
||||||
|
public string Signal { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 建议数量
|
||||||
|
/// </summary>
|
||||||
|
public decimal? SuggestedQuantity { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 信号原因
|
||||||
|
/// </summary>
|
||||||
|
public string Reason { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 目标权重(风险平价策略用)
|
||||||
|
/// </summary>
|
||||||
|
public decimal? TargetWeight { get; set; }
|
||||||
|
}
|
||||||
22
AssetManager.Models/DTOs/StrategyType.cs
Normal file
22
AssetManager.Models/DTOs/StrategyType.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
namespace AssetManager.Models.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 策略类型常量
|
||||||
|
/// </summary>
|
||||||
|
public static class StrategyType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 均线趋势策略
|
||||||
|
/// </summary>
|
||||||
|
public const string MaTrend = "ma_trend";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 吊灯止损策略
|
||||||
|
/// </summary>
|
||||||
|
public const string ChandelierExit = "chandelier_exit";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 风险平价策略
|
||||||
|
/// </summary>
|
||||||
|
public const string RiskParity = "risk_parity";
|
||||||
|
}
|
||||||
118
README.md
118
README.md
@ -23,3 +23,121 @@ AssetManager
|
|||||||
│ ├── AssetManager.Models # [实体层] POCO实体 (由数据库自动生成)
|
│ ├── AssetManager.Models # [实体层] POCO实体 (由数据库自动生成)
|
||||||
│ └── AssetManager.Infrastructure # [基础层] 常用工具类, 外部API调用
|
│ └── AssetManager.Infrastructure # [基础层] 常用工具类, 外部API调用
|
||||||
└── AssetManager.sln
|
└── AssetManager.sln
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📊 策略引擎 (Strategy Engine)
|
||||||
|
|
||||||
|
### 功能介绍
|
||||||
|
|
||||||
|
策略引擎是系统的核心组件,负责根据配置的策略参数计算交易信号。支持以下三种策略类型:
|
||||||
|
|
||||||
|
1. **双均线策略 (ma_trend)** - 经典的趋势跟踪策略,通过短期均线和长期均线的交叉产生买卖信号
|
||||||
|
2. **吊灯止损策略 (chandelier_exit)** - 趋势跟踪止损策略,通过计算最高价/最低价和 ATR(平均真实波幅)来设置止损止盈位
|
||||||
|
3. **风险平价策略 (risk_parity)** - 资产配置策略,通过调整各资产权重使每个资产对组合的风险贡献相等
|
||||||
|
|
||||||
|
### 策略配置示例
|
||||||
|
|
||||||
|
#### 1. 双均线策略 (ma_trend)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"maType": "SMA", // 均线类型:SMA(简单移动平均) / EMA(指数移动平均)
|
||||||
|
"shortPeriod": 20, // 短期均线周期
|
||||||
|
"longPeriod": 60 // 长期均线周期
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 吊灯止损策略 (chandelier_exit)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"period": 22, // 周期(通常为 22)
|
||||||
|
"multiplier": 3.0, // ATR 倍数(通常为 3.0)
|
||||||
|
"useClose": false // 是否使用收盘价计算(false 表示用最高价/最低价)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 风险平价策略 (risk_parity)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"lookbackPeriod": 60, // 历史数据回看周期
|
||||||
|
"rebalanceThreshold": 0.05, // 再平衡阈值(偏离度超过 5% 触发再平衡)
|
||||||
|
"assets": [ // 目标资产列表(可选,不指定则使用当前持仓)
|
||||||
|
{ "symbol": "AAPL", "targetWeight": 0.6 },
|
||||||
|
{ "symbol": "BTC/USD", "targetWeight": 0.4 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📡 API 示例
|
||||||
|
|
||||||
|
### 创建策略 API
|
||||||
|
|
||||||
|
**请求 URL**: `POST /api/v1/strategies`
|
||||||
|
|
||||||
|
**请求体示例**:
|
||||||
|
|
||||||
|
#### 1. 创建双均线策略
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "双均线策略",
|
||||||
|
"type": "ma_trend",
|
||||||
|
"description": "经典趋势跟踪策略",
|
||||||
|
"riskLevel": "medium",
|
||||||
|
"tags": ["趋势", "均线"],
|
||||||
|
"parameters": {
|
||||||
|
"maType": "SMA",
|
||||||
|
"shortPeriod": 20,
|
||||||
|
"longPeriod": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2. 创建风险平价策略
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "风险平价策略",
|
||||||
|
"type": "risk_parity",
|
||||||
|
"description": "资产配置策略",
|
||||||
|
"riskLevel": "low",
|
||||||
|
"tags": ["资产配置", "风险控制"],
|
||||||
|
"parameters": {
|
||||||
|
"lookbackPeriod": 60,
|
||||||
|
"rebalanceThreshold": 0.05,
|
||||||
|
"assets": [
|
||||||
|
{ "symbol": "AAPL", "targetWeight": 0.6 },
|
||||||
|
{ "symbol": "BTC/USD", "targetWeight": 0.4 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 3. 创建吊灯止损策略
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "吊灯止损策略",
|
||||||
|
"type": "chandelier_exit",
|
||||||
|
"description": "趋势跟踪止损策略",
|
||||||
|
"riskLevel": "high",
|
||||||
|
"tags": ["止损", "趋势"],
|
||||||
|
"parameters": {
|
||||||
|
"period": 22,
|
||||||
|
"multiplier": 3.0,
|
||||||
|
"useClose": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**响应示例**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"Id": "12345678-1234-1234-1234-1234567890ab",
|
||||||
|
"Title": "双均线策略",
|
||||||
|
"Status": "created"
|
||||||
|
}
|
||||||
|
```
|
||||||
Loading…
Reference in New Issue
Block a user