AI量化知识树

利用生成对抗网络(GAN)合成数据构建量化策略

由bqopniu创建,最终由bqopniu 被浏览 7 用户

为什么使用TGAN?

在量化交易中,你可能会遇到日常金融数据不足以回测策略的情况。然而,遵循真实数据分布的合成数据可以非常有用,可以帮助你用足够数量的观测值来回测策略。生成对抗网络(GAN)将帮助我们创建合成数据。具体来说,我们将使用用于时间序列数据的GAN模型。

文章大纲

在这篇文章中,你将学习到:

  • GAN算法的定义及其工作原理
  • 如何使用时间序列GAN(TGAN)算法的PAR合成器
  • 如何使用TGAN算法生成的合成数据回测策略
  • TGAN算法的益处和挑战
  • 一些提高结果的注意事项

适用人群与具备条件

这篇文章适用于任何可能需要使用稀缺金融数据回测策略的交易者。你应该已经知道如何回测策略,了解技术指标、机器学习、随机森林、Python和深度学习。

GAN算法是什么

生成对抗网络(GAN)是一种先进的深度学习架构,由两个神经网络在竞争训练过程中相互作用。这个框架旨在基于指定的训练数据集生成越来越逼真的数据。

生成对抗网络(GAN)由两个相互连接的深度神经网络组成:生成器和判别器。这些网络在一个竞争环境中运作,生成器的目标是创建新数据,而判别器的角色是判断产生的输出是真实的还是人工生成的。

GAN算法结构

从技术角度来看,GAN的操作可以总结如下。虽然整个计算机制背后有一个复杂的数学框架,但这里提供一个简化的解释:

  1. 生成器神经网络仔细检查训练数据集以识别其底层特征。同时,判别器神经网络独立分析原始训练数据,识别其特征。
  2. 然后,生成器通过引入噪声或随机修改来改变特定数据属性。这些修改后的数据随后呈现给判别器。
  3. 判别器评估生成的输出来自真实数据集的可能性。然后,它向生成器提供反馈,建议其在后续迭代中减少噪声向量的随机性。
  4. 生成器寻求增加判别器做出错误判断的机会,而判别器则努力降低其错误率。通过迭代训练周期,生成器和判别器逐步发展并相互挑战,直到达到平衡状态。此时,判别器无法区分真实数据和生成数据,标志着训练过程的结束。

在这种情况下,我们将使用SDV库中的特殊GAN算法来处理时间序列数据。该算法遵循上述相同的程序,但在时间序列的情况下,判别器通过匹配真实和合成回报分布来学习从真实数据中生成类似的时间序列。

SDV库中的PAR合成器

这篇文章中讨论的GAN算法来自张在2022年发表的研究论文“合成数据库中的序列模型”。该算法的确切名称是有条件的概率自回归(CPAR)模型。

该模型仅使用多序列数据表,即多变量时间序列数据。这里的区别是,对于每个资产价格,你需要一个上下文变量,该变量在估计过程中识别资产,并且在序列日期时间索引或行内不变化,即这些上下文变量在序列过程中不改变。这被称为“上下文信息”。在股票市场中,行业和公司部门表示资产的“上下文”,即资产所属的上下文。

关于这个算法的一些注意事项:

  • 提供多种数据类型,包括数值、分类、日期时间等,以及一些缺失值。
  • 可以在单个数据框中整合多个序列,每个资产可以有不同的观测值数量。
  • 每个序列都有其独特的上下文。
  • 你不能仅使用单个资产价格数据运行此模型。实际上,你需要多个资产价格数据。

使用合成数据回测基于机器学习的策略

让我们快速进入我们的脚本!

首先,让我们导入库

# Import the necessary libraries
import numpy as np
import pandas as pd
import pyfolio as pf
import datetime as dt
import yfinance as yf
import matplotlib.pyplot as plt
from ta import add_all_ta_features
from sdv.sequential import PARSynthesizer
from sdv.metadata import Metadata
from statsmodels.tsa.stattools import adfuller
from sklearn.ensemble import RandomForestClassifier


import warnings
warnings.filterwarnings('ignore')

让我们导入1990年至2024年12月的苹果和微软股票价格数据。我们分别下载这两家公司的股票价格数据,然后创建一个名为“stock”的新列,该列将在所有行中包含每行对应股票价格数据的公司名称。最后,我们将这些数据合并在一起。

tickers = ['AAPL', 'MSFT']

#Download the Apple stock data
apple = yf.download(tickers[0], start='1990-01-01', end='2024-12-13', auto_adjust=True)

# Create the percentage rets
apple['rets'] = apple['Close'].pct_change()

# Set the signal column
apple['signal'] = 0.0

# Download the Apple stock data
msft = yf.download(tickers[1], start='1990-01-01', end='2024-12-13', auto_adjust=True)

# Create the percentage rets
msft['rets'] = msft['Close'].pct_change()

# Set the signal column
msft['signal'] = 0.0

# Create column with the stock name
apple['stock'] = tickers[0]

# Create column with the stock name
msft['stock'] = tickers[1]

# Concatenate data
data = pd.concat([apple, msft])

# Sort index by datetime
data.sort_index(inplace=True)

# Drop Nan values
data.dropna(inplace=True)

# Print the data
data

让我们创建一个函数来为我们的新合成数据生成合成的日期时间索引:

def create_datetimes_for_synthetic_data(start, num_of_dates):
   # Create datetime list
   date_list = [start + dt.timedelta(days=1) for x in range(1,num_of_dates+1)]
      
   return date_list

让我们现在创建一个将用于生成合成数据的函数。该函数的解释如下步骤所示:

  1. 复制真实的历史数据框:首先,我们需要一个包含真实历史数据的副本,以便在生成合成数据时作为参考。
  2. 创建合成数据框:接下来,我们将创建一个新的空数据框,用于存储生成的合成数据。
  3. 创建上下文列:通过复制“stock”列来创建一个名为“context”的新列。这个列将用于标识每个合成数据点所属的股票。
  4. 设置元数据结构:这个结构对于SDV库中的GAN算法是必要的。在这里,我们定义列的数据类型,并指定“stock”列为ID,因为它将识别属于每只股票的时间序列。我们还指定序列索引,即“Date”列,它描述了每只股票价格数据的时间序列日期时间索引。
  5. 设置上下文列:将上下文列设置为与“stock”列匹配,这是一种“技巧”,用于将“Volume”和“Returns”列与相同资产价格数据关联起来。这种方法确保合成器生成完全新的序列,这些序列遵循原始数据集的结构。每个生成的序列代表一个新的假设资产,反映了数据中的总体模式,但并不对应于任何现实世界的公司(例如苹果或微软)。通过使用“stock”列作为上下文列,我们保持了资产价格回报分布的一致性。
  6. 设置ParSynthetizer模型对象:如果你有Nvidia GPU,请将cuda设置为True,否则设置为False。
  7. 拟合GAN模型:对于“Volume”和价格回报数据拟合GAN模型。我们不输入OHL(开盘价、最高价、最低价)数据,因为模型可能会生成最高价低于最低价,或者最低价高于最高价等不合理的情况。
  8. 输出合成数据:基于不同的种子生成合成数据。对于每个种子:
    • 指定一个定制的情景上下文,其中我们将“stock”和“context”定义为相等,以获得相同的苹果和微软价格回报分布。

    • 使用特定的观测值数量(命名为

      sample_num_obs
      

      )获取苹果和微软的合成样本。

    • 仅保存“Symbol”数据框在

      synthetic_sample
      

      中。

    • 计算收盘价。

    • 获取相对于收盘价的历史平均回报和标准差,用于高价位和低价位。

    • 基于上述计算高价位和低价位。

    • 使用前一个收盘价创建开盘价。

    • 将价格四舍五入到小数点后两位。

    • 根据种子编号将合成数据保存到字典中。关于种子的讨论将在后面进行。

这个函数的目的是生成与原始数据结构相似但具有新特征的合成数据,以便在量化交易策略的回测中使用。通过这种方式,即使在真实数据不足的情况下,也能够进行有效的策略测试和验证。

def create_synthetic_data(seeds_list, symbol, data, sample_num_obs):


   # Copy the dataframe
   data = data.copy()
  
   # Create synthetic dataframe
   synthetic_data = pd.DataFrame(index = create_datetimes_for_synthetic_data(data[data['stock']=='AAPL'].index[-1], sample_num_obs),
                                 columns=['Close', 'Volume','rets'])
  
   data['context'] = data['stock'].copy()
   fitting_data = data[['stock','Volume','rets','context']].reset_index()


   metadata = Metadata.load_from_dict({
       'tables': {
           'fitting_data': {
               'columns': {
                   'stock': { 'sdtype': 'id' },
                   'Date': { 'sdtype': 'datetime', 'datetime_format': '%Y-%m-%d %H:%M:%S' },
                   'Volume': { 'sdtype': 'numerical'},
                   'rets': { 'sdtype': 'numerical'},
                   'context': { 'sdtype': 'categorical'}
               },
               'sequence_key': 'stock',
               'sequence_index': 'Date'
           }
       }
   })


   synthesizer = PARSynthesizer(
       metadata,
       context_columns=['context'],
       cuda=True,
       verbose=False)


   # Fit the model
   synthesizer.fit(fitting_data)


   synthetic_data_dict = {}


   for seed in seeds_list:
       # Set the random numpy seed
       np.random.seed(seed)


       scenario_context = pd.DataFrame(data={
           'stock': ['AAPL', 'MSFT'],
           'context': ['AAPL', 'MSFT']})


       synthetic_sample = synthesizer.sample_sequential_columns(
           context_columns=scenario_context,
           sequence_length=sample_num_obs)


       synthetic_sample = synthetic_sample[synthetic_sample['stock']==symbol][['Volume','rets']]


       # Subset only the ticker synthetic data
       synthetic_data[['Volume','rets']] = synthetic_sample[['Volume','rets']].values
       # Create the Close price data based on the synthetic return data
       synthetic_data['Close'] = (1+synthetic_data['rets']).cumprod()*data['Close'].iloc[-1]
  
       # Get the High mean return and standard deviation with respect to the Close price
       hc_mean, hc_std = (data['High']/data['Close']-1).mean(), (data['High']/data['Close']-1).std()
       # Get the Low mean return and standard deviation with respect to the Close price
       lc_mean, lc_std = abs(data['Low']/data['Close']-1).mean(), abs(data['Low']/data['Close']-1).std()
  
       # Create the High prices
       synthetic_data['High'] = np.random.lognormal(hc_mean, hc_std, len(synthetic_data.index))*synthetic_data['Close']
       # Create the Low prices
       synthetic_data['Low'] = synthetic_data['Close']/np.random.lognormal(lc_mean, lc_std, len(synthetic_data.index))
       # Create the Open prices
       synthetic_data['Open'] = synthetic_data['Close'].shift(1)
       # Round the High and Low prices to 2 decimals
       synthetic_data = synthetic_data.round(2)


       # Save the synthetic price dataframe into the dictionary
       synthetic_data_dict[seed] = synthetic_data
  
   return synthetic_data_dict

以下函数与我在之前关于风险约束凯利准则(Risk Constrained Kelly Criterion)文章中描述的相同。

def get_input_features(data):
   data = data.copy()
   # Get the input features based on the available technical indicators
   features = add_all_ta_features(data, open="Open", high="High", low="Low", close="Close", volume="Volume", fillna=True).drop(\
                                   ["Open", "High", "Low", "Close", "Volume", "rets"], axis=1)
  
   # Set 2 lists to save indicators names as needed
   indicators_to_become_stationary = list()
   indicators_to_drop = list()
  
   # Loop to check for each indicator's stationarity
   for indicator in features.columns.tolist():
       try:
           # Get the Augmented-Dickey Fuller p-value
           pvalue = adfuller(features[indicator].dropna(), regression='c', autolag='AIC')[1]
           # If p-value is higher than 5%
           if pvalue > 0.05:
               # Save the indicator name to become it stationary later
               indicators_to_become_stationary.append(indicator)
           # I p-value is NaN
           elif np.isnan(pvalue):
               # Save the indicator name to drop it later
               indicators_to_drop.append(indicator)
       except:
           # Save the indicator name to drop it later
           indicators_to_drop.append(indicator)
  
   # Make the respective indicators stationary
   features[indicators_to_become_stationary] = features[indicators_to_become_stationary].pct_change()
   # Drop the respective indicators which p-values where NaN
   features.drop(indicators_to_drop, axis=1, inplace=True)
  
   # Concatenate the input features with the previous dataframe
   data = pd.concat([data, features], axis=1)
   # Set the features columns' names as a list
   features = features.columns.tolist()


   return data, features

以下函数是关于使用合并样本(包含真实数据和合成数据)并执行以下操作:

  1. 获取基于技术指标的输入特征:从合并后的数据中提取用于预测的技术指标作为输入特征。这些技术指标可能包括移动平均线、相对强弱指数(RSI)、布林带等,具体取决于所使用的策略。
  2. 将Inf值替换为NaN值:在数据处理过程中,有时会出现无限大(Inf)的值,这些值可能会干扰模型的训练和预测。因此,将这些Inf值替换为缺失值(NaN),以便后续处理。
  3. 创建预测特征:基于输入特征生成用于预测的目标特征。这可能涉及到计算某些指标的组合、转换或衍生特征,以更好地捕捉数据中的模式和趋势。
  4. 删除NaN值:由于在数据处理过程中可能会引入NaN值,这些值在模型训练和预测时是无用的,因此需要将包含NaN值的行删除,以确保数据的完整性和模型的有效性。

这个函数的目的是对合并后的数据进行预处理,使其适合用于机器学习模型的训练和预测。通过这些步骤,可以确保输入数据的质量和一致性,从而提高模型的性能和可靠性。

def get_all_features(whole_sample):
   # Get the input features for the ML model
   whole_sample, features = get_input_features(whole_sample)


   # Replace Inf values with Nan observations
   whole_sample.replace([np.inf, -np.inf], np.nan, inplace=True)
   # Forward-fill the NaN values
   whole_sample.ffill(inplace=True)
   # Set the prediction feature
   whole_sample['y'] = np.where(whole_sample['rets'].shift(-1)>=0, 1.0, 0.0)
   # Dropna the rest of the NaN values
   whole_sample.dropna(inplace=True)


   return whole_sample, features

这个最后的函数是关于分别获取训练样本和测试样本的输入特征和预测特征。具体步骤如下:

def get_input_and_prediction_features(train_sample, test_sample, features):
   # Set the X train data
   X = train_sample[features]
   # Set the prediction feature train data
   y = train_sample['y']
   # Set the X test data
   X_test = test_sample[features]
   # Set the prediction feature test data
   y_test = test_sample['y']


   return X, y, X_test, y_test

接下来的步骤如下:

  1. 设置整个脚本的随机种子:为了确保结果的可重复性,设置一个固定的随机种子。这有助于在每次运行脚本时生成相同的随机数,从而确保合成数据和模型训练的一致性。

  2. 指定用于拟合合成模型和机器学习模型的数据年份:选择4年的数据用于训练合成模型和机器学习模型。这4年的数据将作为训练集,用于生成合成数据和训练预测模型。

  3. 设置用于创建合成数据的观测值数量:确定生成合成数据时使用的观测值数量,并将其保存为变量

    test_span
    

    。这个变量将决定合成数据的大小,以便在后续的回测中使用。

  4. 设置回测年份的起始年:定义回测开始的年份。这将帮助你在指定的时间范围内进行策略回测,评估模型在不同时间段的表现。

  5. 获取月度索引和种子列表:生成月度索引,这些索引将用于在回测过程中选择特定月份的数据。同时,准备一个种子列表,这些种子将用于生成不同的合成数据集,以评估模型在不同随机条件下的表现。月度索引和种子列表的具体用途将在后续步骤中详细解释。

这些步骤的目的是为合成数据的生成和机器学习模型的训练做好准备,同时确保回测过程的系统性和可重复性。通过这些设置,可以更好地控制实验条件,评估模型的稳定性和有效性。

# Set the random seed
np.random.seed(100)
# Set the window to be used to subset the data
window = 1000
# Set the test span to be used to split the data to fit the ML model
test_span = 250
# Set the initial year for backtesting
initial_year = 2024
# Get the monthly datetimes
all_monthly_indexes = apple.resample('M').last().index
monthly_index = [datetime for datetime in all_monthly_indexes if datetime.year==(initial_year-1)][-1:] + \
               [datetime for datetime in all_monthly_indexes if datetime.year>=initial_year]
# The seeds list
seeds_list = list(range(1,101,5))

我们创建一个for循环来回测策略:

这个for循环遍历2024年的每个月。 这是一个向前优化过程,我们在每个月末优化机器学习模型参数,并在下个月进行交易。 对于每个月,我们估计20个随机森林算法。每个模型将根据其随机种子不同。对于每个模型,我们创建合成数据以用于特定的机器学习模型。 for循环的步骤如下:

  1. 指定当前月和下个月末:确定当前月的最后一天和下个月的最后一天。

  2. 定义当前月和下个月末之间的跨度:计算当前月最后一天和下个月最后一天之间的时间跨度。

  3. 定义数据样本:定义到下个月的数据样本,并使用最后1000个观测值加上上述定义的跨度。

  4. 定义两个字典:一个用于保存准确率分数,另一个用于保存模型。

  5. 定义用于训练GAN算法和机器学习模型的数据样本:将这些数据保存在变量

    tgan_train_data
    

    中。

  6. 创建合成数据:使用之前定义的函数

    create_synthetic_data
    

    为每个种子创建合成数据。选择苹果股票数据用于回测策略。

  7. 对于每个种子

    • 创建一个新变量,根据种子保存相应的合成数据。
    • 更新开盘价的第一个观测值。
    • 将真实苹果股票价格数据与其合成数据合并。
    • 排序索引。
    • 创建输入特征。
    • 将数据分为训练和测试数据框。
    • 从上述两个数据框中分离输入和预测特征,分别作为X和y。
    • 设置随机森林算法对象。
    • 使用训练数据拟合模型。
    • 使用测试数据保存准确率分数。
  8. 选择最佳模型种子:在估计所有机器学习模型后,我们根据合成数据预测的准确率分数选择最佳随机森林模型。

  9. 创建输入特征

    • 将数据分为训练和测试数据框。
    • 获取下个月的信号预测。
  10. 继续循环迭代

    • 下一个策略性能计算、绘图和基于pyfolio的性能分析表是基于之前提到的风险约束凯利准则文章。

通过这个for循环,我们可以在每个月末优化模型参数,并在下个月使用优化后的模型进行交易。这种方法允许我们在不同的市场条件下动态调整模型,以提高策略的适应性和盈利能力。

# Subset the data to create the strategy rets
results = apple.loc[monthly_index[1]:,:]

# Create the Buy-and-Hold cumulative rets
results['bh_cum_rets'] = (1+results['rets']).cumprod()

# Create the strategy returns
results['stra_rets'] = results['rets'] * results['signal'].shift()

# Create the strategy cumulative returns
results['stra_cum_rets'] = (1+results['stra_rets']).cumprod()

# Set the figure size
plt.figure(figsize=(15,7))


# Plot both the Buy-and-hold and the strategies' cumulative rets
plt.plot(results.index, results[['bh_cum_rets', 'stra_cum_rets']], label = ["B&H", "Strategy"])


# Set the title of the graph
plt.title('Cumulative Returns', fontsize=16)


# Set the x- and y- axis labels and ticks sizes
plt.xlabel('Year', fontsize=15)
plt.ylabel('Returns', fontsize=15)
plt.tick_params(axis='both', labelsize=15)


# Set the plot legend location
plt.legend(loc=2, prop={'size': 15}, bbox_to_anchor=(0,1))


plt.savefig('Figures/strategy-returns.png', bbox_inches='tight')


# Create the trading tear sheet for the Buy-and-Hold cumulative returns
pf.create_full_tear_sheet(results['bh_cum_rets'].pct_change().dropna())
# Create the trading tear sheet for the strategy cumulative returns
pf.create_full_tear_sheet(results['stra_cum_rets'].pct_change().dropna())

根据以上的收益结果,可以总结如下:


从整体结果来看,使用买入并持有(Buy-and-Hold, B&H)策略通常能获得更好的表现。尽管B&H策略的年化回报率更高,但使用合成数据进行回测的机器学习(ML)策略的波动性更低;尽管如此,B&H策略的夏普比率(Sharpe Ratio)更高。卡尔马比率(Calmar Ratio)和索提诺比率(Sortino Ratio)对于B&H策略更高,尽管我们通过ML策略获得了更低的最大回撤(Max Drawdown)。

详细解释:

  1. 年化回报率(Annual Return)
    • B&H策略:年化回报率更高,这意味着在长期内,简单地买入并持有资产可能带来更高的平均年回报。
    • ML策略:虽然年化回报率略低,但通过机器学习模型的动态调整,可能在某些时期表现更好。
  2. 波动性(Volatility)
    • B&H策略:波动性相对较高,因为市场波动直接影响持仓价值。
    • ML策略:波动性更低,这可能是因为机器学习模型能够通过调整持仓来减少市场波动的影响,尤其是在使用合成数据进行回测时,模型可以更好地适应不同的市场条件。
  3. 夏普比率(Sharpe Ratio)
    • B&H策略:夏普比率更高,这意味着在考虑风险调整后,B&H策略的超额回报更高。夏普比率是通过将超额回报除以标准差(波动性)来计算的,因此B&H策略在风险调整后的表现更好。
    • ML策略:夏普比率略低,但仍然表现出较好的风险调整回报。
  4. 卡尔马比率(Calmar Ratio)
    • B&H策略:卡尔马比率更高,这意味着在考虑最大回撤的情况下,B&H策略的年化复合回报更高。卡尔马比率是通过将年化复合回报除以最大回撤来计算的,因此B&H策略在最大回撤调整后的表现更好。
    • ML策略:卡尔马比率略低,但最大回撤更小,这表明ML策略在控制回撤方面表现更好。
  5. 索提诺比率(Sortino Ratio)
    • B&H策略:索提诺比率更高,这意味着在考虑下行波动性的情况下,B&H策略的超额回报更高。索提诺比率是通过将超额回报除以下行波动性来计算的,因此B&H策略在下行风险调整后的表现更好。
    • ML策略:索提诺比率略低,但下行波动性更小,这表明ML策略在控制下行风险方面表现更好。
  6. 最大回撤(Max Drawdown)
    • B&H策略:最大回撤较大,这意味着在市场下跌时,持仓价值可能会大幅下降。
    • ML策略:最大回撤更小,这表明ML策略在控制回撤方面表现更好,能够更好地保护投资组合的价值。

总结

尽管B&H策略在年化回报率和风险调整后的表现(如夏普比率、卡尔马比率和索提诺比率)上表现更好,但ML策略在控制波动性和最大回撤方面表现更佳。这表明ML策略在某些方面可能更适合风险厌恶型投资者,而B&H策略则更适合追求长期高回报的投资者。选择哪种策略取决于投资者的风险偏好和投资目标。

TGAN算法的益处和挑战

优势

  1. 降低数据收集成本
    • 合成数据可以根据较少的观测值生成,相比收集特定资产或资产组的全部数据,这可以显著降低数据收集成本。这使得我们能够将更多精力集中在模型构建上,而不是数据收集上。
  2. 更好的数据质量控制
    • 历史数据只是整个数据分布的一个路径。高质量的合成数据可以提供同一数据分布的多个路径,允许你基于多种场景拟合模型。
  3. 模型拟合更好
    • 由于上述原因,使用合成数据拟合的模型将更好,机器学习模型的参数将更优化。

挑战

  1. 训练时间长
    • TGAN算法的拟合可能需要很长时间。训练数据样本越大,拟合数据所需的时间就越长。当处理数百万个观测值时,完成训练可能需要很长时间。
  2. 训练不稳定
    • 由于生成器和判别器网络是相互对抗的,GANs经常会出现训练不稳定的情况,即模型无法拟合数据。为了确保稳定的收敛,超参数必须仔细调整。
  3. 模型崩溃
    • 如果生成器和判别器之间的训练不平衡,TGAN可能会出现模型崩溃,导致生成的合成数据样本多样性减少。超参数需要再次调整以解决这个问题。

关于基于TGAN的回测模型的一些注意事项

以下是一些可以改进脚本的建议:

  1. 应用风险管理阈值
    • 通过应用风险管理阈值,如止损(stop-loss)和获利(take-profit)目标,可以改善权益曲线。这些阈值可以帮助控制风险并优化策略的表现。
  2. 选择最佳模型的指标
    • 我们使用了准确率(accuracy score)来选择最佳模型,但也可以使用其他指标,如F1分数、AUC-ROC,或者策略表现指标,如年化回报率、夏普比率等。这些指标可以更全面地评估模型的性能。
  3. 生成多个时间序列
    • 对于每个随机森林模型,可以为每只资产生成多个时间序列(序列),以回测多条路径(序列)上的策略。我们目前这样做是为了减少每天运行算法的时间,并用于演示目的。创建多个路径来回测策略可以让你的最佳模型具有更稳健的策略表现,这是从合成数据中获益的最佳方式。
  4. 优化输入特征的计算
    • 我们多次计算真实股票价格的输入特征,但实际上可以只计算一次。你可以调整数据处理流程,以减少不必要的重复计算,提高效率。
  5. 调整训练周期数
    • 在函数

      create_synthetic_data
      

      中定义的

      ParSynthetizer
      

      对象有一个输入参数

      epochs
      

      。这个参数允许我们将整个训练数据集输入到TGAN算法中(使用生成器和判别器)。我们使用了默认值128。增加训练周期数可以提高合成样本的质量,但同时也会增加模型拟合数据所需的时间。你应该根据你的计算能力和前向优化过程的最佳时间来平衡这两者。

  6. 使用ARFIMA模型处理非平稳特征
    • 代替创建非平稳特征的百分比回报,可以使用ARFIMA模型应用于每个非平稳特征,并使用残差作为输入特征。为什么这样做?可以参考我们的ARFIMA模型博客文章,了解其优势和应用方法。
  7. 考虑交易成本
    • 不要忘记使用交易成本来更真实地模拟权益曲线的表现。交易成本是实际交易中不可避免的一部分,考虑它们可以更准确地评估策略的实际表现。

通过这些改进,可以提高基于TGAN的回测模型的性能和可靠性,更好地评估和优化量化交易策略。

策略源码

代码仅供参考,不适合直接运行,需要在bigquant平台上改进:

https://bigquant.com/codesharev3/2ca5079b-6fc9-44cd-8ecd-d610ed1a16a9

\

标签

量化交易
{link}