supermind logo
同花顺logo
  • 首页
  • 我的策略
    策略创作
    • 策略研究
    • 指标策略
    • 策略监控
    • 策略库
    因子研究
    • 因子策略
    • 因子检测
    • 因子库
    • 绩效分析
  • 我的研究
  • 实盘交易
    • python策略模拟
    • 指标策略模拟
    • 同花顺智能交易
  • 社区
  • 帮助
    • 本地SDK
    • API文档
    • 因子研究
    • 模拟仿真
    • 回测引擎
    • 研究环境
    • 常见问题
  • 数据
研究环境/实盘

研究环境/实盘运行


欢迎使用SuperMind量化交易平台,本文档详细介绍了SuperMind研究环境的功能及使用。

社区交流

如果您有任何意见建议或者您在使用过程中遇到说明文档中无法解答的问题,您可以:

  • 📢在SuperMind官方社区发帖提问交流
  • 💌给我们发送邮件: SuperMind@myhexin.com

研究环境的本质

  • SuperMind研究环境的本质是 Jupyter Notebook(python3.5环境)与 JupyterLab(python3.8环境),支持实时代码、数学方程、markdown,其用途包括:数据查看,统计建模,机器学习等等,您可以在研究环境中实现想法
  • JupyterLab是 Jupyter Notebook的全面升级。事实上,JupyterLab 是一个集 Jupyter Notebook、文本编辑器、终端以及各种个性化组件于一体的全能IDE。
  • 相比 Jupyter Notebook,JupyterLab能够打开更多的文件格式,除了代码文件(.py、.ipynb),还包括CSV、JSON、Markdown、PDF。

更多内容请参考jupyter官方网站

Supermind支持实盘运行

当前研究环境 承载了我们实盘的能力。(我们也即将上线可视化更方便的实盘管理功能)

【重要!!】写在最前面

1、全局变量需要为可pickle保存的对象,如list,dict等。如果没法确认最好先试运行几天。

2、查持仓、资金、委托的接口,券商可能会限制频率,所有代码里最好降低使用频率。

3、晚上22:05-22:15是后台固定清算时间,不接受新单,无法处理交易指令。

【重磅更新】回测代码直接实盘交易 只需一分钟!

1、背景与目的

之前我们有了策略回测代码,到实盘要经过熟悉实盘API、写代码、调试代码的环节,大概还需要1-2周的时间才能实盘,有非常多的用户到这一步 会束手无策,甚至放弃!

现在,有了回测代码直接实盘的功能,可以省去这个步骤,让刚入门的朋友也可以直接拿回测代码进行实盘了。很棒!为我们的工程师点赞!

此外还新增了一些接口,方便实盘交易。

不断降低实盘的门槛是我们的目标,如果您有任何好的想法意见请随时留言

本功能需要重启研究环境才能生效!

2、策略实盘交易(回测代码1分钟实盘)

  • 示例:

    from tick_trade_api import TradeAPI
    #初始化TradeAPI时需要指定下单策略,MarketPolicy为最新价下单;LimitPolicy为限价下单
    trade_api=TradeAPI('69271711',order_policy=LimitPolicy)
    
    source_code="""
    # 股票策略模版
    def init(context):
        pass
    
    # 盘前执行
    def before_trading(context):
        pass
    
    # 开盘时运行函数
    def handle_bar(context, bar_dict):
        order_id = order('000001.SZ', 100)
        print(get_orders())
        try:
            cancel_order(order_id)
        except:
            print('撤单失败')
        print(get_open_orders())
        print(get_tradelogs())
        print(context.portfolio.stock_account)
        print(context.portfolio.positions)
    """
    rtrade = research_trade(
        '研究环境策略',
         source_code,
         frequency='MINUTE', 
         trade_api=trade_api,
         signal_mode=False,
         recover_dt='today'
    )
    

research_trade接口文档详见

trade_api=TradeAPI('69271711',order_policy=LimitPolicy) 中的账号是模拟资金账号或者是实盘资金账号。

df54bf7a47071c5e416a1209b16febaa.png

3、其他更新

这次还增加了几个功能

策略框架中增加 :

  • cancel_order_all() 全撤
  • get_tradelogs()获取当日全部成交订单
  • get_orders() 获取委托,和get_order()一致,主要时和TradeAPI中函数名对齐

TradeAPI增加:

  • get_open_orders() 获取当日未成订单
  • cancel_order_all() 全撤

SuperMind 1分钟快速实盘 入门教程

1、确保先下载好终端 并升级到最新版本

未付费的可以下载体验版本 进行仿真实盘:

https://download.10jqka.com.cn/index/download/id/709

想要用真实资金账号实盘的联系论坛首页右上角的群。

2、用同花顺账号进行登录软件

3、点击下方“研究一下”按钮:

此操作会打开网页端的研究环境 并把下方的研究文件直接拷贝到研究环境根目录

917b01fa655b1a3cf770e33493d69820.png

遇到需要选择环境 建议选择 python3.8

4、打开客户端的研究环境 直接就可以看到这个文件 进行按照提示进行运行了

c7b85ecbc8d3a3a7e91c18bf7ef758c8.png

实盘操作

4.1、进入客户端首页获取资金账号:

0cff56969e4070f0bd2c3c537f552808.png

4.2、把策略对应的资金账号填写到 TradeAPI的第一个参数中把策略对应的资金账号填写到 TradeAPI的第一个参数中

fde7d07e4956ea491b06bd0ddb8629e9.png

4.3、选中下方的代码单元格之后,点击上方三角形 ▶ 按钮启动策略:

a1936f7ce3cb82c021a160391fcd9618.png

模板策略 请到 这个帖子底部进行复制(点击 研究一下,就会复制到您的研究环境根目录):https://quant.10jqka.com.cn/view/article/2358

仿真/实盘与回测的差异以及解决方案

汇总一下目前实盘遇到比较多的问题、产生的原因以及如何解决问题。首先得从回测环境和实盘环境的一些区别开始说起

回测(模拟)与仿真柜台的区别?️

回测(模拟)环境和柜台环境(仿真、实盘)会有部分差异,如果在策略中不考虑这些差异并做对应处置,可能会导致策略在回测中正常运行,导致策略在仿真、实盘中出现bug。

梳理了一下目前主要有以下不同点(如有遗漏请补充):

回测 仿真
一般没有初始持股 可能有初始持股
委托通常会立刻成交 委托不会立刻成交
持仓数据中有持仓天数position_days position_days一直等于0
不存在策略外交易 策略外交易会影响策略内
很少有撤单的场景 最好需要考虑撤单
回报没有延迟 回报有延迟
当日停牌行情数据可以补上(get_price的skip_paused=False) 当日停牌行情数据无法补上

可能导致的问题与解决方案?️

问题一:资金账号内有初始持仓

策略在回测/模拟中,在不设置的情况下是不会有初始持仓的,而在实际交易中,资金账号通常会有初始持仓。

  • 可能导致的问题:
    • 部分策略内部存在内部记录持仓信息的逻辑,在资金账号有初始持仓时,使用research_trade运行策略时,如果有加入同步函数sync_trade_api()或者策略的 signal_mode=False,策略内部无初始持仓的信息,可能导致冲突报错
  • 案例:策略库中的问财分钟模板

f565fca2a94777490155078155d6e4fd.png

在以上策略中,策略会将买入股票的信息记录在字典 context.information中。当资金账户有初始持仓时,卖出这部分股票的时候,策略会将 context.information中的持仓信息删除,但由于 context.information字典没有这部分股票代码的key(因为不在策略内买入,没有记录),使用del方法删除持仓信息时导致报错:

  • 解决方案(用户侧,具体方案需视策略实际情况而定):
    • 初始化时在 context.information中记录初始持仓信息
    • 策略运行时不要在策略外部进行交易
    • 使用dict.pop()删除字典中的key,而不是del方法
  • 长期解决方案(SuperMind功能优化):
    • 以策略为单位构建资产单元(类似分仓功能),将策略内外部资金、持仓、委托、成交数据隔离(预计7月-8月)
问题二:实盘中委托通常不会立即成交
  • 可能导致的问题:
    • 策略内部存在内部记录持仓信息的逻辑,在委托后立即记录买入,实际上委托后不会立刻成交(等待时间视委托价格和当前行情走势而定)。此时就有可能导致信息被漏记/多记
  • 案例:示例代码
import time

def init(context):
    g.information = {}
    g.symbols = ['000001.SZ','600519.SH']

def handle_bar(context):
    for symbol in g.symbols:
        order(symbol,100)
    for symbol in list(context.portfolio.positions):
        g.information[symbol] = 1
    time.sleep(3)
    for symbol in list(context.portfolio.positions):
        print(g.infomation[symbol])

以上代码在回测中通常不会出现问题,因为撮合是在策略内部串行且市价单通常可以成交。但是在实盘中,股票下单后汇报和撮合不会像回测中那样进行,订单的撮合和策略时同步进行的,此时,按示例代码的方式,就可能会由于订单未成交,导致信息未被记录进 g.information,而在三秒之后订单成交,再使用持仓代码去读 g.information的数据时,导致策略出错。

  • 解决方案(用户侧,具体方案需视策略实际情况而定):
    • 优化记录持仓信息的代码,比如在收盘后根据持仓、成交等信息统一进行计算,减少漏记、多记的发生
    • 考虑使用dict.get()函数获取字典内的数据
  • 长期解决方案(SuperMind功能优化):
    • 增加成交回报事件、委托状态更新事件推送(计划6月底前)
问题三:券商/仿真柜台返回的持仓数据中没有position_days数据
  • 可能导致的问题:
    • 策略内使用此数据来进行控制最大持仓天数部分代码可能失效
  • 案例:示例代码
from datetime import timedelta as td

def init(context):
    g.symbols = ['000001.SZ','600519.SH']
    g.status = True

def handle_bar(context):
    if g.status:
        for symbol in g.symbols:
            order(symbol,100)
        g.status = False
    else:
        for k,v in context.portfolio.positions:
            trade_days = get_datetime() - td(v.positions_days)
            tdays = len(get_trade_days(
                trade_days.strftime('%Y%m%d'),
                get_datetime().strftime('%Y%m%d')
            ))
            if tdays>5:
                order_target(k,0)

此示例代码希望实现的是买入股票池后持有5天后卖出,在回测中没有问题,但是在仿真/实盘中,由于柜台没有positions_days的数据,因此v.positions_days一直会等于0,导致此部分代码无法实现预期效果。

  • 解决方案(用户侧,具体方案需视策略实际情况而定):
    • 增加记录持股天数的数据,但需要注意避免问题一和问题二
  • 长期解决方案(SuperMind功能优化):
    • 以策略为单位构建资产单元(类似分仓功能),将策略内外部资金、持仓、委托、成交数据隔离(预计7-8月),并根据此计算出position_days数据
问题四:策略外交易会影响策略内
  • 可能导致的问题:
    • 手动买入的持仓被策略卖出,手动卖出的持仓被策略买回
    • 策略代码报错
  • 解决方案(用户侧,具体方案需视策略实际情况而定):
    • 处理起来比较麻烦,改动比较多,代码能弱的同学暂时尽可能不要在策略外做手动交易
  • 长期解决方案(SuperMind功能优化):
    • 以策略为单位构建资产单元(类似分仓功能),将策略内外部资金、持仓、委托、成交数据隔离(预计7-8月)
问题五/问题六:实盘中需要考虑更复杂的场景
  • 策略回测及模拟交易时,策略可以说实在相对静态的环境下运行的,并且时不考虑延迟的。而在实际的交易中,市场瞬息万变,无论是数据获取、计算耗时这种,还是下单与回报的延迟(不可控),都会导致实盘中产生更复杂的情景
  • 可能导致的问题:
    • 考虑订单长期未成交情况下的处理方式,回测中对手价/市价单通常可以立刻成交,而实际交易中则相对来说有较大概率不会立刻成交
      • 例如:价格变动剧烈时,下单后未成交,又未及时撤单、追单,降低资金效率。可能会影响策略调仓,造成策略表现变差
    • 在策略逻辑上充分考虑到订单生成、下单到券商柜台、券商柜台回报所产生的延迟,避免策略出现异常
      • 例如:在下单后立刻撤销订单,此订单刚生成,未完成初始化,处于不可撤销的状态,从而导致报错
    • 尽可能减少在handle_bar中获取任何数据,提高计算效率,避免策略本身产生较高的延时
      • 例如:handle_bar在9.31分被触发,从触发到下单中间计算耗时五分钟,在回测中,订单仍然会以9.31分的行情数据进行撮合。而在实盘中,订单会在9.36分被发出,从而产生延时成本。
  • 解决方案(用户侧,具体方案需视策略实际情况而定):
    • 考虑复杂场景,并增加相对于的策略代码
  • 解决方案(SuperMind功能优化):
    • 增加成交回报事件、委托状态更新事件推送(计划6月底前)
问题七:实现秒级别频率的策略交易
  • 可能导致的问题:
    • “量化实盘”环境虽然能丝滑便捷的实现“策略回测”一键上实盘,但同时也存在一些限制,如signal_mode的参数设置,以及只支持日频/分钟频的策略。
  • 解决方案:
    • 在“研究环境”环境提供更高的自由度,但对代码的能力要求也更高。用户可以自行搭建策略框架实现秒级别策略交易。
    • 也可以通过调用handle_tick函数实现,注意:使用该函数必需先通过subscribe订阅标的行情,且在订阅标的有tick行情更新时触发策略执行。
    • 还可以通过调用run_daily函数实现,每秒、或固定时点触发策略执行。
    • 其他频率级别的触发策略执行,请移步参考研究环境的ipynb文档:“Datafeed数据接口使用教程”“交易接口应用实例”。

e98af7b9bdf07cc9c192db6acd86ee67.png

  • 案例一:示例代码
from tick_trade_api import TradeAPI
#初始化TradeAPI时需要指定下单策略,MarketPolicy为最新价下单;LimitPolicy为限价下单
trade_api=TradeAPI('69271711',order_policy=LimitPolicy)

source_code="""
# tick股票策略模版
def init(context):
    #设定标的代码
    context.symbol = ['300033.SZ']
    subscribe(context.symbol)

# 开盘时运行函数
def handle_tick(context, tick):
    log.info('定时运行')

"""
rtrade = research_trade(
    '研究环境策略handletick',
     source_code,
     frequency='TICK', 
     trade_api=trade_api,
     signal_mode=False,
     recover_dt='today'
)
  • 案例二:示例代码
from tick_trade_api import TradeAPI
#初始化TradeAPI时需要指定下单策略,MarketPolicy为最新价下单;LimitPolicy为限价下单
trade_api=TradeAPI('69271711',order_policy=LimitPolicy)

source_code="""
# tick股票策略模版
def init(context):
# 每个交易日开盘每秒执行一次
    run_daily(func=test_day, time_rule='every_bar', reference_security='300033.SZ')

def test_day(context, bar_dict):
    log.info('定时运行')

"""
rtrade = research_trade(
    '研究环境策略rundaily',
     source_code,
     frequency='TICK', 
     trade_api=trade_api,
     signal_mode=False,
     recover_dt='today'
)

总结

目前遇到的主要就是这些问题,如果在交易过程中有其他问题,也可以在本贴留言,最好可以提供测试代码复现问题便于我们查找原因。

想快速实盘可以看下面文章:

  • 回测正常,模拟ok,如何快速实盘?(保姆式教学 含截图)
  • SuperMind 1分钟快速实盘教程0718
  • 策略仿真/实盘与策略回测中的差异问题以及解决方案

每个账号实盘可以开几个策略?

supermind不限制策略个数,但是会被内存约束,一般用户2G内存,可以运行3-4个策略,一般一个策略是500M内存占用。如果要升级内存可以看下这里:内存不够怎么办?

但要注意 如果在一个资金账号下运行多个策略,需要处理策略之间的持仓冲突问题。

实盘注意必须开着客户端 并确保研究环境正常运行

  • 研究环境关闭规则 点击这里

内置接口

数据接口

  • 研究环境中提供了丰富的数据接口,具体可以参考:
    • API文档-通用数据接口
    • SuperMind数据平台

仿真交易接口 - TradeAPI

准备工作

  • 先打开SuperMind客户端,并确保需要使用交易接口的账户处于已登录状态

ccd34e61195d4c679c6cd1d27c089a0d.png

  • 进入量化交易标签下的研究环境,并创建一个python笔记本文件(ipynb文件)

1d4e234afcca4ef0e17e8ef6ee031e47.png

账户信息查询

  • 导入包、初始化账户
from tick_trade_api.api import TradeAPI
trade_api = TradeAPI(account_id='84728199') #填入已登录的资金账号
  • 查询账户资金
portfolio = trade_api.portfolio
print(portfolio)

输出:

{'available_cash': 10350203.22, 'market_value': 1168, 'total_value': 10351371.22, 'frozen_cash': 0}

字段说明:

变量 释意
available_cash 可用资金
market_value 证券市值
frozen_cash 冻结资金
total_value 总资产
  • 查询资金转入/转出流水
transfer = trade_api.get_transfers(start_date=None, end_date=None)

输出:

{'transfer_id': '20241213003359', 'transfer_type': '转入', 'transfer_status': '', 'cash': 100000.0, 'bank_name': '存管招商人民币', 'currency': '人民币', 'total_cash': 0.0, 'datetime': Timestamp('2024-12-13 09:51:09')}

字段说明:

变量 释意
transfer_id 流水编号
transfer_type 流水类型
transfer_status 流水状态
cash 资金
bank_name 银行名称
currency 币种
datetime 成交时间
  • 查询持仓信息
positions= trade_api.positions
print(positions)

输出:返回一个字典对象,键值为证券代码;对应的数据为一个Namedict对象, 包含证券的各种信息。对应变量的意义可查询下面的表格。需要注意的是Namedict对象仅可以根据键值获取数值,并不具备字典可迭代等其他的特性

{'000001.SZ': {'symbol': '000001.SZ', 'name': '平安银行', 'amount': 100, 'frozen_amount': 100, 'available_amount': 0, 'market_value': 1164, 'cost_basis': 11.644, 'last_price': 11.64, 'pnl': -0.37, 'profit_rate': -0.0003}}

字段说明:

变量 释意
name 股票名
available_amount 可用证券数量
cost_basis 成本
last_price 最新价
amount 证券数量
profit_rate 收益率
frozen_amount 冻结数量
market_value 证券市值
symbol 证券代码
pnl 盈亏金额

将持仓信息转换为dataframe,可供进一步分析使用

import pandas as pd
vb_list = ['symbol', 'name', 'amount', 'available_amount', 'frozen_amount', 'cost_basis', 'last_price', 'market_value', 'pnl', 'profit_rate']

positions_df = pd.DataFrame()
for symbol, info in positions.items():
    temp = pd.DataFrame([[info[vb] for vb in vb_list]], columns=vb_list, index=[0])
    positions_df = pd.concat([positions_df, temp], ignore_index=True)
print(positions_df)

柜台断线重连

  • 因各家券商均有在盘前做账号初始化的机制,导致策略运行的资金账号断线,最终导致报错策略中止。故新增账号重连尝试入参,允许客户自定义重连参数,实现策略的连续运行。
trade_api = TradeAPI(account_id='84728199', retry_query_exception=6, delay_query_exception=10)#设置每隔10秒尝试重连,尝试6次
  • 参数解释:

    • int retry_query_exception :重连尝试次数,大于等于0,默认0次。
    • int delay_query_exception :重连尝试间隔,大于等于0,默认0秒。

下单与撤单

最新价下单

当amount为负数时为卖出指令,下单成功后返回委托ID('order_id'),委托ID可用于撤单函数进行撤单。

trade_api.order(symbol='300033.SZ', amount=100)
限价单

当amount为负数时为卖出指令,下单成功后返回委托ID('order_id'),委托ID可用于撤单函数进行撤单。

trade_api.order(symbol='000001.SZ', amount=100, price=13.4)
智能下单

可以通过指定pricetype实现更多样化的下单需求

pricetype 意义 pricetype 意义
0 指定价 1 涨停价
2 跌停价 3 最新价
4 卖一价 5 卖二价
6 卖三价 7 卖四价
8 卖五价 9 买一价
10 买二价 11 买三价
12 买四价 13 买五价
17 市价 21 跟限价

当pricetype为0时,以price的价格下单;

当pricetype为1-13时,price表示pricetype指定价格的浮动价,最终下单委托价格pricetype指定价格+price,若最终价格大于涨停价,最终价格取涨停价 , 若小于跌停价,最终价格取跌停价

当pricetype为17时,price表示市价类型,对于沪深有不同的含义。上证:1-五档即成剩撤,2-五档即成剩转限,3-本方最优,4-对手方最优;深证:1-对手方最优,2-本方最优,3-即成剩撤,4-五档即成剩撤,5-全额成交或撤

当pricetype为21时,买入下单时会紧贴价格笼子上沿,卖出下单时会紧贴价格笼子下沿

#以卖五价上浮0.3元挂100股002109的买单
trade_api.order(symbol='002109.SZ', amount=100, price=0.3, pricetype=8)
撤单
  • 传入委托ID即可撤销指定未成交委托

    trade_api.cancel_order('11591')
    
  • 如果该委托已成交或者已撤单则会报错,可以通过try、except等函数捕获该错误,操作如下

    try:
        trade_api.cancel_order('11591')
    except:
        print('撤单失败: 不允许的指令:已完成或取消中的条件单不允许取消')
    
  • 全部撤单

    trade_api.cancel_order_all()
    

查询成交委托

查询SuperMind后台委托记录
  • 委托的结果可以通过 trade_api.get_orders函数查询, 同时也能在智能交易终端查看到
    • 调用方法:trade_api.get_orders(order_id=None, start_time=None, end_time=None, symbol=None)
    • 参数解释:
      • order_id:订单编号
      • start_time:历史查询的开始时间
      • end_time:历史查询的截止时间
      • symbol:指定股票代码
    • 注意事项:默认查询当日所有订单
委托成交推送回调
  • 设定下列回调函数后,再下达委托函数,即可主动推送成交情况
    def order_push(order):
        print("接收到订单推送: ", order)
    trade_api.register_push(orderpush=order_push)
    

算法交易

自动撤追单
  • 调用方法 trade_api.NEW_RECHASE(reprice=None, spread=0, entrustcnt=3, revoke=True, timeout=60, **kwargs)

  • 参数解释:

    • reprice:设置为None时追单算法不生效,设置为非None时,会根据reprice和repricetype来决定追单的价格:
      • 当未传入repricetype时
        • reprice=0,repricetype会设置为3,以最新价追单,执行entrustcnt次
        • reprice为指定价格,repricetype会设置为0,以指定价格追单1次
      • 当传入repricetype时
        • 在追单时生效,repricetype含义等同于pricetype,reprice等同于price,详见智能下单,为0时只追单1次,其余追单entrustcnt次
    • spread:价差比。|(追单价格-第一次下单价格)/第一次下单价格|≤spread时,以追单价格下单,否则以第一次下单价格*(1±spread)下单,且后续不再追单。
    • entrustcnt:交易次数,默认3次。
    • revoke: 是否撤单,默认撤单。
    • timeout: 撤单间隔,默认60s。
    • repricetype: 可不传,在追单时生效,repricetype含义等同于pricetype,reprice等同于price,详见智能下单
  • 示例

# 创建一个自动追单下单对象 设置追单价格为82元,价差比为0.15,交易次数为3次,自动撤单,撤单时间为30秒
# 设置好追单参数以后, 自动下单追单对象的使用方法和普通下单api一样
rechase_api = trade_api.NEW_RECHASE(reprice=82, spread=0.15, entrustcnt=3, revoke=True, timeout=5)
rechase_api.order('601012.SH',amount=100, price=79)
# 此接口另有一隐藏参数repricetype,repricetype含义等同pricetype,指定repricetype时,reprice等同price,参考智能下单
rechase_api = trade_api.NEW_RECHASE(reprice=0.01, spread=0.15, entrustcnt=3, revoke=True, timeout=5, repricetype=10)
rechase_api.order('601012.SH', amount=200, price=79)
篮子交易
basket_api = trade_api.NEW_BASKET()
rechase_api = trade_api.NEW_RECHASE(reprice=None, spread=0, entrustcnt=3, revoke=True, timeout=60)
basket_api.add(symbol='000001.SZ', amount=100, algo_api=rechase_api)  # 添加订单到篮子,可多次添加
basket_api.add(symbol='000001.SZ', amount=100, algo_api=rechase_api)  # 添加订单到篮子,可多次添加
basket_api.add(symbol='000001.SZ', amount=100, algo_api=rechase_api)  # 添加订单到篮子,可多次添加
basket_api.order(balance=1000)  # 指定篮子使用的资金,默认0为全部
TWAP算法
  • 调用方法 twap_api = trade_api.NEW_TWAP(start_time='09:30', end_time='15:00', order_interval=60, rechase_interval=30, min_order_number=100, max_order_number=10000)

  • 参数解释:

    • start_time: 启动时间,不能大于收盘时间和结束时间,默认09:30。
    • end_time: 结束时间,不能大于收盘时间,默认15:00。
    • order_interval: 下单间隔,最好大于10,给前一笔订单充分成交和撤单时间,默认60s。
    • rechase_interval: 补单间隔,大于等于0,小于order_interval,如果为0则下单间隔内不撤单,默认30s。
    • min_order_number: 最小下单的股票数量,默认100股。
    • max_order_number: 最大下单的股票数量,默认10000股。
    • randprice: 浮动价格,非必填,委托时在price+-randprice范围内波动。
  • 说明

    • 算法根据起止时间、下单间隔、最大最小下单数量,来确定订单拆分笔数和每笔订单委托数量;
    • 订单拆分完成后,每2笔订单下单时间间隔order_interval秒,每笔订单下单之后如未完成则间隔rechase_interval秒后进行撤补操作,撤补不影响下1笔订单,举例而言,一笔300股的订单,order_interval为60秒,rechase_interval为30秒,下单起止时间t1、t2间隔3分钟,则t1下单100股,t1+30s撤补,t1+60s下单100股,以此类推
  • 示例

# 创建一个TWAP下单对象,委托价格为最新价上浮0.01元,浮动范围为1元,下单间隔为60秒,撤补间隔为30秒
twap_api = trade_api.NEW_TWAP(start_time='09:30', end_time='15:00', order_interval=60, rechase_interval=30, min_order_number=100, 
                              max_order_number=10000,randprice=1)
twap_api.order(symbol='000001.SZ', amount=100, price=0.01,pricetype=3) # 限价单,amount为负数代表卖出
VWAP算法
  • 调用方法 vwap_api = trade_api.NEW_VWAP(start_time='09:30', end_time='15:00', order_interval=60, rechase_interval=30, min_order_number=100, max_order_number=10000, ordercycle=1, sampledays=30)

  • 参数解释:

    • str start_time: 启动时间,不能大于收盘时间和结束时间,默认09:30。
  • str end_time: 结束时间,不能大于收盘时间,默认15:00。
  • int order_interval: 下单间隔,最好大于10,给前一笔订单充分成交和撤单时间,默认60s。
  • int rechase_interval: 补单间隔,大于等于0,小于order_interval,如果为0则下单间隔内不撤单,默认30s。
  • int min_order_number: 最小下单的股票数量,默认100股。
  • int max_order_number: 最大下单的股票数量,默认10000股。
  • int ordercycle: 分段周期,1、5、15、30、60分钟k线,默认1分。
  • int sampledays: 样本天数,默认30天。
  • float randprice: 浮动价格,非必填,委托时在price+-randprice范围内波动。
  • 示例
vwap_api = trade_api.NEW_VWAP(start_time='09:30', end_time='15:00', order_interval=60, rechase_interval=30, min_order_number=100, max_order_number=10000, ordercycle=1, sampledays=30)
vwap_api.order(symbol='000001.SZ', amount=100) # 最新价下单,amount为负数代表卖出
冰山算法
  • 调用方法 iceberg_api = trade_api.NEW_ICEBERG(start_time='09:30', end_time='15:00', order_percent=0.05, rechase_interval=30)
  • 参数解释:
  • str start_time: 启动时间,不能大于收盘时间和结束时间,默认09:30。
  • str end_time: 结束时间,不能大于收盘时间,默认15:00。
  • int order_percent: 每单占比,默认0.05。
  • int rechase_interval: 补单间隔,大于等于0,小于order_interval,如果为0则下单间隔内不撤单,默认30s。
  • float randprice: 浮动价格,非必填,委托时在price+-randprice范围内波动。
  • int entrusttype: 委托方式,非必填,0-全成即补(默认)、1-部成即补。
  • float entrusttraderatio: 成交比例,非必填,当entrusttype=1时的成交比例,默认为0,只要部成就补单。
  • int entrusttimeout: 成交超时时间,非必填,当entrusttype=1时的成交超时时间,默认为0,只要部成就补单。
  • 说明

    • 一个母单拆成多个小单,每笔子单的数量是一定的,子单数量 = 委托数量 * 每单占比 , 取下整(手)
    • 一笔子单不成交,达到撤单间隔时会撤单,撤单成功后再委托下一笔子单。
    • 5种委托方式:
      • 全成即补:前一个子单全部成交完成,后一个子单才会发单。
      • 部成即补:前一个子单部分成交,后一个子单即委托出去,但是必须等到第一个子单全部成交,第二个子单部分成交,才可以委托第三个子单。
      • 成交占比:比如总委托量是10000,暴露量是1000。当比例是50%,第一次暴露量是1000,然后部分成交了400(100040%),是不会到第二次补单的;一直到成交了大于等于500(100050%)才会第二次委托。同时,必须等到第一个子单全部成交,第二个子单到50%,才可以委托第三个子单。
      • 成交超时:指超过一定时间,虽未成交,但也自动再补一笔。成交超时的规则:比如900s,指的是如果第一笔委托后900s,仍旧出现未成交or部分成交,就直接开始第二次委托。同时,必须等到第一个子单全部成交,第二个子单到委托后900s,才可以委托第三个子单。
      • 占比和超时同时设置:则只需要并且必须其中任何一个满足条件,才进行委托
  • 示例

iceberg_api = trade_api.NEW_ICEBERG(start_time='09:30', end_time='15:00', order_percent=0.05, rechase_interval=30)
iceberg_api.order(symbol='000001.SZ', amount=100) # 最新价下单,amount为负数代表卖出

融资融券交易

导入包,建立连接
from tick_trade_api.api import TradeCredit as TradeAPI, SIDE
trade_api = TradeAPI(account_id='18771019820')
账号各类资产及委托查询函数
print("账号信息:", trade_api.portfolio)
print("持仓信息:", trade_api.positions)
print("成交信息:", trade_api.get_tradelogs())
print("委托信息:", trade_api.get_orders()) # 可传入order_id查询指定委托信息
print("柜台委托信息:", trade_api.secondary_orders())
print("可融资标的券:", trade_api.get_stocks()

账户信息字段说明:

字段 含义
available_cash 可用资金
market_value 市值
total_value 总资产
frozen_cash 冻结资金
finance_debt 融资负债
stock_debt 融券负债
total_debt 总负债
net_assets 净资产
finance_available 融资可用额度
available_margin 可用保证金

其他API返回结果的字段含义与普通账户的API一致

最新价下单
trade_api.order(symbol='000001.SZ', amount=100, side=SIDE.BUY) # 最新价下单,担保品买入
trade_api.order(symbol='000001.SZ', amount=100, side=SIDE.FINANCE_BUY) # 最新价下单,融资买入
trade_api.order(symbol='000001.SZ', amount=100, side=SIDE.SALE) # 最新价下单,担保品卖出
trade_api.order(symbol='000001.SZ', amount=100, side=SIDE.STOCK_SALE) # 最新价下单,融券卖出
限价下单
trade_api.order(symbol='000001.SZ', amount=100, price=10, side=SIDE.BUY) # 限价单
撤单
trade_api.cancel_order('107828')

因子检验

  • 可通过自定义代码实现因子检测,代码调整方法可参考SuperMind的因子检测模块
    # coding: utf-8
    # ==========================================因子检测==============================================================
    import alphalens
    import pandas as pd
    import numpy as np
    import time
    import statsmodels.api as sm
    import scipy as sp
    
    class FactorAnalyse(object):
    
    #==========================================因子检测参数设置==============================================================
        start_date = '2021-05-22'      #回测开始时间
        end_date = '2021-06-21'        #回测结束时间
        benchindex = '000300.SH'       #基准指数设置
        stockpool = '000905.SH'            #股票池设置
        quantiles=3                    #因子分组数量
        periods=1                      #调仓周期
        frequency='daily'              #调仓频率'daily','weekly','monthly'一周按5个交易日计算,一月按21个交易日计算
    
    #==========================================添加因子==============================================================
        #添加因子名称,保持list形式,系统因子对应字段参照 http://quant.10jqka.com.cn/platform/html/help-api.html?t=data#222/436
        factor_input=['weighted_roe']
    
    
    #==========================================因子合成参数设置==============================================================
        # 因子合成参数
        #direct表示因子方向,weight表示因子权重
        #因子方向:一共有两种1和-1。1表示正序,从小到大排列,因子值越大的股票会分组至前几组;-1表示倒序,效果反之
        factor_set={"weighted_roe":{"direct":1,"weight":1}}
    
    #==========================================因子数据处理参数设置==============================================================
        #对应的因子数据处理选项
        #缺失值处理 fillna:0—不处理,1—均值法,2—回归填充法
        #极值处理 winsorize:0—不处理,1—中位数法,2—三倍标准差,3—四分位
        #正交化处理 neutralize:0—不处理,1—申万一级正交化,2—市值正交化,3—申万行业市值正交化
        #标准化处理 standardize:0—不处理,1—标准化法,2—rank值标准化,3—极差正规化
        factor_dp={"weighted_roe":{"fillna":0,"winsorize":0,"neutralize":0,"standardize":0}}
    
    #==========================================合成因子数据处理参数设置==============================================================
        #合成因子数据处理选项
        dataprcess={"fillna":0,"winsorize":0,"neutralize":0,"standardize":0}
    
    #==========================================用户自定义因子算法========================================================
        #用户可在此函数下编译因子计算方式,返回的结果需为DataFrame,列名(columns)为时间,行名(index)为股票代码  
        def factor_gen(self, start_date, end_date,stocks=None):
            stocks=stocks or self.get_stocks()   #此行代码用于更新因子时获取股票代码,请勿更改
            factor_df = get_sfactor_data(start_date, end_date, stocks, ['trix'])  #样例数据,可获取行情数据及财务数据构建新的因子
            return factor_df['trix']
    
    
    
    #==========================================因子检测前进行数据准备及数据处理========================================================
        def calc(self):
            log.info("回测区间: %s / %s" % (self.start_date, self.end_date))
            log.info("开始时间: %s " % time.strftime("%H:%M:%S"))
            if self.frequency=='daily':
                period=self.periods
            elif self.frequency=='weekly':
                period=self.periods*5
            elif self.frequency=='monthly':
                period=self.periods*21
    
            # 获取股票池
            self.get_stocks()
            # 价格数据price_df
            log.info("正在获取行情数据......: %s " % time.strftime("%H:%M:%S"))
            price_df = get_price(self.stocks, self.start_date, self.end_date, str(period) + 'd', ['close'], bar_count=0, skip_paused=False, fq='pre', is_panel=1)['close']
            days = get_trade_days(self.start_date, self.end_date)
            day_index=pd.Index((days[min(i+period-1, len(days)-1)] for i in range(0, len(days), period)))
            price_df=price_df.loc[day_index,:]
            log.info("行情数据提取完成: %s " % time.strftime("%H:%M:%S"))
    
    #==========================================获取行业分类==============================================================  
            # 获取行业分类哑变量,行业分类数据groupby
            log.info("正在获取行业分类数据......: %s " % time.strftime("%H:%M:%S"))
            industry_data, ind_dict = get_sfactor_industry(self.start_date, self.end_date, self.stocks, industry='s_industryid1')
            log.info("行业分类数据提取完毕: %s " % time.strftime("%H:%M:%S"))
    
    
    # ==========================================根据参数选择生成因子==========================================================
            # 获取因子数据
            log.info("正在获取因子数据......: %s " % time.strftime("%H:%M:%S"))
            factor_df = get_sfactor_data(self.start_date, self.end_date, self.stocks, self.factor_input)
            log.info("获取因子数据完毕: %s 因子名称: %s" % (time.strftime("%H:%M:%S"), self.factor_input))
    
    
    #==========================================因子合成=========================================================   
            factors = factor_df[self.factor_input[0]].copy()
            factors.iloc[:, :] = 0
            for ia in self.factor_input:
                factors = factors + self.factor_set[ia]['direct'] * self.factor_set[ia]['weight'] * factor_df[ia]
            log.info("因子数据合成完成: %s " % time.strftime("%H:%M:%S"))
    
    
    #==========================================根据用户自定义因子算法生成因子=========================================================   
    
    #         factors = self.factor_gen(self.start_date, self.end_date,self.stocks)
    #          log.info("自定义因子数据计算完成: %s " % time.strftime("%H:%M:%S"))
    
    
    #==========================================使用alphalens进行因子数据预处理=========================================================
            log.info("正在使用alphalens进行数据处理......: %s " % time.strftime("%H:%M:%S"))
            factor_data=get_clean_factor_data(factors, price_df, self.quantiles, ind_dict,[self.periods])
            factor_data.rename(columns={'1D':str(period) + 'D'},inplace=True)
    
            log.info("因子数据处理完成: %s " % time.strftime("%H:%M:%S"))
            return factor_data
    
    
    #==========================================获取股票池中股票代码========================================================
        def get_stocks(self):
            if self.stockpool=='stock':
                self.stocks=list(get_all_securities('stock',self.start_date).index)
            else:
                self.stocks = get_index_stocks(self.stockpool, self.start_date)
    
    #==========================================使用alphalens进行因子检测=========================================================
    try:
        __IPYTHON__
        alphalens.tears.create_full_tear_sheet(FactorAnalyse().calc(),
                                               long_short=True,
                                               group_neutral=True,
                                               by_group=True)
        log.info("因子检测完成: %s " % time.strftime("%H:%M:%S"))
    except NameError:
        pass
    

策略回测 - research_strategy

  • 调用方法
research_strategy(
    source_code, 
    start_date=None, 
    end_date=None, 
    capital_base=100000, 
    frequency='DAILY', 
    stock_market='STOCK', 
    benchmark=None
)
  • 参数解释:

    • source_code:策略代码,可从策略研究模块中直接复制,代码置于"""..."""中
    • start_date:回测开始时间,如'20210601'
    • end_date:回测结束时间,如'20210601'
    • capital_base: float,初始资金量
    • frequency: 回测频率,'DAILY'或'MINUTE'
    • stock_market: 策略类型,默认'STOCK'
    • benchmark: 基准指数
  • 示例

source_code=r"""
# 股票策略模版
def init(context):
    pass
  
## 开盘时运行函数
def handle_bar(context, bar_dict):
    order('000001.SZ', 100)
"""

btest = research_strategy(source_code, start_date='20210601', end_date='20210815', capital_base=float(10000000), frequency='DAILY', stock_market='STOCK', benchmark=None)

模拟仿真 - research_trade

  • 调用方法
research_trade(
    name, 
    source_code, 
    capital_base=100000, 
    frequency='DAILY', 
    stock_market='STOCK', 
    benchmark=None, 
    trade_api=None, 
    signal_mode=True, 
    dry_run=False
)
  • 参数解释:

    • name:str,策略名称,会在./persist/下生成一个同名目录,用于存放持久化的策略信息
    • source_code:str,策略代码,可从策略研究模块中直接复制,代码置于"""..."""中
    • capital_base: float,初始资金量
      • 如果接入了 TradeAPI对象,且 signal_mode=False,那么此参数无意义
    • frequency: str,策略频率,'DAILY'或 'MINUTE'
    • stock_market: str,策略类型,默认 'STOCK'
    • benchmark: str,基准指数
    • trade_api: TradeAPI对象,绑定需要仿真交易的资金账号
    • signal_mode: bool,(新增)默认为 True
      • signal_mode=True,此时策略实际上运行的时初始资金为 capital_base的模拟交易,委托撮合由策略框架处理,context、get_orders等方法返回的结果均为模拟交易中计算的数据,与资金账号的数据无关;策略在模拟交易撮合成交后,才会通过 trade_api下单至柜台(所以当前模式下无法进行撤单)
      • signal_mode=False,此时为仿真交易模式,此时策略中 context、get_orders等方法返回的结果均为从柜台查询,策略下单也会直接下至柜台
    • dry_run: bool,试运行,立即返回,默认为 False
    • recover_dt: bool或 str,(新增)是否断点运行,默认为 False
      • recover_dt=False,从当前时点开始执行,不从断定运行
      • recover_dt=True,从上次策略结束时点开始运行
      • recover_dt='today',从当日开始运行,此模式下只会补执行 before_trading与 open_auction,handle_bar依旧从当前时间开始执行
      • recover_dt='yyyyMMdd HH:mm',从指定时间开始运行
  • 注意事项:

    • 使用方法如上,trade_api可支持传入 rechase_api
    • 策略数据持久化会在策略运行过程中同步进行,以防止策略中断,数据丢失。存储数据包含账户数据、全局变量等,路径位于研究平台persist目录下,以策略名称为存储,当此目录下存在策略名相同的路径时,则不再执行 init,故启动新策略前,请确认没有persist下没有同名文件夹
    • 策略请在9:00前开启运行,中间中断则会跳过运行时间,如需要补充执行,可通过设置 recover_dt参数
    • 初始化 TradeAPI时需要指定下单策略 order_policy,MarketPolicy为最新价下单;LimitPolicy为限价下单。如未指定,由于策略下单时使用均价,可能存在多位小数,最终实盘账户下单的时候可能产生废单。
    • 若想在策略代码 source_code 中使用智能下单策略,不支持直接调用 trade_api.order,可以使用 style 入参实现:order('002109.SZ', 100, style=CustomOrder(price=0, pricetype=21))。
    • signal_mode=True时,如想在context中获得仿真账号的持仓、资金等数据,可以使用同步函数 sync_trade_api()
  • 示例:

from tick_trade_api import TradeAPI
#初始化TradeAPI时需要指定下单策略,MarketPolicy为最新价下单;LimitPolicy为限价下单
trade_api=TradeAPI('69271711', order_policy=LimitPolicy)

source_code="""
# 股票策略模版
def init(context):
    pass

# 盘前执行
def before_trading(context):
    pass

# 开盘时运行函数
def handle_bar(context, bar_dict):
    order_id = order('000001.SZ', 100)
    print(get_orders())
    try:
        cancel_order(order_id)
    except:
        print('撤单失败')
    print(get_open_orders())
    print(get_tradelogs())
    print(context.portfolio.stock_account)
    print(context.portfolio.positions)
"""
rtrade = research_trade(
    '研究环境策略',
     source_code,
     frequency='MINUTE', 
     trade_api=trade_api,
     signal_mode=False,
     recover_dt='today'
)

研究环境的使用

储存资源

  • 研究环境默认提供2G内存与1G硬盘,并支持扩展,如有扩展需求,可与我们的工作人员取得联系

外部数据

访问外网

  • SuperMind支持通过tushare、akshare等第三方数据接口获取数据,其他外网地址访问受限。

文件上传

  • 可以通过左上角上传按钮将本地文件上传到研究环境中

9343f2e558635c45161fbea712f0203e.png

研究环境关闭规则

  • 晚上23点至次日6点,1小时不活动会被关闭。不活动是至浏览器、客户端关闭,计算器休眠等情况
  • 研究环境最大生存时间为24小时
  • 对于SuperMind正式付费用户,可以申请延长研究环境的生存时间,具体请咨询工作人员

研究环境如何关闭?

python3.5 环境关闭研究环境:

9fbd560837573c043cb6c9e8c1c67147.png

python3.8 环境关闭研究环境:

bebbc999a616a3bf3dc20e3717be5154.png

关闭之后,重新进入研究环境就可以启动研究环境。

定时任务Crontab

定时任务是在研究环境开启的状态下,可由后台定时调起脚本,执行任务。

您的文件名称 (every 1minutes from 9.25am to 2.50pm).ipynb

您的文件名称 (every 1minutes from 9.25am to 2.50pm).py

用法:

  • 开通权限后Jupyter的home页面会有个crontabs文件夹,这个文件夹里的脚本会由后台自动调起

  • 通过命名来设定定时任务执行的方式,如

    • schedule (every 1minutes from 9.25am to 2.50pm).ipynb
      • 5cbd716fe3e4e2561bfee75c96e6d452.png
    • 括号内为具体参数,即9:25到2:50每分钟执行一次,前后时间一致则只执行一次,注意am,pm
    • 首次添加之后需要重启下研究环境 点此关闭 后 再开启:https://quant.10jqka.com.cn/notebook/hub/home
  • 定时任务不支持并行,会按时间顺序排队执行,如果超过设定时间则今日不会执行

  • crontabs.log中会显示增加任务、执行任务的日志,(notebook中并不会打印出日志)。

  • 支持设置超时时间如下(py文件也可以)

    helloworld (every 5minutes from 9.00am to 7.10pm) with {timeout:10}.ipynb
    # 或者
    helloworld (every 5minutes from 9.00am to 7.10pm) with {timeout:10}.py
    
  • 如果有2个文件命名如何命名?

    Crontab 文件的命名是可以变更的 括号里面按照要求即可

    2个的话 可以叫不一样的名字,默认会进行串行执行 按照名字进行排序执行。

    第一个叫:
    AAA (every 1minutes from 9.25am to 2.50pm).ipynb

    第二个叫:
    BBB (every 1minutes from 9.25am to 2.50pm).ipynb

  • 定时任务只有在研究平台保持开启的状态下才会执行,请关注研究平台状态

    • 开启即能看到界面。
      • b12a04395eeac78420fdff94a8250189.png
    • b12a04395eeac78420fdff94a8250189.png
    • 如需多个定时任务并行,请联系工作人员,并提供userid,获取方法见怎么获取userid

实盘可以云端托管么?

我们的研究环境是云端的,但是也必须开着Supermind客户端。因为为了安全,我们是不能在云端记录您的资金账号的,所以必须通过您的客户端来读取资金账号,所以必须开着客户端。请谅解。

那我没有条件一直开着电脑怎么办?

建议您购买一个最便宜的windows 云主机 ,一年可能只需要几百,比如一些用户会用天翼云。

这样的话即使没有条件一直开着电脑,也可以一直开着Supermind客户端

可以实现完全无人值守么?我可以一直不用管运行的程序么?

可以实现7天内的无人值守,

您需要确保以下3点:

1、客户端一直开启。

2、设定好Crontab定时任务,能在盘前自动开启脚本(因为如果用我们的和回测框架开发的策略,盘后会关闭。但如果自己开发的代码 如果能确保一直运行的话 就不需要开启crontab),crontab相关的知识请看:定时任务

3、申请好7天不重启服务器,付费购买之后,您可以向您对接的经理 或者 supermind社群群主申请 7天服务器不重启。(默认情况下是24小时会重启,更多关于服务器重启规则的描述请看:研究环境存活时间

我可以申请超过7天么?

不行,因为量化交易及其容易出现问题,我们建议至少7天要进服务器看下代码运行情况。以便能及时发现问题。

另外:我们正在研发更方便易用的实盘管理工具,届时就可以比较方便的实现无人值守,尽情期待!

为什么研究环境报“服务器不可用或无法访问”?

b2e0620bc604580dc84fdf583310163b.png

大部分通过云主机来运行客户端出现过这个问题,

确保云主机的防火墙关闭,如果还是有这个问题,

那么请开启经典模式:

5b28382f5814690adf42b0b43a497e5b.png

为什么研究环境卡住不断刷新?

当研究环境出现不断刷新的情况,说明内存爆满。点击此处进行关闭研究环境,并重新开启:

点此关闭研究环境(如果研究环境没有开启 是无法访问的)

研究环境可以网页运行?

是的,

如果不实盘的情况下 可以用网页运行:http://quant.10jqka.com.cn/platform/html/study-research.html

如果需要仿真或者实盘需要下载客户端进行:

支持仿真模拟实盘的客户端:https://download.10jqka.com.cn/index/download/id/709

(仿真实盘和真实实盘的差异 在于用的资金账号是模拟的还是真实的,其他流程都一致,建议真实实盘之前用仿真的测试好策略)

支持实盘的客户端:需要付费之后提供。

研究环境的快捷键有哪些?

http://quant.10jqka.com.cn/view/article/2998

为什么会内存爆满?

可能因为您开启了过多的文档,建议不要开启很多文档,文档很消耗内存。

如何关闭?

注意仅关闭打开页面不会释放内存,关闭内核才会释放内存。

a08355cd035104bdbb244c5bdc7f02aa.png

另外如果你开启了较多的策略 也会消耗内存。基本上一个策略会消耗0.5G 到1G之间的内存。如果有很多的数据读取和处理逻辑可能会更大。

云端2G内存,不够怎么办?

关闭不需要的进程
13f0bbf9f3be4e4c39714de6cf42d730.png

升级配置
可以联系微信群主 进行联系购买:打开论坛首页 扫描右上角二维码 进群找到群主:[http://quant.10jqka.com.cn/view/community]

11514ef590a7eca41d05e7145e306fc9.png

为什么打开文件会报错

b8a4cee489d9ad57789d3101c62b193f.png

出于服务器安全性考虑,部分系统函数不能使用,量化平台提供了替代函数,参照文档

为什么不能使用os,sys等包

出于服务器安全性考虑,涉及系统级操作的包都被禁用

怎么获取userid

方法一
先在网页上登录

方法二
研究环境3.8
85fe9742bf6249eba9512f6553313b6b.png

5508d873cdc6958b3f8093c27b7f4533.png

研究环境3.5
48c90459581d02273d7c2ea103edf67d.png

5508d873cdc6958b3f8093c27b7f4533.png

上一节
下一节
编组 4

回到

顶部