avatar


量化投资平台PTrade入门

简介

PTrade,由恒生电子开发的量化软件。

在右上角的"帮助-帮助文档"处,可以查看除量化之外的文档。

帮助-帮助文档

在量化模块的帮助处,可以查看和量化相关的文档。

量化-帮助

在工具模块中,有一些预设的策略。

工具模块

但是,需要注意的是,在工具模块中的策略,运行在本地;在量化中的策略,运行在券商的云端。

这也是PTrade和QMT的区别之一,PTrade和QMT都是面向股票场景的量化投资平台。

  • PTrade中的策略是运行在券商的云端服务器。
  • QMT中的策略是运行在本地终端。

关于QMT,可以参考《量化投资平台QMT入门》

策略

结构

在PTrade中,有且仅有一种结构,如图所示。

策略结构

  • initialize,只会在策略启动的时候,被执行一次。
    例如,tt日启动策略,会在启动的时候运行initialize;之后即使在t+1t+1日,也不会再执行initialize
  • before_trading_start,每个交易日都会被执行。
    例如,tt日启动策略,tt日会执行before_trading_startt+1t+1日会再次执行before_trading_start
  • 盘中相关的方法:
    • handle_datatick_data,按照其固定的周期运行。
    • on_order_responseon_trade_response两个函数,不受其运行周期控制,是回调函数,被回调(触发),就会运行。
    • run_intervalrun_daily是定时器函数
      • run_interval可以指定间隔秒(最快3秒)运行,也可以作为盘中函数。
      • run_daily在进行开盘集合竞价的时候会用到。
  • initializehandle_data是两个必须有的函数,其他可选。
    如果我们不需要在这两个函数中执行任何操作,可以采取如下的方式
    1
    2
    def handle_data(context, data):
    pass

handle_data

通过data获取行情

handle_data方法有两个入参,contextdata。我们可以在handle_data方法中,直接读取data中的数据,这样获取数据会更快。

data,字典(dict),key是标的代码,value是当时的SecurityUnitData对象,存放当前周期的数据。data中的数据都是没有复权的数据。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)

def handle_data(context, data):
log.info(data[g.security])
log.info(data[g.security]['dt'])
log.info(data[g.security]['open'])
log.info(data[g.security]['close'])
log.info(data[g.security]['price'])
log.info(data[g.security]['low'])
log.info(data[g.security]['high'])
log.info(data[g.security]['volume'])
log.info(data[g.security]['money'])

运行结果:

1
2
3
4
5
6
7
8
9
10
2023-08-02 15:29:09 开始运行回测, 策略名称: 测试
2023-06-29 15:00:00 - INFO - BarData(symbol: '600570.XSHG', name: '恒生电子', datetime: datetime.datetime(2023, 6, 29, 15, 0))
2023-06-29 15:00:00 - INFO - 2023-06-29 00:00:00+00:00
2023-06-29 15:00:00 - INFO - 45.06
2023-06-29 15:00:00 - INFO - 45.3
2023-06-29 15:00:00 - INFO - 45.3
2023-06-29 15:00:00 - INFO - 43.6
2023-06-29 15:00:00 - INFO - 46.16
2023-06-29 15:00:00 - INFO - 36922573.0
2023-06-29 15:00:00 - INFO - 1656880800.0

关于data的歧义

在官方文档中,关于data有如下的论述。
“为了加速,data中的数据只包含股票池中所订阅标的的信息,可使用data[security]的方式来获取当前周期对应的标的信息;”

data中的数据只包含股票池中所订阅标的的信息

根据这个理解,只有在set_universe中的股票才能获取。
在下例中,我没有进行set_universe,却可以获取恒生电子(在g.security中)和浦发银行(不在g.security中)。

没有进行set_universe

实际上,应该是:

为了加速,data中的数据只包含股票池中所订阅标的的信息。
若想获取股票池外的股票信息可以使用data[security]的方式来获取。

正确的论述-1

我们可以验证一下,确实如此。直接打印data,内容是BarDict(600570.SS)600570.SS是我们通过set_universe订阅过的。

正确的论述-2

handle_data被调用的时间

在有些资料中,说日线级别中的handle_data:在回测中,是每天15:00执行;但是在交易中,需要第二天执行。

实际上,关于该部分,在官方文档中有如下的论述:

在交易环境中,取决于券商实际配置时间

默认是14:50

在交易环境中,取决于券商实际配置时间,默认是14:50。

如果不清楚券商配置的时间,或者想自定义日线策略的交易时间。可以直接采用分钟级别的,每分钟调用一次handle_data
然后在handle_data内部做逻辑判断,判断时间。有三种思路:

  1. 用一个全局变量记录handle_data的被调用的次数,当该值等于某个设定的值,说明到了预定的时间,然后执行后续逻辑。
    这种方法需要注意,策略盘中启动的话,记录可能不准确。
  2. 计算K线的长度,如果K线的长度等于某个设定的值,说明到了预定的时间,然后执行后续逻辑。
    关于K线长度的计算,可以参考本文最后的"一阳穿三线(案例)"。
  3. 或者直接通过代码获取当前时间,进行判断。
    PTrade的策略是运行在券商的服务器,通过代码获取当前时间,获取的是券商服务器的本地时间。这种情况需要注意,券商服务器的本地时间需要和交易所的时间同步。

handle_data中的data的内容

有些资料会说,handle_data中的data,在回测环境是一分钟的K线,在交易环境是行情快照。
我发现实际不是。

如图,是在交易环境中,我们看到有最高价、最低价这些。
即,在交易环境中也是K线。

都是一分钟的K线

特别的,我们可以看到,时间是没有"+8"的。

tick_data(交易)

tick_data,仅交易模块可用。

注意:

  1. 该函数中的data和handle_data函数中的data是不一样的,不要搞混了。
  2. 当调用set_parameters()并设置tick_data_no_l2="1"时,参数data中将不包含逐笔委托、逐笔成交字段。当券商有l2行情、并且我们不需要时,配置该参数可提升data取速。
    关于set_parameters()的用法,可以参考官方文档的介绍,一般放在initialize中执行。

关于如何获取买一价、买二价等,参考官方文档。
其中一个重要的函数是eval,关于eval,可以参考《基于Python的后端开发入门:1.基础语法》的"数据类型转换"部分。

有些资料说,在该函数中,只可以调用order_tick。实际上该部分已经修改了。

不是只可以调用order_tick

回测

添加策略

点击如图所示处的+,添加策略,在弹框处输入策略名称和业务类型。

添加策略

与在QMT中一样,新添加的策略会有一些预设的代码。

功能介绍

回测

  • 处,可以选择回测的时间范围、初始资金、业绩基准、回测周期等。
  • 点击回测,进行回测。
  • 处,可以查看日志。
  • 可以查看回测结果,从上到下,包括:
    • 收益、基准收益、Alpha比率等
    • 收益率曲线
    • 每日盈亏
    • 当日买卖
  • 点击处,可以查看回测详情。

点击回测详情后,分别可以查看汇总记录和某一天的记录,点击左侧选择具体看哪一天。

回测详情

性能分析

在回测期间,可以勾线"性能分析"。

性能分析-1

然后在"回测详情"中,勾选Tab页,可以看到每个函数包括每个语句的运行时间。

性能分析-2

交易

新增交易

点击新增,即可新增交易,然后在弹窗的下拉框,选择回测中的策略。

确认后,交易会立即开始运行。

新增

交易频率

交易频率

  1. 日级别
    如果回测中的策略是日级别的,那么基于其新增的交易,也会是日级别的。
  2. 分钟级别
    如果回测中的策略是分钟级别的,那么基于其新增的交易,也会是分钟级别的。
  3. tick级别
    对于tick级别策略,基于tick_data或者run_interval实现,不用在意页面显示的策略是日线或者分钟。

需要注意的是,对于日线级别的,在回测和交易中,handle_data被调用的时间是不一样的,这可能会导致我们的策略在回测和交易上,存在一些逻辑差异。
具体可以参考上文关于handle_data的讨论。

数据

股票池

全市场股票池

get_Ashares,获取指定日期A股代码列表。

按指数获取股票池

get_index_stocks

get_index_stocks,获取指数成分股。

关于尾缀的歧义

根据官方文档的说法,尾缀必须是.SS

尾缀必须是

该部分存在表述不清。

我们可以用创业板指(399006)试一下,发现不是这样的。

创业板指(399006)

特别的,对于沪深300,我们会发现只支持上交所的沪深300指数。

沪深300

总之,建议,对于该部分,进行足够的验证和测试。

按行业获取股票池

get_industry_stocks,获取行业成份股。

行情数据

handle_data和tick_data

在上文我们讨论过handle_datatick_data,这两个方法都自带入参datadata中就是行情数据。
除了上述两种方法,这里再讨论三种方法:

  1. get_history
  2. get_price
  3. get_snapshot

get_history

get_history,获取历史行情。

需要注意的是:

  1. 入数fq,数据复权选项,支持包括:pre(前复权)、post(后复权)、dypre(动态前复权)和None(不复权)。
    dypre(动态前复权),是站在回测日的当天,以回测日之前的除权因子,进行前复权,不考虑回测日之后的除权因子。
    相比pre(前复权),dypre(动态前复权)更贴合真实交易场景。
  2. 入参is_dict,返回是否是字典(dict)格式,True(是),False(不是);
    返回为字典格式取数速度相对较快;
  3. 针对停牌场景,没有跳过停牌的日期,无论对单只股票还是多只股票进行调用,时间轴均为二级市场交易日日历,停牌时使用停牌前的数据填充,成交量为0,日K线可使用成交量为0的逻辑进行停牌日过滤。
  4. 数据返回内容可以包括当前周期的数据,基于include字段。

获取单支股票,is_dict=True,每日周期,回测,示例代码:

1
2
3
4
5
6
7
8
9
def initialize(context):
# 初始化策略
g.security = "600570.SS"
set_universe(g.security)

def handle_data(context, data):
his = get_history(count=5, frequency='1d', fq=None, include=False, is_dict=True)
log.info(his)
log.info(his['600570.SS']['close'])

运行结果:

1
2
3
4
5
6
7
8
9
2023-08-02 10:48:16 开始运行回测, 策略名称: 测试
2023-06-29 15:00:00 - INFO - OrderedDict([('600570.SS', array(
[(20230620, 47.01, 50.81, 46.68, 50.81, 81173649.0, 4082276600.0, 50.81, 46.19, 50.81, 41.57, 0),
(20230621, 49.71, 51.08, 49.24, 49.4, 56965636.0, 2852368100.0, 49.4, 50.81, 55.89, 45.73, 0),
(20230626, 48.5, 48.67, 44.87, 45.23, 48889899.0, 2276332400.0, 45.23, 49.4, 54.34, 44.46, 0),
(20230627, 45.11, 46.15, 44.26, 44.88, 37718794.0, 1695806300.0, 44.88, 45.23, 49.75, 40.71, 0),
(20230628, 45.8, 45.8, 42.89, 44.4, 37254156.0, 1638135100.0, 44.4, 44.88, 49.37, 40.39, 0)],
dtype=[('datetime', '<i8'), ('open', '<f8'), ('high', '<f8'), ('low', '<f8'), ('close', '<f8'), ('volume', '<f8'), ('money', '<f8'), ('price', '<f8'), ('preclose', '<f8'), ('high_limit', '<f8'), ('low_limit', '<f8'), ('unlimited', '<i8')]))])
2023-06-29 15:00:00 - INFO - [ 50.81 49.4 45.23 44.88 44.4 ]

获取多只股票,is_dict=True,每日周期,回测,示例代码:

1
2
3
4
5
6
7
8
9
10
def initialize(context):
# 初始化策略
g.security = ['600570.SS','600571.SS']
set_universe(g.security)

def handle_data(context, data):
his = get_history(count=5, frequency='1d', fq=None, include=False, is_dict=True)
log.info(his)
log.info(his['600570.SS']['close'])
log.info(his['600571.SS']['close'])

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2023-08-02 10:53:39 开始运行回测, 策略名称: 测试
2023-06-29 15:00:00 - INFO - OrderedDict([
('600570.SS', array([ (20230620, 47.01, 50.81, 46.68, 50.81, 81173649.0, 4082276600.0, 50.81, 46.19, 50.81, 41.57, 0),
(20230621, 49.71, 51.08, 49.24, 49.4, 56965636.0, 2852368100.0, 49.4, 50.81, 55.89, 45.73, 0),
(20230626, 48.5, 48.67, 44.87, 45.23, 48889899.0, 2276332400.0, 45.23, 49.4, 54.34, 44.46, 0),
(20230627, 45.11, 46.15, 44.26, 44.88, 37718794.0, 1695806300.0, 44.88, 45.23, 49.75, 40.71, 0),
(20230628, 45.8, 45.8, 42.89, 44.4, 37254156.0, 1638135100.0, 44.4, 44.88, 49.37, 40.39, 0)],
dtype=[('datetime', '<i8'), ('open', '<f8'), ('high', '<f8'), ('low', '<f8'), ('close', '<f8'), ('volume', '<f8'), ('money', '<f8'), ('price', '<f8'), ('preclose', '<f8'), ('high_limit', '<f8'), ('low_limit', '<f8'), ('unlimited', '<i8')])),
('600571.SS', array([ (20230620, 10.14, 10.42, 9.93, 10.2, 21521645.0, 219347210.0, 10.2, 9.95, 10.95, 8.96, 0),
(20230621, 10.2, 10.24, 9.78, 9.78, 13551507.0, 134478250.0, 9.78, 10.2, 11.22, 9.18, 0),
(20230626, 9.69, 9.78, 9.14, 9.15, 13273234.0, 124047434.0, 9.15, 9.78, 10.76, 8.8, 0),
(20230627, 9.22, 9.33, 9.1, 9.21, 7798814.0, 71853092.0, 9.21, 9.15, 10.07, 8.24, 0),
(20230628, 9.2, 9.24, 8.78, 9.06, 10663700.0, 95340537.0, 9.06, 9.21, 10.13, 8.29, 0)],
dtype=[('datetime', '<i8'), ('open', '<f8'), ('high', '<f8'), ('low', '<f8'), ('close', '<f8'), ('volume', '<f8'), ('money', '<f8'), ('price', '<f8'), ('preclose', '<f8'), ('high_limit', '<f8'), ('low_limit', '<f8'), ('unlimited', '<i8')]))])
2023-06-29 15:00:00 - INFO - [ 50.81 49.4 45.23 44.88 44.4 ]
2023-06-29 15:00:00 - INFO - [ 10.2 9.78 9.15 9.21 9.06]

根据PTrade官方文档的介绍,is_dict=True,在取数的时候会更快,本文讨论的也都是is_dict=True的情况。关于is_dict=False,可以参考官方文档。

get_price

使用方法

get_price,获取历史数据。

需要注意的是:

  1. start_datecount必须且只能选择输入一个,不能同时输入或者同时都不输入。
    count只在某些频率下有效,具体参考官方文档。
    有某些频率,不支持start_dateend_date组合的入参,只支持end_datecount组合的入参形式,具体参考官方文档。
  2. 针对停牌场景,没有跳过停牌的日期,无论对单只股票还是多只股票进行调用,时间轴均为二级市场交易日日历,停牌时使用停牌前的数据填充,成交量为0,日K线可使用成交量为0的逻辑进行停牌日过滤。
  3. 数据返回内容不包括当天数据。

security的参数为字符串

security的参数为字符串,示例代码:

1
get_price(security='600570.SS',start_date='20170201',end_date='20170213',frequency='1d')

运行结果:

1
2
3
4
5
6
7
8
             open  close   high    low      volume  price        money  preclose  high_limit  low_limit  unlimited
2017-02-03 44.47 43.90 44.50 43.58 4418325.0 43.90 193895820.0 44.26 48.69 39.83 0.0
2017-02-06 43.91 44.10 44.30 43.66 4428487.0 44.10 194979290.0 43.90 48.29 39.51 0.0
2017-02-07 44.05 43.52 44.07 43.34 5649251.0 43.52 246776480.0 44.10 48.51 39.69 0.0
2017-02-08 43.59 44.59 44.78 43.53 12570233.0 44.59 557883600.0 43.52 47.87 39.17 0.0
2017-02-09 44.74 44.74 45.28 44.39 9240223.0 44.74 413875390.0 44.59 49.05 40.13 0.0
2017-02-10 44.80 44.62 44.98 44.41 8097465.0 44.62 361757300.0 44.74 49.21 40.27 0.0
2017-02-13 44.32 44.89 45.98 44.02 14931596.0 44.89 672360490.0 44.62 49.08 40.16 0.0

security的参数为列表

security的参数为列表,示例代码:

1
get_price(security=['600570.SS'],start_date='20170201',end_date='20170213',frequency='1d')

运行结果:

1
2
3
4
5
6
Out[12]:
<class 'pandas.core.panel.Panel'>
Dimensions: 11 (items) x 7 (major_axis) x 1 (minor_axis)
Items axis: open to unlimited
Major_axis axis: 2017-02-03 00:00:00 to 2017-02-13 00:00:00
Minor_axis axis: 600570.SS to 600570.SS

在解析时,注意iters为行情字段('open''close'等),示例代码:

1
2
p = get_price(security=['600570.SS'],start_date='20170201',end_date='20170213',frequency='1d')
print(p['close'])

运行结果:

1
2
3
4
5
6
7
8
            600570.SS
2017-02-03 43.90
2017-02-06 44.10
2017-02-07 43.52
2017-02-08 44.59
2017-02-09 44.74
2017-02-10 44.62
2017-02-13 44.89

如果需要将items索引由行情字段转换成股票代码,可以通过swapaxes方法转换。示例代码:

1
p = p.swapaxes("minor_axis", "items")

关于Panel的更多,可以参考《用Python分析数据的方法和技巧:3.pandas》的"Panel"部分。

不包含当天的

例如,我们在20230803这一天,获取这一天的分钟数据。示例代码:

1
2
3
df = get_price(security='600570.SS',start_date='20230803',end_date='20230803',frequency='1m')
print(df.shape)
print(df)

运行结果:

1
2
3
4
(0, 7)
Empty DataFrame
Columns: [open, close, high, low, volume, price, money]
Index: []

get_snapshot

get_snapshot,取行情快照,仅在交易模块可用。
get_snapshot通常和run_interval配合;run_interval的作用是按设定周期定时运行;这样可以定时获取行情快照。

关于get_snapshotrun_interval的具体使用方法,参考官方文档。

一个需要注意的点:

get_snapshot

财报数据

get_fundamentals

通过get_fundamentals,可以获取财报数据。
需要注意的是:

  1. 该接口可能存在因网络拥堵等原因导致应答失败的情况,如果返回数据结果为空请多次尝试,策略中请增加保护机制。
  2. 该接口有流量限制,具体参考官方文档。
  3. 入参中有一个字段report_types,财报类型。
    如果为年份查询模式(start_year/end_year),不输入report_types返回当年可查询到的全部类型财报。
    如果为日期查询模式(date),不输入report_types返回距离指定日期最近( 不含指定日期 )一份财报(str)。

例子

日期查询模式

日期查询模式(date),不输入report_types返回距离指定日期最近( 不含指定日期 )一份财报(str)。示例代码:

1
2
3
df = get_fundamentals('600000.SS','balance_statement',date='20211231')
print(df.shape)
print(df)

运行结果:

1
2
3
4
(1, 115)
longterm_equity_invest sold_buyback_secu_proceeds interest_receivable treasury_stock other_current_assets shortterm_loan surplus_reserve_fund long_defer_income impawned_loan specific_reserves hold_for_sale_assets retained_profit non_current_liability_in_one_year interest_payable intangible_assets capital_reserve_fund bonds_payable other_equityinstruments longterm_loan independence_liability sub_issue_secu_proceeds insurance_receivables other_payable non_current_asset_in_one_year total_non_current_assets reinsurance_receivables refundable_deposit accounts_payable other_composite_income deferred_tax_liability constru_in_process client_deposit total_non_current_liability deposit_in_interbank cash_equivalents advance_insurance receivable_life_r deposit inventories advance_payment se_without_mi secu_abbr account_receivable investment_property trading_assets good_will fixed_deposit receivable_unearned_r settlement_provi hold_to_maturity_investments fixed_assets_liquidation compensation_payable insurer_impawn_loan receivable_subrogation_fee receivable_lt_health_r deposits_received specific_account_payable lend_capital company_type other_receivable independence_account_assets trading_liability salaries_payable refundable_capital_deposit taxs_payable notes_payable dividend_payable bought_sellback_assets biological_assets long_deferred_expense commission_payable derivative_liability deposit_of_interbank other_non_current_liability loan_and_advance construction_materials oil_gas_assets total_shareholder_equity minority_interests long_salaries_pay end_date publ_date proxy_secu_proceeds outstanding_claim_reserve other_liability other_current_liability borrowing_capital insurer_deposit_investment bill_receivable fixed_assets derivative_assets paidin_capital receivable_claims_r development_expenditure deferred_tax_assets advance_receipts r_metal total_current_assets estimate_liability other_assets policy_dividend_payable reinsurance_payables longterm_receivable_account client_provi longterm_account_payable dividend_receivable life_insurance_reserve foreign_currency_report_conv_diff ordinary_risk_reserve_fund other_non_current_assets unearned_premium_reserve total_current_liability borrowing_from_centralbank lt_health_insurance_lr seat_costs
secu_code
600000.SS 2.801000e+09 1.790510e+11 NaN NaN NaN NaN 1.592920e+11 NaN NaN NaN NaN 1.849380e+11 NaN NaN 1.051600e+10 8.176100e+10 1.279874e+12 1.126910e+11 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 3.598000e+09 685000000.0 NaN NaN NaN 1.405880e+11 3.914360e+11 NaN NaN 4.523782e+12 NaN NaN 6.625280e+11 浦发银行 NaN NaN 5.119040e+11 6.981000e+09 NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN 2.686430e+11 2 NaN NaN 2.942600e+10 1.313600e+10 NaN 2.429500e+10 NaN NaN 1.781000e+09 NaN NaN NaN 2.809000e+10 8.520490e+11 NaN 4.675327e+12 NaN NaN 6.706230e+11 8.095000e+09 NaN 2021-09-30 2021-10-30 NaN NaN 3.885600e+10 NaN 1.770230e+11 NaN NaN NaN 3.435600e+10 2.935200e+10 NaN NaN 5.265900e+10 NaN 1.001800e+10 NaN 6.155000e+09 1.281620e+11 NaN NaN NaN NaN NaN NaN NaN NaN 9.089600e+10 NaN NaN NaN 2.332850e+11 NaN NaN

年份查询模式

年份查询模式(start_year/end_year参数模式),返回数据类型为pandas.Panel类型,索引为股票代码。不输入report_types返回当年可查询到的全部类型财报。示例代码:

1
2
p = get_fundamentals('600000.SS', 'balance_statement', start_year='2021', end_year='2021')
print(p['600000.SS'])

运行结果:

1
2
3
4
5
6
           account_receivable accounts_payable advance_insurance advance_payment advance_receipts bill_receivable biological_assets bonds_payable borrowing_capital borrowing_from_centralbank bought_sellback_assets capital_reserve_fund cash_equivalents client_deposit client_provi commission_payable company_type compensation_payable constru_in_process construction_materials deferred_tax_assets deferred_tax_liability      deposit deposit_in_interbank deposit_of_interbank deposits_received derivative_assets derivative_liability development_expenditure dividend_payable dividend_receivable estimate_liability fixed_assets fixed_assets_liquidation fixed_deposit foreign_currency_report_conv_diff  good_will hold_for_sale_assets hold_to_maturity_investments impawned_loan independence_account_assets independence_liability insurance_receivables insurer_deposit_investment insurer_impawn_loan intangible_assets interest_payable interest_receivable inventories investment_property lend_capital life_insurance_reserve loan_and_advance long_defer_income long_deferred_expense long_salaries_pay longterm_account_payable longterm_equity_invest longterm_loan longterm_receivable_account lt_health_insurance_lr minority_interests non_current_asset_in_one_year non_current_liability_in_one_year notes_payable oil_gas_assets ordinary_risk_reserve_fund other_assets other_composite_income other_current_assets other_current_liability other_equityinstruments other_liability other_non_current_assets other_non_current_liability other_payable other_receivable outstanding_claim_reserve paidin_capital policy_dividend_payable proxy_secu_proceeds   publ_date     r_metal receivable_claims_r receivable_life_r receivable_lt_health_r receivable_subrogation_fee receivable_unearned_r refundable_capital_deposit refundable_deposit reinsurance_payables reinsurance_receivables retained_profit salaries_payable se_without_mi seat_costs secu_abbr settlement_provi shortterm_loan sold_buyback_secu_proceeds specific_account_payable specific_reserves sub_issue_secu_proceeds surplus_reserve_fund taxs_payable total_current_assets total_current_liability total_non_current_assets total_non_current_liability total_shareholder_equity trading_assets trading_liability treasury_stock unearned_premium_reserve
end_date
2021-03-31 NaN NaN NaN NaN NaN NaN NaN 1.21544e+12 1.73158e+11 2.61649e+11 4.314e+09 8.1761e+10 4.40796e+11 NaN NaN NaN 2 NaN NaN NaN 5.461e+10 6.88e+08 4.24195e+12 1.68069e+11 1.13254e+12 NaN 5.7962e+10 4.9915e+10 NaN NaN NaN 5.625e+09 NaN NaN NaN NaN 6.981e+09 NaN NaN NaN NaN NaN NaN NaN NaN 1.0511e+10 NaN NaN NaN NaN 2.10822e+11 NaN 4.59194e+12 NaN NaN NaN NaN 2.432e+09 NaN NaN NaN 7.776e+09 NaN NaN NaN NaN 7.9661e+10 9.956e+10 4.09e+09 NaN NaN 1.12691e+11 4.205e+10 NaN NaN NaN NaN NaN 2.9352e+10 NaN NaN 2021-04-30 5.6257e+10 NaN NaN NaN NaN NaN NaN NaN NaN NaN 2.05394e+11 1.2085e+10 6.55689e+11 NaN 浦发银行 NaN NaN 2.01934e+11 NaN NaN NaN 1.4274e+11 3.4294e+10 NaN NaN NaN NaN 6.63465e+11 4.86378e+11 2.4156e+10 NaN NaN
2021-06-30 NaN NaN NaN NaN NaN NaN NaN 1.29486e+12 1.35839e+11 2.66905e+11 1.19259e+11 8.1761e+10 4.14587e+11 NaN NaN NaN 2 NaN NaN NaN 4.7629e+10 8.13e+08 4.462e+12 1.70565e+11 1.01582e+12 NaN 4.5839e+10 4.0107e+10 NaN NaN NaN 5.975e+09 3.5933e+10 0 NaN NaN 6.981e+09 NaN NaN NaN NaN NaN NaN NaN NaN 1.05e+10 NaN NaN NaN NaN 2.19798e+11 NaN 4.64334e+12 NaN NaN NaN NaN 2.554e+09 NaN NaN NaN 7.878e+09 NaN NaN NaN NaN 9.0865e+10 1.23538e+11 3.932e+09 NaN NaN 1.12691e+11 5.8164e+10 NaN NaN NaN NaN NaN 2.9352e+10 NaN NaN 2021-08-28 6.208e+09 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.7469e+11 1.322e+10 6.52583e+11 NaN 浦发银行 NaN NaN 1.16421e+11 NaN NaN NaN 1.59292e+11 1.9711e+10 NaN NaN NaN NaN 6.60461e+11 4.82527e+11 2.4944e+10 NaN NaN
2021-09-30 NaN NaN NaN NaN NaN NaN NaN 1.27987e+12 1.77023e+11 2.33285e+11 1.781e+09 8.1761e+10 3.91436e+11 NaN NaN NaN 2 NaN NaN NaN 5.2659e+10 6.85e+08 4.52378e+12 1.40588e+11 8.52049e+11 NaN 3.4356e+10 2.809e+10 NaN NaN NaN 6.155e+09 NaN NaN NaN NaN 6.981e+09 NaN NaN NaN NaN NaN NaN NaN NaN 1.0516e+10 NaN NaN NaN NaN 2.68643e+11 NaN 4.67533e+12 NaN NaN NaN NaN 2.801e+09 NaN NaN NaN 8.095e+09 NaN NaN NaN NaN 9.0896e+10 1.28162e+11 3.598e+09 NaN NaN 1.12691e+11 3.8856e+10 NaN NaN NaN NaN NaN 2.9352e+10 NaN NaN 2021-10-30 1.0018e+10 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.84938e+11 1.3136e+10 6.62528e+11 NaN 浦发银行 NaN NaN 1.79051e+11 NaN NaN NaN 1.59292e+11 2.4295e+10 NaN NaN NaN NaN 6.70623e+11 5.11904e+11 2.9426e+10 NaN NaN
2021-12-31 NaN NaN NaN NaN NaN NaN NaN 1.31712e+12 1.82697e+11 2.36317e+11 1.17e+08 8.1762e+10 4.20996e+11 NaN NaN NaN 2 NaN NaN NaN 5.8962e+10 6.38e+08 4.46361e+12 1.25836e+11 9.24078e+11 NaN 3.3773e+10 2.9528e+10 NaN NaN NaN 6.275e+09 3.8708e+10 0 NaN NaN 6.981e+09 NaN NaN NaN NaN NaN NaN NaN NaN 1.0538e+10 NaN NaN NaN NaN 3.07945e+11 NaN 4.69095e+12 NaN NaN NaN NaN 2.819e+09 NaN NaN NaN 8.211e+09 NaN NaN NaN NaN 9.0993e+10 9.8494e+10 2.821e+09 NaN NaN 1.12691e+11 3.9033e+10 NaN NaN NaN NaN NaN 2.9352e+10 NaN NaN 2022-04-28 1.3151e+10 NaN NaN NaN NaN NaN NaN NaN NaN NaN 1.93096e+11 1.4865e+10 6.70007e+11 NaN 浦发银行 NaN NaN 1.74219e+11 NaN NaN NaN 1.59292e+11 3.0429e+10 NaN NaN NaN NaN 6.78218e+11 5.26034e+11 3.128e+10 NaN NaN

publ_date

特别的,我们可以看看publ_date这个字段。示例代码:

1
2
p = get_fundamentals('600000.SS', 'balance_statement', start_year='2021', end_year='2021')
print(p['600000.SS']['publ_date'])

运行结果:

1
2
3
4
5
6
end_date
2021-03-31 2021-04-30
2021-06-30 2021-08-28
2021-09-30 2021-10-30
2021-12-31 2022-04-28
Name: publ_date, dtype: object

我们看到,publ_date的数据,一般晚于财报的日期。
这个在回测过程中,需要特别注意。在回测中,获取的财报数据,一定要根据publ_date进行一次筛选,否则会导致"数据泄漏",回测结果不准确。

账户信息

账户信息通过context上下文对象获取,在官方文档,首字母是大写的,实际应该小写。

context

context对象的内容很多,具体可以参考官方文档。

示例代码:

1
2
3
4
5
def initialize(context):
pass

def handle_data(context, data):
log.info(context.portfolio)

运行结果:

1
2
2023-08-02 14:59:34 开始运行回测, 策略名称: 测试
2023-06-29 15:00:00 - INFO - <Portfolio {'returns': 0.0, 'positions': {}, 'pnl': 0.0, 'positions_value': 0, 'capital_used': 0, 'start_date': datetime.date(2023, 6, 29), 'cash': 100000.0, 'portfolio_value': 100000.0}>

持仓信息

通过get_position()可以获取一个position类(持仓类)的对象。
关于get_position()方法和position类(持仓类)的对象,都可以参考官方文档。

示例代码:

1
2
3
4
5
6
7
def initialize(context):
g.security = '600570.SS'
set_universe(g.security)

def handle_data(context, data):
position = get_position(g.security)
log.info(position)

运行结果:

1
2
2023-08-02 15:03:51 开始运行回测, 策略名称: 测试
2023-06-29 15:00:00 - INFO - <Position {'enable_amount': 0, 'cost_basis': 0, 'business_type': 'stock', 'sid': '600570.SS', 'amount': 0, 'last_sale_price': 45.299999999999997}>

下单

指定数量买卖

order,按指定数量买卖。

注意:

  1. 如果是卖出的话,amount填负数。
  2. 回测场景下,amount有最小下单数量校验;交易场景接口不做amount校验,直接报柜台。
  3. 交易场景如果limit_price字段不入参,系统会默认用行情快照数据最新价报单,如果行情快照获取失败会导致委托失败,系统会在日志中增加提醒。

指定持仓数量买卖

order_target,指定持仓数量买卖。

该接口用于买卖股票,直到持有的股票最终数量达到指定的amount

需要注意的是,在交易中谨慎使用该接口,因为容易导致重复下单。具体原因如下:

  • 柜台返回持仓数据体现当日变化(由柜台配置决定):交易场景中持仓信息同步有时滞,一般在6秒左右,假如在这6秒之内连续下单两笔或更多order_target委托,由于持仓数量不会瞬时更新,会造成重复下单。
  • 柜台返回持仓数据体现当日变化(由柜台配置决定):第一笔委托未完全成交,如果不对第一笔做撤单再次order_target相同的委托目标数量,引擎不会计算包括在途的总委托数量,也会造成重复下单。
  • 柜台返回持仓数据不体现当日变化(由柜台配置决定):这种情况下持仓数量只会一天同步一次,必然会造成重复下单。

所以,如果需要在交易场景使用该接口,首先要确定券商柜台的配置,是否实时更新持仓情况,其次需要增加订单和持仓同步的管理,来配合order_target使用。

指定金额买卖

order_value,指定金额买卖。

指定持仓市值买卖

order_target_value,指定持仓市值买卖。

注意!该函数同样存在可能造成重复下单,原因也相同。建议在交易中谨慎使用该接口。

order_market

order_market,按市价进行委托。

关于order_market的具体用法,可以参考官方文档。
关于什么是市价委托,可以参考《金融产品与金融市场:A股的交易规则》的"申报方式"部分,需要注意市价委托的适用时间范围。

order_tick

order_tick,tick行情触发买卖。
能且仅能在tick_data中使用。

获取订单

  1. get_open_orders,获取未完成订单。
    1
    get_open_orders(security=None)
  2. get_order,获取指定订单。
    1
    get_order(order_id)
  3. get_orders,获取全部订单(策略内所有订单)。
    1
    get_orders(security=None)
  4. get_all_orders,获取账户当日所有订单(包含非本交易的订单记录)。
    1
    get_all_orders(security=None)

撤单

cancel_order,撤单。

在PTrade中,订单状态有:'0'(未报)'1'(待报)'2'(已报)'3'(已报待撤)'4'(部成待撤)'5'(部撤)'6'(已撤)'7'(部成)'8'(已成)'9'(废单)'+'(已受理)'-'(已确认)'V'(已确认)

其中,可以进行撤单的订单状态有:'2'(已报)'7'(部成)

委托下单时间

在QMT和TqSdk中,有所谓的"K线走完(Bar结束)下单"和"立即下单"。
这个在PTrade中不存在,会直接报到柜台。

直接报柜台

工作经验

全局对象

在使用全局对象的时候,一定要记住,前面要加g.

获取股票池

在上文,我们讨论过:

  • initialize,只会在策略启动的时候,被执行一次。
  • before_trading_start,每天都会被执行。

所以我们获取股票池(获取全市场股票、获取没有停牌的股票、获取指数的成分股)等,尤其是我们的策略需要跨日运行的话,应该在before_trading_start方法中获取股票池,而不是initialize方法中获取股票池。

财报数据的获取(回测)

该部分在上文有过讨论。
在回测中,我们获取的财报数据,一定要根据publ_date进行一次筛选,否则可能会导致获取的回测数据不准确。
通过上下文对象contextblotter.current_dt,可以获取当前回测的时间。

不一定五档都有

不一定五档的价格都有,可能会只有买一和卖一,其他档位都没有。这时候我们获取其他档位的价格会报错。
可以先做一个判断。

因为异常数据缺失

tick_data()中,不排除因为异常,导致获取不到最新价(last_px)。
可以参考如下的方式,进行判断。

1
2
3
4
5
last_px = snapshot['last_px']
up_px = snapshot['up_px']

if last_px == 0 or up_px == 0:
log.info('获取价格失败')

控制调用顺序

一般情况下,都是先运行initializebefore_trading_start,然后再运行handle_data等。
但是,如果我们是盘中新开的交易、或者交易在盘中重启等情况。可能会存在先运行了handle_data、再运行before_trading_start
所以,需要我们人为的强制调用顺序。

可以利用全局的变量,记录运行状态。后者运行的前提条件必须是前者已经运行。示例代码:

1
2
3
4
5
6
7
8
9
10
def initialize(context):
g.before_trading_start_ran = false

def before_trading_start(context, data):
g.before_trading_start_ran = true


def handle_data(context, data):
if not g.before_trading_start_ran:
return

做持久化

什么是持久化

持久化,这个是数据库中的术语,是指将数据从内存持久化到磁盘。
在这里的持久化,指的是将交易期间的一些状态,保存至本地。

为什么需要持久化

因为策略可能会中断,因为券商重启PTrade、或者因为我们自己重启策略等各种原因。

如何实现持久化

争议(回测期间持仓修改时间)

在有些资料中会说,回测期间,不要用持仓做成交判断,因为持仓数据在下一个回测周期才会被修改。我发现这个BUG,应该已经被修复了。

回测周期选分钟。示例代码:

1
2
3
4
5
6
7
8
9
def initialize(context):
pass;

def handle_data(context, data):
pos = get_positions()
print(pos)
order(security='600000.SS', amount=100)
pos = get_positions()
print(pos)

运行结果:

1
2
3
4
5
6
7
2023-08-02 16:33:58 开始运行回测, 策略名称: 测试
2023-01-03 09:31:00 - INFO - {}
2023-01-03 09:31:00 - INFO - 生成订单,订单号:d6c1ee0935434244b49a73634ec0072d,股票代码:600000.XSHG,数量:买入100股
2023-01-03 09:31:00 - INFO - {'600000.XSHG': <Position {'enable_amount': 0, 'amount': 100, 'last_sale_price': 7.2300000000000004, 'business_type': 'stock', 'cost_basis': 7.2803521010000001, 'sid': '600000.SS'}>}
2023-01-03 09:32:00 - INFO - {'600000.XSHG': <Position {'enable_amount': 0, 'amount': 100, 'last_sale_price': 7.2400000000000002, 'business_type': 'stock', 'cost_basis': 7.2803521010000001, 'sid': '600000.SS'}>}
2023-01-03 09:32:00 - INFO - 生成订单,订单号:b38b4afc752b456fbf801d8227efb95a,股票代码:600000.XSHG,数量:买入100股
2023-01-03 09:32:00 - INFO - {'600000.XSHG': <Position {'enable_amount': 0, 'amount': 200, 'last_sale_price': 7.2400000000000002, 'business_type': 'stock', 'cost_basis': 7.2853523444999997, 'sid': '600000.SS'}>}

我们看到,在09:31:00,执行order(security='600000.SS', amount=100)之后,其持仓数据就已经修改了。

集合竞价打板(案例)

思路

集合竞价的时间是9:15至9:25,而handle_data需要在9:30分才会被执行。

思路是利用run_daily这个方法。

run_daily

run_daily,按日周期处理。
关于该函数的更多用法,可以参考官方文档,这里论述一个注意点。

入参time,指定周期运行具体触发运行时间点:

  • 交易场景可设置范围:[00:00,23:59][00:00,23:59]
  • 回测场景可设置范围:
    • 当回测周期为分钟时,time的取值指定在[09:31,11:30][09:31,11:30][13:00,15:00][13:00,15:00]之间。
    • 当回测周期为日时,无论设定值是多少都只会在15:0015:00执行。

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def initialize(context):
# 初始化策略
g.security = '600000.SS'
set_universe(g.security)
# 每天9:23运行集合竞价处理程序
run_daily(context, hit_up, time = '9:23')


def handle_data(context, data):
pass

def hit_up(context):
snapshot = get_snapshot(g.security)
snapshot = snapshot[g.security]

last_px = snapshot.get('last_px',0)
up_px = snapshot.get('up_px',0)

if last_px == 0 or up_px == 0:
log.info('获取价格失败')
elif float(last_px) == float(up_px):
order(g.security,100,limit_price=last_px)

一阳穿三线(案例)

策略思路

一阳穿三线,是一种形态。指的是一根阳线,从开盘到收盘,突破了3根不同周期的均线。

实现代码

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
import pandas as pd
import numpy as np
from decimal import Decimal

# LIMIT_HOLD:最大持仓
LIMIT_HOLD = 10

# 初始化模块
def initialize(context):
#设置比较基准,默认是000300.SS
set_benchmark('000300.SS')
#设置佣金费率,默认是万三佣金费率和5元的最低交易佣金
set_commission(commission_ratio=0.0003, min_commission =5.0)
#设置固定滑点,即委托价格与最后的成交价格的价差,默认是0
set_fixed_slippage(fixedslippage=0.2)
#设置滑点比例,默认为0.1,具体影响还与当时成交数量与该周期最大可成交量有关
set_slippage(slippage = 0.2)
#设置成交比例,默认0.25,即指本周期最大成交数量为本周期市场可成交总量的四分之一
set_volume_ratio(volume_ratio = 0.2)
#设置交易股票池
g.security = '600570.SS'
set_universe(g.security)
g.limit_num = LIMIT_HOLD

# 盘前处理
def before_trading_start(context, data):
# 今日已委托买入股票池
g.order_stocks = []
# 每天开盘价存储
g.open_price_info = {}
# 获取今日日期
g.current_date = context.blotter.current_dt.strftime("%Y%m%d")
# 获取当日的股票池
g.stock_list = get_Ashares(g.current_date)
# g.stock_list = get_index_stocks('000300.XBHS')
g.stock_list = list(set(g.stock_list))
# 获取股票的状态ST、停牌、退市
st_status = get_stock_status(g.stock_list, 'ST')
halt_status = get_stock_status(g.stock_list, 'HALT')
delisting_status = get_stock_status(g.stock_list, 'DELISTING')
# 将三种状态的股票剔除当日的股票池
for stock in g.stock_list.copy():
if st_status[stock] or halt_status[stock] or delisting_status[stock]:
g.stock_list.remove(stock)
# 获取股票历史数据
history = get_history(70, '1d', ['close','volume'], g.stock_list, fq='dypre', include=False)
history = history.swapaxes("minor_axis", "items")
g.close_df_dict = {}
for stock in g.stock_list.copy():
# log.info(stock)
df = history[stock]
# 过滤历史停牌的数据
df = df[df['volume']>0]
# 如果非停牌日期>=60, 增加到历史数据存储字典, 否则移除
if len(df.index)<60:
g.stock_list.remove(stock)
continue

stock_exrights = get_stock_exrights(stock, date=g.current_date)
if stock_exrights is not None:
# log.info(stock_exrights)
# A_fact = stock_exrights['dynamic_exer_forward_a'].iloc[0]
A_fact = stock_exrights['dynamic_exer_forward_a'].iloc[0]
# log.info(type(A_fact))
B_fact = stock_exrights['dynamic_exer_forward_b'].iloc[0]
# log.info(A_fact)
# log.info(B_fact)
df['price'] = 0
df['price'] = df.apply(lambda x: x['close']*A_fact + B_fact, axis =1)
# log.info(df)
close_array = df['price'].values[:]
g.close_df_dict[stock] = close_array
else:
close_array = df['close'].values[:]
g.close_df_dict[stock] = close_array
#设置今天的股票池
set_universe(g.stock_list)
g.count = 0

# 盘中运行
def handle_data(context, data):
K_count = get_K_count(context)
# 第一分钟
if K_count == 1:
position_last_close_init(context)
for stock in g.stock_list:
# 保存开盘价
g.open_price_info[stock] = data[stock].open
# 下午2:40进行卖出买入
if K_count != 230:
return
g.data = data
sell_function(context)
buy_function(context)

# 盘后处理
def after_trading_end(context, data):
position_list = []
for stock in context.portfolio.positions:
if context.portfolio.positions[stock].amount != 0:
position_list.append(stock)
log.info(('持仓股:', position_list))
g.limit_num = LIMIT_HOLD - len(position_list)
log.info('校准可买股票数量为%s'%g.limit_num)

# 卖出模块
def sell_function(context):
for stock in g.position_last_map:
# sell_decision,卖出决策
flag = sell_decision(stock)
if flag:
order_id = order_target(stock, 0)
if order_id is not None:
g.limit_num+=1
log.info(('已下单卖出:',stock))

# 卖出决策
def sell_decision(stock):
curr_price = g.data[stock].price
close_array = g.close_df_dict.get(stock)
MA10 = get_MA(10, close_array, current_price=curr_price)
if curr_price < MA10:
return True
else:
return False

# 买入模块
def buy_function(context):
# get_buy_stocks,买入决策
buy_stocks = get_buy_stocks()
# 获取账户信息
portfolio_info = context.portfolio
# 获取账户现金
cash = portfolio_info.cash
# 判断最大持仓数字的全局变量g.limit_num大于等于1
if g.limit_num>=1:
# 判断成立则通过当前现金与可买入股票数量的余数赋值给平均每股可买变量mean_cash
mean_cash = cash/g.limit_num
# 判断不成立返回当前现金
else:
mean_cash = cash
# 如果有需要买入的
if buy_stocks:
# 过滤已经涨停的
buy_list = filter_limitup_stock(buy_stocks)
log.info('最终下单股票池%s'%buy_list)
for stock in buy_stocks:
# log.info(stock)
# 如果不在持仓列表中,并且没有进行过委托
if (stock not in g.position_last_map) and (stock not in g.order_stocks):
# 判断最大持仓数字的全局变量g.limit_num大于等于1
if g.limit_num >=1:
order_id = order_value(stock, mean_cash)
# 判断该次买入的股票是否已在持仓信息的dict中
# 如果是None,说明委托失败了
# 如果不是None,说明委托成功(不一定成交)
if order_id is not None:
# log.info(stock)
# 如果下单成功,最大持仓数字的全局变量g.limit_num减去1
g.order_stocks.append(stock)
g.limit_num-=1
log.info(('已下单买入:',stock))
# g.buy_date[stock] = g.current_date

# 买入股票池决策
def get_buy_stocks():
'''
一阳穿三线形态个股筛选
'''
buy_list = []
# log.info(len(g.stock_list))
for stock in g.stock_list.copy():

close_array = g.close_df_dict.get(stock)
curr_close = g.data[stock].price
curr_open = g.open_price_info.get(stock)
ma_short = get_MA(5, close_array, current_price=curr_close)
ma_mid = get_MA(20, close_array, current_price=curr_close)
ma_long = get_MA(60, close_array, current_price=curr_close)
# log.info('stock:%s,close_array:%s,curr_close:%s,curr_open:%s'%(stock,close_array[-5:],curr_close,curr_open))
cond1 = curr_open<ma_short and curr_open<ma_mid and curr_open<ma_long
if not cond1:
continue
cond2 = curr_close>ma_short and curr_close>ma_mid and curr_close>ma_long
if not cond2:
continue
cond3 = ma_short>ma_mid and ma_short>ma_long
if not cond3:
continue
price_range = (curr_close - close_array[-1])/close_array[-1]
if 0.02 < price_range < 0.05:
buy_list.append(stock)
return buy_list

# 保留小数点两位
def replace(x):
y = Decimal(x)
y = float(str(round(x, 2)))
return y

# 剔除涨停
def filter_limitup_stock(stocks):
stock_list = stocks.copy()
for stock in stock_list.copy():
last_close = g.close_df_dict.get(stock)[-1]
curr_price = g.data[stock].price
# log.info('222')
# log.info(last_close)
limitup_price = last_close*1.1
# log.info(limitup_price)
limitup_price = replace(limitup_price)
if curr_price >= limitup_price:
stock_list.remove(stock)
return stock_list


# 获取均线方法
def get_MA(days, close_array, current_price=None):
if current_price is None:
MA = mean(close_array[-days:])
else:
MA = (close_array[-days:-1].sum()+current_price)/days
return MA



# 获取最新分钟K线数
def get_K_count(context):
date = str(context.blotter.current_dt)
hour = int(date[11:13])
minute = int(date[14:16])
# log.info(date)
# log.info(hour)
# log.info(minute)
min_num = 0
if hour == 1:
min_num = minute-29
elif hour == 2:
min_num = minute + 31
elif hour == 3:
min_num = minute + 91
elif hour == 5:
min_num = minute + 121
elif hour == 6:
min_num = minute + 181
min_num -= 1
return min_num

# 生成昨日持仓股票列表
def position_last_close_init(context):
g.position_last_map = [
position.sid
for position in context.portfolio.positions.values()
if position.amount != 0
]

解释说明:

  • get_K_count:获取当前K线数。
  • replace:保留两位小数,
  • filter_limitup_stock:剔除涨停。
文章作者: Kaka Wan Yifan
文章链接: https://kakawanyifan.com/20503
版权声明: 本博客所有文章版权为文章作者所有,未经书面许可,任何机构和个人不得以任何形式转载、摘编或复制。

评论区