- GetNavHistoryAsync现在会自动检查是否有历史数据 - 无历史数据时自动调用BackfillNavHistoryInternalAsync - 拆分内部回填方法,避免重复验证权限
233 lines
6.8 KiB
C#
Executable File
233 lines
6.8 KiB
C#
Executable File
namespace AssetManager.Infrastructure.StrategyEngine;
|
||
|
||
/// <summary>
|
||
/// 技术指标计算库
|
||
/// </summary>
|
||
public static class TechnicalIndicators
|
||
{
|
||
/// <summary>
|
||
/// 计算简单移动平均 (SMA)
|
||
/// </summary>
|
||
/// <param name="values">值列表</param>
|
||
/// <param name="period">周期</param>
|
||
/// <returns>SMA 列表(长度与输入一致,前 period-1 个为 null)</returns>
|
||
public static List<decimal?> CalculateSMA(List<decimal> values, int period)
|
||
{
|
||
var result = new List<decimal?>();
|
||
if (values.Count < period)
|
||
{
|
||
result.AddRange(values.Select(_ => (decimal?)null));
|
||
return result;
|
||
}
|
||
|
||
// 前 period-1 个为 null
|
||
for (int i = 0; i < period - 1; i++)
|
||
{
|
||
result.Add(null);
|
||
}
|
||
|
||
// 计算第一个 SMA
|
||
decimal sum = 0;
|
||
for (int i = 0; i < period; i++)
|
||
{
|
||
sum += values[i];
|
||
}
|
||
result.Add(sum / period);
|
||
|
||
// 滑动窗口计算后续 SMA
|
||
for (int i = period; i < values.Count; i++)
|
||
{
|
||
sum = sum - values[i - period] + values[i];
|
||
result.Add(sum / period);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算指数移动平均 (EMA)
|
||
/// </summary>
|
||
/// <param name="values">值列表</param>
|
||
/// <param name="period">周期</param>
|
||
/// <returns>EMA 列表(长度与输入一致,前 period-1 个为 null)</returns>
|
||
public static List<decimal?> CalculateEMA(List<decimal> values, int period)
|
||
{
|
||
var result = new List<decimal?>();
|
||
if (values.Count < period)
|
||
{
|
||
result.AddRange(values.Select(_ => (decimal?)null));
|
||
return result;
|
||
}
|
||
|
||
// 前 period-1 个为 null
|
||
for (int i = 0; i < period - 1; i++)
|
||
{
|
||
result.Add(null);
|
||
}
|
||
|
||
// 第一个 EMA 使用 SMA
|
||
decimal sma = 0;
|
||
for (int i = 0; i < period; i++)
|
||
{
|
||
sma += values[i];
|
||
}
|
||
sma /= period;
|
||
result.Add(sma);
|
||
|
||
// 计算后续 EMA:EMA = (当前值 - 前一个 EMA) * 乘数 + 前一个 EMA
|
||
decimal multiplier = 2.0m / (period + 1);
|
||
decimal prevEma = sma;
|
||
|
||
for (int i = period; i < values.Count; i++)
|
||
{
|
||
decimal ema = (values[i] - prevEma) * multiplier + prevEma;
|
||
result.Add(ema);
|
||
prevEma = ema;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算真实波幅 (TR)
|
||
/// </summary>
|
||
/// <param name="highs">最高价列表</param>
|
||
/// <param name="lows">最低价列表</param>
|
||
/// <param name="closes">收盘价列表</param>
|
||
/// <returns>TR 列表</returns>
|
||
public static List<decimal> CalculateTR(List<decimal> highs, List<decimal> lows, List<decimal> closes)
|
||
{
|
||
var trList = new List<decimal>();
|
||
if (highs.Count == 0) return trList;
|
||
|
||
// 第一个 TR:High - Low
|
||
trList.Add(highs[0] - lows[0]);
|
||
|
||
// 后续 TR:Max(High-Low, High-PrevClose, PrevClose-Low)
|
||
for (int i = 1; i < highs.Count; i++)
|
||
{
|
||
decimal prevClose = closes[i - 1];
|
||
decimal tr1 = highs[i] - lows[i];
|
||
decimal tr2 = Math.Abs(highs[i] - prevClose);
|
||
decimal tr3 = Math.Abs(prevClose - lows[i]);
|
||
trList.Add(Math.Max(Math.Max(tr1, tr2), tr3));
|
||
}
|
||
|
||
return trList;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算平均真实波幅 (ATR)
|
||
/// </summary>
|
||
/// <param name="highs">最高价列表</param>
|
||
/// <param name="lows">最低价列表</param>
|
||
/// <param name="closes">收盘价列表</param>
|
||
/// <param name="period">周期(默认 14)</param>
|
||
/// <returns>ATR 列表(长度与输入一致,前 period-1 个为 null)</returns>
|
||
public static List<decimal?> CalculateATR(List<decimal> highs, List<decimal> lows, List<decimal> closes, int period = 14)
|
||
{
|
||
var result = new List<decimal?>();
|
||
var trList = CalculateTR(highs, lows, closes);
|
||
|
||
if (trList.Count < period)
|
||
{
|
||
result.AddRange(trList.Select(_ => (decimal?)null));
|
||
return result;
|
||
}
|
||
|
||
// 前 period-1 个为 null
|
||
for (int i = 0; i < period - 1; i++)
|
||
{
|
||
result.Add(null);
|
||
}
|
||
|
||
// 第一个 ATR:TR 的简单平均
|
||
decimal sum = 0;
|
||
for (int i = 0; i < period; i++)
|
||
{
|
||
sum += trList[i];
|
||
}
|
||
decimal atr = sum / period;
|
||
result.Add(atr);
|
||
|
||
// 后续 ATR:(前一个 ATR * (period-1) + 当前 TR) / period
|
||
for (int i = period; i < trList.Count; i++)
|
||
{
|
||
atr = (atr * (period - 1) + trList[i]) / period;
|
||
result.Add(atr);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算滚动窗口最高价
|
||
/// </summary>
|
||
/// <param name="values">值列表</param>
|
||
/// <param name="period">周期</param>
|
||
/// <returns>滚动最高价列表(长度与输入一致,前 period-1 个为 null)</returns>
|
||
public static List<decimal?> CalculateHighestHigh(List<decimal> values, int period)
|
||
{
|
||
var result = new List<decimal?>();
|
||
if (values.Count < period)
|
||
{
|
||
result.AddRange(values.Select(_ => (decimal?)null));
|
||
return result;
|
||
}
|
||
|
||
// 前 period-1 个为 null
|
||
for (int i = 0; i < period - 1; i++)
|
||
{
|
||
result.Add(null);
|
||
}
|
||
|
||
// 滑动窗口计算
|
||
for (int i = period - 1; i < values.Count; i++)
|
||
{
|
||
decimal max = decimal.MinValue;
|
||
for (int j = i - period + 1; j <= i; j++)
|
||
{
|
||
if (values[j] > max) max = values[j];
|
||
}
|
||
result.Add(max);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算滚动窗口最低价
|
||
/// </summary>
|
||
/// <param name="values">值列表</param>
|
||
/// <param name="period">周期</param>
|
||
/// <returns>滚动最低价列表(长度与输入一致,前 period-1 个为 null)</returns>
|
||
public static List<decimal?> CalculateLowestLow(List<decimal> values, int period)
|
||
{
|
||
var result = new List<decimal?>();
|
||
if (values.Count < period)
|
||
{
|
||
result.AddRange(values.Select(_ => (decimal?)null));
|
||
return result;
|
||
}
|
||
|
||
// 前 period-1 个为 null
|
||
for (int i = 0; i < period - 1; i++)
|
||
{
|
||
result.Add(null);
|
||
}
|
||
|
||
// 滑动窗口计算
|
||
for (int i = period - 1; i < values.Count; i++)
|
||
{
|
||
decimal min = decimal.MaxValue;
|
||
for (int j = i - period + 1; j <= i; j++)
|
||
{
|
||
if (values[j] < min) min = values[j];
|
||
}
|
||
result.Add(min);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
}
|