策略优化

概述

StockSharp 提供了内置机制,可在历史数据上优化策略参数。优化过程会自动遍历策略参数的不同组合,例如指标周期、时间周期、阈值以及其他设置,并帮助按选定标准找到最佳结果,例如利润、回撤、交易次数等。

可用的优化模式有两种:

  • Brute force -- BruteForceOptimizer 类。遍历所有可能的参数组合,或遍历其中的随机子集。
  • Genetic algorithm -- GeneticOptimizer 类。使用进化算法寻找最优参数,对于较大的参数空间效率更高。

两个优化器都继承自 BaseOptimizer,并以异步方式运行,在每次迭代完成时通过 IAsyncEnumerable 返回结果。

准备策略

定义带优化范围的参数

要优化策略,需要通过 StrategyParam<T> 定义参数并指定取值范围。SetOptimize(from, to, step) 方法设置需要遍历的范围,SetCanOptimize(true) 为参数启用优化:

class SmaStrategy : Strategy
{
    private bool? _isShortLessThenLong;

    public SmaStrategy()
    {
        _longSma = Param(nameof(LongSma), 80)
            .SetCanOptimize(true)
            .SetOptimize(50, 100, 5);      // from 50 to 100 with a step of 5

        _shortSma = Param(nameof(ShortSma), 30)
            .SetCanOptimize(true)
            .SetOptimize(20, 40, 1);        // from 20 to 40 with a step of 1

        _candleTimeFrame = Param<TimeSpan?>(nameof(CandleTimeFrame))
            .SetCanOptimize(true)
            .SetOptimize(
                TimeSpan.FromMinutes(5),    // from 5 minutes
                TimeSpan.FromMinutes(15),   // to 15 minutes
                TimeSpan.FromMinutes(5));   // with a step of 5 minutes

        _candleType = Param(nameof(CandleType),
            TimeSpan.FromMinutes(1).TimeFrame()).SetRequired();
    }

    private readonly StrategyParam<int> _longSma;
    public int LongSma
    {
        get => _longSma.Value;
        set => _longSma.Value = value;
    }

    private readonly StrategyParam<int> _shortSma;
    public int ShortSma
    {
        get => _shortSma.Value;
        set => _shortSma.Value = value;
    }

    private readonly StrategyParam<TimeSpan?> _candleTimeFrame;
    public TimeSpan? CandleTimeFrame
    {
        get => _candleTimeFrame.Value;
        set => _candleTimeFrame.Value = value;
    }

    private readonly StrategyParam<DataType> _candleType;
    public DataType CandleType
    {
        get => _candleType.Value;
        set => _candleType.Value = value;
    }

    protected override void OnStarted2(DateTime time)
    {
        base.OnStarted2(time);

        var dt = CandleTimeFrame is null
            ? CandleType
            : DataType.Create(CandleType.MessageType, CandleTimeFrame);

        var subscription = new Subscription(dt, Security)
        {
            MarketData =
            {
                IsFinishedOnly = true,
            }
        };

        var longSma = new SMA { Length = LongSma };
        var shortSma = new SMA { Length = ShortSma };

        SubscribeCandles(subscription)
            .Bind(longSma, shortSma, OnProcess)
            .Start();
    }

    private void OnProcess(ICandleMessage candle, decimal longValue, decimal shortValue)
    {
        if (candle.State != CandleStates.Finished)
            return;

        var isShortLessThenLong = shortValue < longValue;

        if (_isShortLessThenLong == null)
        {
            _isShortLessThenLong = isShortLessThenLong;
        }
        else if (_isShortLessThenLong != isShortLessThenLong)
        {
            var direction = isShortLessThenLong ? Sides.Sell : Sides.Buy;
            var volume = Position == 0 ? Volume : Position.Abs().Min(Volume) * 2;
            var price = candle.ClosePrice;

            if (direction == Sides.Buy)
                BuyLimit(price, volume);
            else
                SellLimit(price, volume);

            _isShortLessThenLong = isShortLessThenLong;
        }
    }

    protected override void OnReseted()
    {
        base.OnReseted();
        _isShortLessThenLong = null;
    }
}

支持的参数类型

优化支持以下参数类型:

类型 SetOptimize 示例
intlong 以及其他整数类型 .SetOptimize(10, 100, 5)
decimaldoublefloat .SetOptimize(0.01m, 0.10m, 0.01m)
TimeSpan .SetOptimize(TimeSpan.FromMinutes(1), TimeSpan.FromMinutes(30), TimeSpan.FromMinutes(1))
Unit .SetOptimize(new Unit(1, UnitTypes.Percent), new Unit(5, UnitTypes.Percent), new Unit(0.5m, UnitTypes.Percent))
bool .SetOptimize(false, true)

对于具有离散取值集合的参数,例如 DataType,请使用 SetOptimizeValues

_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
    .SetCanOptimize(true)
    .SetOptimizeValues(new[]
    {
        TimeSpan.FromMinutes(5).TimeFrame(),
        TimeSpan.FromMinutes(15).TimeFrame(),
        TimeSpan.FromMinutes(30).TimeFrame(),
    });

Brute force 优化

BruteForceOptimizer 类会遍历参数值的所有可能组合。该模式适用于较小的参数空间。

创建并配置优化器

// 工具和投资组合。
var security = new Security
{
    Id = "AAPL@NASDAQ",
    PriceStep = 0.01m,
};

var portfolio = Portfolio.CreateSimulator();

// 历史数据存储。
var storageRegistry = new StorageRegistry
{
    DefaultDrive = new LocalMarketDataDrive(folder)
};

// 创建优化器。
var optimizer = new BruteForceOptimizer(
    new CollectionSecurityProvider(new[] { security }),
    new CollectionPortfolioProvider(new[] { portfolio }),
    storageRegistry);

// 配置仿真参数。
var settings = optimizer.EmulationSettings;
settings.MaxIterations = 100;                          // maximum iterations (0 = unlimited)
settings.CommissionRules = new[]                       // commission
{
    new CommissionTradeRule { Value = 0.01m },
};
// settings.BatchSize = 8;                             // number of parallel threads
                                                       // default = CPU * 2

// 在迭代之间缓存市场数据以加快优化。
optimizer.AdapterCache = new();

运行 brute force 优化

// 带优化范围的基础策略。
var strategy = new SmaStrategy
{
    Volume = 1,
    Security = security,
    Portfolio = portfolio,
};

// 选择要优化的参数。
var longParam = (StrategyParam<int>)strategy.Parameters[nameof(strategy.LongSma)];
var shortParam = (StrategyParam<int>)strategy.Parameters[nameof(strategy.ShortSma)];
var tfParam = (StrategyParam<TimeSpan?>)strategy.Parameters[nameof(strategy.CandleTimeFrame)];

var optimizeParams = new IStrategyParam[] { longParam, shortParam, tfParam };

// 生成所有参数组合。
var strategies = strategy.ToBruteForce(optimizeParams, out _, out var totalCount);

// 运行优化。
var startTime = new DateTime(2020, 1, 1);
var stopTime = new DateTime(2020, 12, 31);
var cts = new CancellationTokenSource();

await foreach (var (s, parameters) in optimizer.RunAsync(startTime, stopTime, strategies, cts.Token))
{
    // s 是回测后带有结果的策略。
    Console.WriteLine($"PnL={s.PnL}, LongSma={s.Parameters["LongSma"].Value}, " +
                      $"ShortSma={s.Parameters["ShortSma"].Value}");
}

随机抽样

如果遍历所有组合耗时过长,可以使用随机抽样。ToBruteForceRandom 方法会生成指定数量的随机组合:

var randomCount = 50; // number of random combinations

var strategies = strategy.ToBruteForceRandom(
    optimizeParams,
    randomCount,
    out _,
    out var totalCount);

await foreach (var (s, parameters) in optimizer.RunAsync(startTime, stopTime, strategies, cts.Token))
{
    Console.WriteLine($"PnL={s.PnL}");
}

Genetic 优化

GeneticOptimizer 类实现了遗传算法。当参数数量较多时,它比 brute force 更高效。该算法会自动在较少迭代中向最优值收敛。

创建并配置优化器

var optimizer = new GeneticOptimizer(
    new CollectionSecurityProvider(new[] { security }),
    new CollectionPortfolioProvider(new[] { portfolio }),
    storageRegistry,
    Paths.FileSystem);    // file system for the fitness formula

optimizer.AdapterCache = new();

// 配置遗传算法。
optimizer.Settings.Population = 8;            // population size
optimizer.Settings.PopulationMax = 16;        // maximum population size
optimizer.Settings.GenerationsMax = 20;       // maximum generations
optimizer.Settings.GenerationsStagnation = 5; // stop after N generations without improvement
optimizer.Settings.MutationProbability = 0.1m;
optimizer.Settings.CrossoverProbability = 0.75m;
optimizer.Settings.Fitness = "PnL";           // fitness formula (PnL by default)

optimizer.EmulationSettings.MaxIterations = 100;

遗传算法参数

属性 描述 默认值
Population 初始种群大小 8
PopulationMax 最大种群大小 16
GenerationsMax 最大代数(0 = 不限制) 20
GenerationsStagnation 连续 N 代无改进后停止(0 = 禁用) 5
MutationProbability 变异概率(0..1) 0.1
CrossoverProbability 交叉概率(0..1) 0.75
Fitness 适应度函数公式 "PnL"
Selection 选择算子 TournamentSelection
Crossover 交叉算子 OnePointCrossover
Mutation 变异算子 UniformMutation
Reinsertion 代际替换策略 ElitistReinsertion

适应度公式

Fitness 属性定义用于评估策略的公式。策略统计指标可通过缩写访问:

缩写 策略统计指标
PnL 净利润
MaxDD 最大回撤
MaxRelDD 最大相对回撤
WinTrades 盈利交易
LosTrades 亏损交易
Recovery 恢复因子
Ret 收益率
TCount 交易次数
AvgTPnL 每笔交易平均利润

公式可以组合使用,例如 "PnL - MaxDD""Recovery"

运行 genetic 优化

var strategy = new SmaStrategy
{
    Volume = 1,
    Security = security,
    Portfolio = portfolio,
};

// 为遗传优化器准备参数。
var longParam = (StrategyParam<int>)strategy.Parameters[nameof(strategy.LongSma)];
var shortParam = (StrategyParam<int>)strategy.Parameters[nameof(strategy.ShortSma)];
var tfParam = (StrategyParam<TimeSpan?>)strategy.Parameters[nameof(strategy.CandleTimeFrame)];

// ToGeneticParameters 将策略参数转换为遗传优化器格式。
// 对于 TimeSpan? 这类取值集合离散的参数,请通过
// (param, values) 元组显式传入列表:
var geneticParams = strategy.ToGeneticParameters(new (IStrategyParam, IEnumerable)[]
{
    (tfParam, new[] { TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(15) }),
    (longParam, null),   // null = use the range from SetOptimize
    (shortParam, null),
});

// 运行优化。
var cts = new CancellationTokenSource();

await foreach (var (s, parameters) in optimizer.RunAsync(
    startTime, stopTime, strategy, geneticParams, cancellationToken: cts.Token))
{
    Console.WriteLine($"PnL={s.PnL}");
}

通用优化器设置

OptimizerSettings 类可通过 optimizer.EmulationSettings 访问,其中包含两种优化类型通用的设置:

属性 描述 默认值
BatchSize 并行测试的策略数量 CPU * 2
MaxIterations 最大迭代次数(0 = 不限制) 0
MaxMessageCount 最大处理消息数(-1 = 不限制) -1
CommissionRules 佣金计算规则 null

数据缓存

AdapterCache 属性会在迭代之间缓存市场数据。由于数据只从存储中加载一次,这可以显著加快优化速度:

optimizer.AdapterCache = new MarketDataStorageCache();

优化器事件

事件 描述
SingleProgressChanged 单次迭代进度变化时调用。参数:(Strategy, IStrategyParam[], int progress)。进度为 100 表示该迭代已完成。
StrategyInitialized 策略初始化后、回测开始前调用。
ConnectorInitialized 连接器创建后、连接前调用。允许配置 HistoryEmulationConnector 参数。
optimizer.SingleProgressChanged += (strategy, parameters, progress) =>
{
    if (progress == 100)
        Console.WriteLine($"迭代完成: PnL={strategy.PnL}");
};

暂停和停止

优化可以暂停和恢复:

// 暂停。当前迭代会完成,新迭代不会启动。
optimizer.Pause();

// Resume.
optimizer.Resume();

// 检查状态。
bool isPaused = optimizer.IsPaused;

要完全停止优化,请取消 CancellationToken

cts.Cancel();

完整示例(控制台应用程序)

using System;
using System.Linq;
using System.Threading;

using StockSharp.Algo;
using StockSharp.Algo.Storages;
using StockSharp.Algo.Strategies;
using StockSharp.Algo.Strategies.Optimization;
using StockSharp.Algo.Commissions;
using StockSharp.BusinessEntities;
using StockSharp.Configuration;
using StockSharp.Messages;

// 配置工具和投资组合。
var security = new Security
{
    Id = "AAPL@NASDAQ",
    PriceStep = 0.01m,
};

var portfolio = Portfolio.CreateSimulator();

// 数据存储。
var storageRegistry = new StorageRegistry
{
    DefaultDrive = new LocalMarketDataDrive(Paths.HistoryDataPath)
};

// 创建优化器(暴力搜索)。
var optimizer = new BruteForceOptimizer(
    new CollectionSecurityProvider(new[] { security }),
    new CollectionPortfolioProvider(new[] { portfolio }),
    storageRegistry);

optimizer.EmulationSettings.MaxIterations = 100;
optimizer.EmulationSettings.CommissionRules = new[]
{
    new CommissionTradeRule { Value = 0.01m },
};
optimizer.AdapterCache = new();

// 配置策略。
var strategy = new SmaStrategy
{
    Volume = 1,
    Security = security,
    Portfolio = portfolio,
};

// 要优化的参数。
var longParam = (StrategyParam<int>)strategy.Parameters[nameof(strategy.LongSma)];
var shortParam = (StrategyParam<int>)strategy.Parameters[nameof(strategy.ShortSma)];
var optimizeParams = new IStrategyParam[] { longParam, shortParam };

// 生成组合。
var strategies = strategy.ToBruteForce(optimizeParams, out _, out var totalCount);

Console.WriteLine($"总迭代次数: {totalCount}");

// 运行优化。
var startTime = Paths.HistoryBeginDate;
var stopTime = Paths.HistoryEndDate;
var cts = new CancellationTokenSource();

var bestPnL = decimal.MinValue;
Strategy bestStrategy = null;

await foreach (var (s, parameters) in optimizer.RunAsync(startTime, stopTime, strategies, cts.Token))
{
    var pnl = s.PnL;
    var paramStr = string.Join(", ", parameters.Select(p => $"{p.Id}={p.Value}"));
    Console.WriteLine($"[{paramStr}] PnL={pnl:F2}");

    if (pnl > bestPnL)
    {
        bestPnL = pnl;
        bestStrategy = s;
    }
}

if (bestStrategy != null)
{
    Console.WriteLine($"\n最佳结果: PnL={bestPnL:F2}");
    foreach (var p in bestStrategy.Parameters)
        Console.WriteLine($"  {p.Id} = {p.Value}");
}

另请参阅