合约与元数据(Instruments)

合约与元数据配置。

译文说明

注:本文为非官方翻译,可能存在疏漏;请以原文为准。

本文档说明如何选择要交易的品种(instrument),以及相关配置方式。

在阅读本文之前,你应该已经看过:

Table of Contents

Different instrument sets

不同的品种集合用于不同的目的:

  • 所有“可能使用的品种”的超集在品种配置中定义;
  • 在生产环境中采样价格时,会使用一个子集:即当前已经保存了 multiple prices 的品种列表;
  • 在把调整价(back adjusted prices)导入回测环境或 update_systems 生产脚本时,会使用另一个子集:当前已经保存了调整价的品种列表(在数据库环境中,这个集合可能和上面不同;对于 .csv 来说,则是这里的价格文件);
  • 在回测环境内部,我们还可以基于不同原因进一步排除若干品种。

The global superset of all instruments

“全局品种超集”包含现在或未来你可能想交易的所有品种。仓库中给出的默认列表已经非常全面。除非是为了避免未来命名冲突,否则纯粹“臆想新增”一些品种意义不大。

The list of instruments we are sampling

这个集合是以 multiple instruments 数据库为基准的。在时间和数据成本允许的前提下,这个集合应该尽量全面——“先把价格采起来”一般是值得的,而事后回补历史数据通常会非常麻烦。

Instruments we have adjusted prices for, used for simulation and production system backtest raw data

如果回测和生产都从数据库读取价格,那么这个集合原则上应该和上一节的集合完全一致;但如果你在回测中使用 CSV 调整价,那么集合可能会有所不同。尽量避免这种差异!(除非是刻意为之,比如你打算在部分品种上做一个“快而脏”的回测。)

1
2
3
4
5
6
from systems.provided.futures_chapter15.basesystem import *
config = Config()
system = futures_system(config = config)
instruments_with_adj_prices = system.data.get_instrument_list()
"SP500" in instruments_with_adj_prices
>True

Instruments used for simulation and production system backtests

接下来是稍微复杂一点的部分 :-)
基本思路是:从“已经有调整价的品种集合”出发,继续构造三个子集:

  • “全局品种列表”(在有定义的情况下);
  • 去掉“始终排除的品种”(always excluded)后的全局品种列表。在这个阶段我们会删除 duplicate_instrumentsignored_instruments
  • 再在此基础上,去掉“为优化目的而排除的品种”:即具有 trading_restrictionsbad_markets 标签的品种。

The global list of instruments, when defined

如果我们加载的默认配置中不包含任何品种信息,那么原则上,只要某个品种在当前数据源(模拟用 CSV 或数据库)里有调整价,我们就都能用:

1
2
3
4
5
6
from systems.provided.futures_chapter15.basesystem import *
config = Config() # using a default config so we know we have all instruments there in principle
system = futures_system(config = config)
instruments_with_adj_prices = system.data.get_instrument_list()
"SP500" in instruments_with_adj_prices
>True

不过,一般我们不会用所有品种一起跑回测,因此可以用两种方式来缩小这个子集。第一种是显式指定标的权重:

1
2
3
4
system.cache.delete_all_items()
system.config.instrument_weights = dict(BTP=.5, US2=.5)
system.get_instrument_list()
>['BTP', 'US2']

第二种方式是只传入一个“品种列表”(不带权重),这是做参数优化时常用的做法:

1
2
3
4
5
del(system.config.instrument_weights)
system.cache.delete_all_items()
system.config.instruments = ['US5', 'US10']
system.get_instrument_list()
>['US5', 'US10']

上面的代码片段也顺便提醒了一点:如果你打算从“固定系统”(即使用固定的 instrument_weights)切换到“估计系统”(即由系统自行估计权重),那么配置里需要显式给出 instruments 列表。系统不会假设:你在固定系统里 instrument_weights 使用的那一批品种,就等同于你在估计系统里要用的那批品种;如果不显式指定,它会默认使用“所有有数据的品种”。因此你需要显式设置这一配置项,例如:

1
system.config.instruments = list(system.config.instrument_weights.keys())

Always excluded

现在我们来讨论“始终排除的品种”,这些品种在后续的回测和优化中都不会使用。

在默认配置中,有两类品种会被自动排除:

  • duplicated_instruments:重复品种(例如同一指数对应的多种合约规格);
  • ignored_instruments:被明确标记为需要忽略的品种。

当我们调用:

1
system.get_instrument_list()

时,返回值已经在 system.data.get_instrument_list() 的基础上,扣除了这两类品种。

Duplicated instruments

标普 500 是一个典型的 duplicated instrument 示例。这里的 SP500 是 e-mini 合约(在我最初交易时它是唯一使用的合约,所以没有特别加标注),但实际上我们更希望使用 micro 合约。两者的价格数据几乎一模一样。

保留重复数据既没有意义,甚至在某些场景下是危险的——例如在做参数优化或校准时,我们经常对“跨品种平均”或估计相关矩阵,如果把高度相关的两个合约都算进去,就会扭曲结果。

1
2
"SP500_micro" in system.get_instrument_list()
> True

我们可以查看哪些重复品种会被排除:

1
2
system.get_list_of_duplicate_instruments_to_remove()
> ['COPPER-mini', 'CORN_mini', 'CRUDE_W', 'GAS_US', 'GASOILINE_mini', 'GOLD', 'HEATOIL_mini', 'JGB_mini', 'JGB-SGX-mini', 'JPY_micro', 'JPY-SGX-TITAN', 'JPY-SGX', 'KOSPI_mini', 'KRWUSD_mini', 'NASDAQ', 'SILVER_mini', 'SOYBEAN_mini', 'SP500', 'TWD-mini', 'VIX_mini', 'WHEAT_mini']

这些是通过如下配置元素定义的(这里展示的是 defaults.yaml 中的值):

1
2
3
4
system.config.duplicate_instruments['exclude']
>{'copper': 'COPPER-mini', 'corn': 'CORN_mini', 'crude': 'CRUDE_W', 'gas_us': 'GAS_US', 'gasoiline': 'GASOILINE_mini', 'gold': 'GOLD', 'heatoil': 'HEATOIL_mini', 'jgb': ['JGB_mini', 'JGB-SGX-mini'], 'jpy': ['JPY_micro', 'JPY-SGX-TITAN', 'JPY-SGX'], 'kospi': 'KOSPI_mini', 'krwusd': 'KRWUSD_mini', 'nasdaq': 'NASDAQ', 'silver': 'SILVER_mini', 'soybean': 'SOYBEAN_mini', 'sp500': 'SP500', 'twd': 'TWD-mini', 'vix': 'VIX_mini', 'wheat': 'WHEAT_mini'}
system.config.duplicate_instruments['include']
>{'copper': 'COPPER', 'corn': 'CORN', 'crude': 'CRUDE_W_mini', 'gas_us': 'GAS_US_mini', 'gasoiline': 'GASOILINE', 'gold': 'GOLD_micro', 'heatoil': 'HEATOIL', 'jgb': 'JGB', 'jpy': 'JPY', 'kospi': 'KOSPI', 'krwusd': 'KRWUSD', 'nasdaq': 'NASDAQ_micro', 'silver': 'SILVER', 'soybean': 'SOYBEAN', 'sp500': 'SP500_micro', 'twd': 'TWD', 'vix': 'VIX', 'wheat': 'WHEAT'}

如果你愿意,完全可以把两个 S&P 合约的角色对调:

1
2
3
4
5
6
7
system.cache.delete_all_items()
system.config.duplicate_instruments['exclude']['sp500']="SP500_micro"
system.config.duplicate_instruments['include']['sp500']="SP500"
"SP500" in system.get_instrument_list()
> True
"SP500_micro" in system.get_instrument_list()
> False

如果想让这类改动长期生效,可以修改回测配置和/或 private_config.yaml 文件(关于配置的更多说明见后文)。在文档最后部分,我会解释如何判断一对重复品种中哪个更“好”。

Ignored instruments

除了重复品种外,我们还可能有一些“完全不想碰”的品种。这些品种同样不会出现在 get_instrument_list() 的返回结果中。

1
2
3
4
5
6
7
8
"EURIBOR" in system.data.get_instrument_list() ## 如果你使用 CSV 价格,这里可能不会是 True;我本地数据库里有 EURIBOR,但质量不太好
> True
system.config.exclude_instrument_lists['ignore_instruments'] # 来自默认配置
>['EURIBOR']
system.get_list_of_ignored_instruments_to_remove()
>['EURIBOR']
"EURIBOR" in system.get_instrument_list()
False

Excluded for optimisation

在应用上述“始终排除的品种”规则之后得到的品种集合,将在整个回测过程中使用。此时我们仍然可以:

  • 为这些品种生成预测(forecasts);
  • 甚至计算子系统账户曲线(subsystem account curves)。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from systems.provided.futures_chapter15.estimatedsystem import *
system = futures_system()
system.config.instruments
>['SOFR', 'US10', 'EUROSTX', 'MXP', 'CORN', 'V2X']
system.get_instrument_list()
>['CORN', 'SOFR', 'EUROSTX', 'MXP', 'US10', 'V2X'] ## 此时还没有为优化目的排除任何品种
system.portfolio.get_subsystem_position("V2X")
> ....
2021-10-05   -33.749026
2021-10-06   -34.279859
Freq: B, Length: 2314, dtype: float64

不过,V2X 实际上是一个“bad market”,应该在优化中被排除:

1
2
3
4
"V2X" in system.get_list_of_bad_markets()
>True
"V2X" in system.get_list_of_markets_not_trading_but_with_data()
>True
1
2
3
4
5
6
system.portfolio.get_instrument_list(for_instrument_weights=True)
>*** Following instruments are listed as trading_restrictions and/or bad_markets but still included in instrument weight optimisation: ***
['V2X']
This is fine for dynamic systems where we remove them in later optimisation, but may be problematic for static systems
Consider adding to config element allocate_zero_instrument_weights_to_these_instruments
['CORN', 'SOFR', 'EUROSTX', 'MXP', 'US10', 'V2X']

那我们就按提示操作:

1
2
3
4
5
6
7
8
9
system.cache.delete_all_items()
system.config.allocate_zero_instrument_weights_to_these_instruments= ['V2X']
system.portfolio.get_instrument_list(for_instrument_weights=True)
['CORN', 'SOFR', 'EUROSTX', 'MXP', 'US10']
system.portfolio.get_instrument_weights().tail(1)

>           CORN      SOFR      EUROSTX   MXP      US10      V2X
index                                                           
2021-10-06  0.260899  0.188551  0.181449  0.18055  0.188551  0.0

顺带一提,如果我们在使用 1/n 固定标的权重(fixed instrument weights),这个设置同样会让相关品种的权重变成 0:

1
2
3
4
5
system.portfolio.get_raw_fixed_instrument_weights()
>WARNING: No instrument weights  - using equal weights of 0.2000 over all 5 instruments in data
            CORN     SOFR  EUROSTX  MXP  US10  V2X
1972-10-18   0.2      0.2      0.2  0.2   0.2  0.0
2021-10-06   0.2      0.2      0.2  0.2   0.2  0.0

如果你是手工传入一组“显式标的权重”,那就需要自行把对应的 bad market 权重设为 0。

在使用“动态优化”(dynamic optimisation)时,这个过程发生在更靠后的阶段:系统会自动把这些品种作为 reduce_only 约束加入动态优化(这样也就和生产环境保持一致——在生产中相同逻辑是由策略下单器应用的)。一般来说,我们希望:

  • 在“标的权重优化”阶段,仍然把这些品种包括进去(即参与权重估计、发出目标头寸);
  • 但在最终的动态优化阶段,不再给这些品种分配真实交易。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from sysproduction.strategy_code.run_dynamic_optimised_system import *
data =csvFuturesSimData()
config = Config()
system = futures_system(data, config)
>Following instruments are 'duplicate_markets' and will be excluded from sim ...
>Following instruments are marked as 'ignore_instruments': not included: ['EURIBOR']
>Following instruments have restricted trading: optimisation will not trade them ...
>Following instruments are marked as 'bad_markets': optimisation will not trade them ['ALUMINIUM', .... 'V2X']

system.optimisedPositions.get_reduce_only_instruments()
>['US-STAPLES',... 'EU-CHEM']

Untradeable

不可交易的品种通常是因为监管或合规限制。例如对我来说,某些美国股指期货是不能交易的:

1
2
3
4
5
6
7
from systems.provided.futures_chapter15.basesystem import *
config = Config() # 使用默认配置,这样我们知道理论上所有品种都在里面
system = futures_system(config = config)
system.config.exclude_instrument_lists['trading_restrictions']
>['US-DISCRETE', 'US-ENERGY', 'US-FINANCE', 'US-HEALTH', 'US-INDUSTRY', 'US-MATERIAL', 'US-PROPERTY', 'US-REALESTATE', 'US-STAPLES', 'US-TECH', 'US-UTILS']
system.get_list_of_markets_with_trading_restrictions()
>['US-DISCRETE', 'US-ENERGY', 'US-FINANCE', 'US-HEALTH', 'US-INDUSTRY', 'US-MATERIAL', 'US-PROPERTY', 'US-REALESTATE', 'US-STAPLES', 'US-TECH', 'US-UTILS']

Bad markets

所谓 “bad markets”,指的是成本太高或流动性太差的市场(直接叫 bad markets 会更顺口一些,不是吗?)。这些市场理论上可以交易,但我们选择不去碰它们。

1
2
3
4
system.config.exclude_instrument_lists['bad_markets']
['ALUMINIUM', 'BBCOMM', 'CHEESE', 'DJSTX-SMALL', 'EU-BANKS', 'EU-CHEM', 'EU-CONSTRUCTION', 'EU-DIV30', 'EU-FOOD', 'EU-HEALTH', 'EU-INSURE', 'EU-TRAVEL', 'EURIBOR', 'FTSEINDO', 'INR', 'KOSPI_mini', 'KRWUSD', 'LUMBER', 'MILK', 'MILKDRY', 'MSCIASIA', 'NOK', 'OATIES', 'RICE', 'SGD', 'SHATZ', 'US-DISCRETE', 'US-REALESTATE', 'USIRS5', 'V2X']
system.get_list_of_bad_markets()
['ALUMINIUM', 'BBCOMM', 'CHEESE', 'DJSTX-SMALL', 'EU-BANKS', 'EU-CHEM', 'EU-CONSTRUCTION', 'EU-DIV30', 'EU-FOOD', 'EU-HEALTH', 'EU-INSURE', 'EU-TRAVEL', 'EURIBOR', 'FTSEINDO', 'INR', 'KOSPI_mini', 'KRWUSD', 'LUMBER', 'MILK', 'MILKDRY', 'MSCIASIA', 'NOK', 'OATIES', 'RICE', 'SGD', 'SHATZ', 'US-DISCRETE', 'US-REALESTATE', 'USIRS5', 'V2X']

Automatically excluded

还有一种情况:某些品种在整个样本期内持仓始终为 0。最常见的原因是:你在成本上设置了“速度限制”(speed limit),结果导致没有任何交易规则便宜到足以交易该品种。

这类品种会被自动加入“优化权重为 0 的市场”列表。

Customising the list of ‘all instruments’ and ’excluded for optimisation’

如果你在对系统做任何其他操作之前,先执行下面这两个方法调用,就可以精确控制哪些品种会包含在列表中,哪些不会。下面的调用组合会复现默认行为;如果需要定制,只要从这里修改即可。重要:如果你希望影响 instrument_list() 的结果,这两个方法必须按以下顺序调用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
## days_required 参数用于在“删除历史太短的市场”时使用
system.get_instrument_list(
                            remove_duplicates=True,
                            remove_ignored=True,
                            remove_trading_restrictions=False,
                            remove_bad_markets=False,
                            remove_short_history=False,
                            days_required = 750)

system.get_list_of_markets_not_trading_but_with_data(
                                                      remove_duplicates=True,
                                                      remove_ignored=True,
                                                      remove_trading_restrictions=True,
                                                      remove_bad_markets=True,
                                                      remove_short_history=False,
                                                      days_required = 750)

在此基础上,你可以自由修改上述参数来定制:

  • “回测用的全局品种集合”(get_instrument_list);
  • “有数据但不做交易的品种集合”(get_list_of_markets_not_trading_but_with_data)。

Operating in production environment

在生产环境中运行会稍微复杂一些,因为会同时受到多层配置文件的作用、各种约束条件的具体实现方式,以及从数据库中拉取的附加约束等因素的影响。

A note about configuration

在纯回测环境里,配置相对简单:大部分配置项先在 defaults.yaml 中定义,可以被你的 private_config.yaml 覆盖,最后还可以被每个回测自己专用的 backtest.yaml 再次覆盖。

需要特别注意的是:一旦离开生产系统中的“回测阶段”,后续代码就看不到 backtest.yaml 了(毕竟 backtest 配置是针对单个系统的,而生产环境中我们通常只依赖全局参数)。因此在生产环境中,配置优先级是:defaults.yaml,再由 private_config.yaml 覆盖。
生产回测生成最优头寸之后,下游用来生成策略订单并实际执行交易的代码,只会基于 private_config.yamldefaults.yaml 中的配置运行。

除此之外,生产环境还可以从数据库中读取一些额外的约束(例如手工设置的 override),这些约束会和 YAML 配置叠加使用。

Reduce only and other constraints in static systems

在生产环境里,重复品种和被忽略品种的处理方式与回测中完全相同:它们在回测阶段会被直接忽略。
在一个“静态系统”(static system)里,我们通常不会在每次生产回测时都重新做动态权重优化,而是使用事先保存好的标的权重。如果当初生成这些权重时,就已经把某些品种标记为不可交易(untradeable)或 bad markets,那么这些品种在当前权重里就会以 0 权重出现。我们仍然会为它们计算预测和子系统账户曲线,但最终的最优头寸是 0。

当策略订单生成器(strategy order generator)运行时,它会对所有生成出来的订单应用一层 override(覆盖约束)。override 可以按以下几种粒度生效:

  • 针对某个策略;
  • 针对某个品种(跨所有策略);
  • 针对“某个策略 + 某个品种”的组合。

一条 override 可以是下面三种类型之一:

  • 一个介于 0 和 1 之间的乘数,用来乘以目标头寸;乘数为 1 表示“正常交易”,为 0 则表示“把头寸全部平掉”;
  • 一个标志位,表示“只允许减仓”(reduce_only),即只允许使持仓绝对值减少的交易;
  • 一个标志位,表示“完全禁止交易”(don’t trade)。

override 列表会包含:

  • 记录在 override 数据库中的所有 override(可能是上述任意类型);
  • 对于在配置中被标记为 bad、duplicated、ignored 或 untradeable 的品种,系统会自动为其生成 override:通常是 reduce_only;对于 untradeable 品种,则会标记为 don't trade

如果同一个品种同时受多条 override 影响,我们总是采用其中最保守的一条。

在实际使用中,这意味着:你只要修改各类品种分类(bad / duplicated / ignored / untradeable)的配置,系统就会自动做出相应调整。例如,要“逐步退出”某个 bad instrument,可以这样做:

  • 把它加入配置里的 bad instruments 列表;
  • 把该品种的标的权重设为 0(可以一次性设为 0,也可以随时间逐渐降到 0);
  • 生产系统会认为该品种有 reduce_only 标志,并允许执行那些能减小该品种头寸的交易。

反过来,如果想让某个原先的 bad instrument 重新开始交易:

  • 把它从配置中的 bad instruments 列表里移除;
  • 重新优化标的权重,让该品种获得一个正的权重;
  • 生产系统将不再认为它是 reduce_only,并开始为它生成新的交易;
  • 如果是逐步提高权重,我们也会逐步交易到目标持仓。

类似的逻辑同样适用于 ignored 和 duplicated 品种。
需要注意的是:如果你把某个品种标记为 untradeable,而账户里已经持有该品种的仓位,这个仓位会继续保留;同时,系统对“单日最大交易数量”等限制依然有效,因此如果你希望“今天就关掉”一个品种的所有仓位,很可能需要通过交互式订单处理工具手工下单完成。

负责应用这些约束的代码是“策略无关”的:它不会加载具体策略的配置 YAML。也就是说,如果你想修改 bad / duplicated / ignored / untradeable 这些类别的默认定义,需要修改的是 private_config.yaml,而不是某个具体策略的 backtest 配置。

你可以通过 interactive_controls 脚本查看当前所有 override(包括来自配置和数据库的):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
~/pysystemtrade/sysproduction/linux/scripts$ . interactive_controls 

0: Trade limits
1: Position limits
2: Trade control (override)
3: Broker client IDS
4: Process control and monitoring
5: Update configuration

Your choice? <RETURN for EXIT> 2
20: View overrides (configured, and database)
21: Update / add / remove override for strategy in database
22: Update / add / remove override for instrument in database
23: Update / add / remove override for strategy & instrument in database
24: Delete all overrides in database


Your choice? <RETURN for Back> 20
All overrides:

ALUMINIUM Override Reduce only because bad_instrument in config
BBCOMM Override Reduce only because bad_instrument in config
CHEESE Override Reduce only because bad_instrument in config
COPPER-mini Override Reduce only because duplicate_instrument in config
....
V2X Override Reduce only because bad_instrument in config
VIX_mini Override Reduce only because duplicate_instrument in config
WHEAT_mini Override Reduce only because duplicate_instrument in config

你也可以在同一个菜单下新增 / 更新 / 删除数据库中的 trade override。

Reduce only and other constraints in dynamic systems

在“动态系统”(dynamic systems)中,我们会在生产回测给出最优头寸之后,再做一次动态优化,然后才生成订单。这个优化器需要知道哪些品种当前处于 reduce_only 状态,哪些处于 dont_trade 状态;这些信息会从两处合并而来:

  • 配置 YAML(同样不包含 backtest YAML,只看 defaults.yamlprivate_config.yaml);
  • 数据库中记录的各种 override。

理论上,优化器生成的订单在提交给订单生成 / 交易模块时,还会再经过一遍和静态系统相同的约束检查;但由于这些约束已经在优化器阶段被处理过了,这一步通常不会再改变订单。

这种设计还有一个好处:当你把一个品种新标记为 bad / ignored / duplicate,或者把某个品种从这些列表里“赎回”出来时,头寸会通过优化器的成本感知模型逐步调整,而不是一下子跳变。如果你希望“今天就平掉”某个品种的所有头寸,仍然需要手工下单完成。

Deciding which are ‘bad’ markets

接下来讨论:如何判断哪些市场应该被标记为“bad markets”(高成本或低流动性市场)。

在这里,我对 “bad market” 给出一个操作性定义:

  • 每笔交易成本 > 0.01 个 Sharpe 比率单位(SR units);
  • 日均成交量 < 100 手;
  • 日均成交的“风险名义市值”(risk units) < 150 万美元。

我们可以通过交互式控制脚本,生成一份“建议的 bad markets 列表”。

Check slippage costs are accurate

首先要确保滑点成本(slippage costs)的估计是合理的。我们可以利用生产中的真实成交数据和采样到的 bid/ask 价差,对滑点配置做一个“事后校验和更新”。

在交互式控制脚本中,有一个菜单项可以自动更新点差成本配置:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
~/pysystemtrade/sysproduction/linux/scripts$ . interactive_controls 
1: Position limits
2: Trade control (override)
3: Broker client IDS
4: Process control and monitoring
5: Update configuration


Your choice? <RETURN for EXIT> 5
50: Auto update spread cost configuration based on sampling and trades
51: Suggest 'bad' markets (illiquid or costly)
52: Suggest which duplicate market to use


Your choice? <RETURN for Back> 50

选择 50 之后,系统会基于实际交易与采样数据,对当前配置的点差成本做一个建议更新,并打印出类似下面的摘要(这里省略了大部分输出,只保留关键片段):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
New configured slippage value (current 0.172000, default is estimate 0.117354) <RETURN for default 0.11735449352746953> 
ALL VALUES MULTIPLIED BY 1000000.000000 INCLUDING INPUTS!!!!
bid_ask_trades          1.500000
total_trades           -0.100000
bid_ask_sampled         0.179167
weight_trades       97192.224622
weight_samples     578833.693305
weight_config      323974.082073
estimate                0.476278
Configured              0.700000
% Difference      -319603.003188
Name: KRWUSD, dtype: float64
New configured slippage value (current 0.700000, default is estimate 0.476278) <RETURN for default 0.47627789776817986> 

唯一需要留意的小“坑”是:对于点差极小的品种,我们会把所有数值按某个 10 的幂进行缩放,以便打印出更“可读”的数字(懒一点的做法是连 weight_* 字段也一起乘上同样的倍数)。

Get list of bad markets

在确认滑点配置大致合理之后,就可以获取“坏市场”列表了:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
~/pysystemtrade/sysproduction/linux/scripts$ . interactive_controls 
1: Position limits
2: Trade control (override)
3: Broker client IDS
4: Process control and monitoring
5: Update configuration


Your choice? <RETURN for EXIT> 5
50: Auto update spread cost configuration based on sampling and trades
51: Suggest 'bad' markets (illiquid or costly)
52: Suggest which duplicate market to use


Your choice? <RETURN for Back> 51
Maximum SR cost? <RETURN for default 0.01> 
Minimum contracts traded per day? <RETURN for default 100> 
Min risk $m traded per day? <RETURN for default 1.5> 

这个过程会花一点时间来收集相关数据,最后会给出类似这样的输出:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
Add the following to YAML .config under bad_markets heading:

bad_markets:
  - ALUMINIUM
  - BBCOMM
  - CHEESE
  - DJSTX-SMALL
....
  - USIRS5
  - V2X
New bad markets ['FTSEINDO', 'V2X']
Removed bad markets ['US10']

底部的内容给出了“相对于当前配置,新建议加入和移除的 bad markets 列表”。更新配置需要你自己手工把上面的 YAML 片段复制到配置文件里 —— 系统不会自动写入。

虽然这里不会直接展示“为什么某个市场是 bad”,但用于生成这份报告的底层数据和“成本 / 流动性报告”是一致的。

Deciding which duplicate instruments to use

对于重复品种的选择,逻辑与 bad markets 的筛选非常类似:我们会先应用同样的一组过滤条件,确保不会选中一个成本巨高或流动性极差的合约。

同样在交互式脚本中,菜单 52 用于“建议使用哪个重复市场”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
~/pysystemtrade/sysproduction/linux/scripts$ . interactive_controls 
1: Position limits
2: Trade control (override)
3: Broker client IDS
4: Process control and monitoring
5: Update configuration


Your choice? <RETURN for EXIT> 5
50: Auto update spread cost configuration based on sampling and trades
51: Suggest 'bad' markets (illiquid or costly)
52: Suggest which duplicate market to use


Your choice? <RETURN for Back> 52
Maximum SR cost? <RETURN for default 0.01> 
Minimum contracts traded per day? <RETURN for default 100> 
Min risk $m traded per day? <RETURN for default 1.5> 

然后会打印出“推荐使用的重复市场”列表。

在这个简单示例里,我们只对一个市场有数据,而且它不满足任何过滤条件:

1
2
3
4
5
Current list of included markets ['KRWUSD'], excluded markets ['KRWUSD_mini']
              SR_cost  volume_contracts  volume_risk  contract_size
KRWUSD       0.025299              12.0         0.06         5017.0
KRWUSD_mini       NaN               NaN          NaN            NaN
Best market <No good markets>, current included market(s) ['KRWUSD']

下面这个例子中,我们同样只有一个市场有数据,但这次它通过了所有过滤条件,因此被推荐使用:

1
2
3
4
5
Current list of included markets ['HEATOIL'], excluded markets ['HEATOIL_mini']
               SR_cost  volume_contracts  volume_risk  contract_size
HEATOIL       0.002312           18436.0       315.85        17133.0
HEATOIL_mini       NaN               NaN          NaN            NaN
Best market HEATOIL, current included market(s) ['HEATOIL']

再来看一个有两个市场的例子,但只有一个满足过滤条件:

1
2
3
4
5
Current list of included markets ['KOSPI'], excluded markets ['KOSPI_mini']
             SR_cost  volume_contracts  volume_risk  contract_size
KOSPI_mini  0.015313           69366.0       128.40         1851.0
KOSPI       0.006080          209258.0      1944.95         9295.0
Best market KOSPI, current included market(s) ['KOSPI']

下面是几个“多个候选都通过过滤”的例子;我们会选择合约规模最小(contract_size 最小)的那个:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Current list of included markets ['CRUDE_W_mini'], excluded markets ['CRUDE_W']
               SR_cost  volume_contracts  volume_risk  contract_size
CRUDE_W_mini  0.003753            8495.0        61.24         7208.0
CRUDE_W       0.001992          170167.0      1987.92        11682.0
Best market CRUDE_W_mini, current included market(s) ['CRUDE_W_mini']


Current list of included markets ['SP500_micro'], excluded markets ['SP500']
              SR_cost  volume_contracts  volume_risk  contract_size
SP500_micro  0.000858          545179.0       941.33         1727.0
SP500        0.000814          865522.0     14932.25        17252.0
Best market SP500_micro, current included market(s) ['SP500_micro']

Current list of included markets ['GAS_US_mini'], excluded markets ['GAS_US']

              SR_cost  volume_contracts  volume_risk  contract_size
GAS_US_mini  0.003782            4614.0        39.06         8467.0
GAS_US       0.000849           48674.0      1619.18        33266.0
Best market GAS_US_mini, current included market(s) ['GAS_US_mini']

Current list of included markets ['NASDAQ_micro'], excluded markets ['NASDAQ']
               SR_cost  volume_contracts  volume_risk  contract_size
NASDAQ        0.000452          361956.0     11287.52        31185.0
NASDAQ_micro  0.000436          577104.0      1740.20         3015.0
Best market NASDAQ_micro, current included market(s) ['NASDAQ_micro']


Current list of included markets ['GOLD_micro'], excluded markets ['GOLD']
             SR_cost  volume_contracts  volume_risk  contract_size
GOLD_micro  0.001062           22632.0        39.07         1726.0
GOLD        0.000950           84525.0      1457.25        17240.0
Best market GOLD_micro, current included market(s) ['GOLD_micro']