回测的仓位不正确

策略分享
新手专区
标签: #<Tag:0x00007fb3d4d6ff58> #<Tag:0x00007fb3d4d6fdc8>

(moonlit) #1

发现策略的回测有这样的问题
首先 trade 是:
order_price_field_buy=‘open’,
order_price_field_sell=‘close’,
开盘买入,收盘卖出
每月交易一次

交易日时候:
第一次是 a ,b ,c,d 4个股票 ,等权重,满仓
要等权重买入 e,f,g,h 4个股票
理论交易日后,应该是空仓,或者很低仓位
但回测结果发现 是e,f,g,h 4个股票满仓

克隆策略
In [13]:
import pandas as pd
import numpy as np

instruments = D.instruments()

# # 确定起始时间
start_date = '2018-10-08'

# 确定结束时间
end_date = '2018-11-15'

def get_buy_list(date="",portfolio_value=0.0):
    if date == '2018-10-08' :
        return ['002159.SZA', '603518.SHA', '603520.SHA', '002718.SZA']
    else :
        return ['600810.SHA' ,'603018.SHA' ,'002746.SZA', '603339.SHA']
    

# 回测参数设置,initialize函数只运行一次
def initialize(context):
    # 手续费设置
    context.set_commission(PerOrder(buy_cost=0.0001, sell_cost=0.0001, min_cost=5)) 
    # 调仓规则(每月的第一天调仓)
    context.schedule_function(rebalance, date_rule=date_rules.month_start(days_offset=0)) 
#     context.schedule_function(rebalance, date_rule=date_rules.month_end()) 
    # 传入 整理好的调仓股票数据
#     context.daily_buy_stock = daily_buy_stock
    set_long_only()
#     set_max_leverage(1)  

#     print(context.daily_buy_stock)

# handle_data函数会每天运行一次
def handle_data(context,data):
    if data.current_dt.strftime('%Y-%m-%d') == end_date :
        rebalance(context,data)
        # 打印持仓的股票
        positions = context.portfolio.positions
        for equity in positions:
            position = positions[equity]
            print(position.sid)
            
# 换仓函数
def rebalance(context, data):
    # 当前的日期
    date = data.current_dt.strftime('%Y-%m-%d')
    # 根据日期获取调仓需要买入的股票的列表
    print(date)

    stock_to_buy = get_buy_list(date,portfolio_value=context.portfolio.portfolio_value)    
    
    print(stock_to_buy)
    
    # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表
    stock_hold_now = [equity.symbol for equity in context.portfolio.positions]
    # 继续持有的股票:调仓时,如果买入的股票已经存在于目前的持仓里,那么应继续持有
    no_need_to_sell = [i for i in stock_hold_now if i in stock_to_buy]
    # 需要卖出的股票
    stock_to_sell = [i for i in stock_hold_now if i not in no_need_to_sell]
  
    # 卖出
    for stock in stock_to_sell:
        # 如果该股票停牌,则没法成交。因此需要用can_trade方法检查下该股票的状态
        # 如果返回真值,则可以正常下单,否则会出错
        # 因为stock是字符串格式,我们用symbol方法将其转化成平台可以接受的形式:Equity格式

        if data.can_trade(context.symbol(stock)):
            # order_target_percent是平台的一个下单接口,表明下单使得该股票的权重为0,
            #   即卖出全部股票,可参考回测文档
            context.order_target_percent(context.symbol(stock), 0)
    
    # 如果当天没有买入的股票,就返回
    if len(stock_to_buy) == 0:
        return

    # 等权重买入 
    weight =  1 / len(stock_to_buy)
    
    # 买入
    for stock in stock_to_buy:
        if data.can_trade(context.symbol(stock)):
            # 下单使得某只股票的持仓权重达到weight,因为
            # weight大于0,因此是等权重买入
            context.order_target_percent(context.symbol(stock), weight)

m = M.trade.v3(
    instruments=instruments,
    start_date=start_date,
    end_date=end_date,
    initialize=initialize,
    handle_data=handle_data,
    # 买入订单以开盘价成交
    order_price_field_buy='open',
    # 卖出订单以开盘价成交
    order_price_field_sell='close',
    # 策略本金    
    capital_base= 1000000,
    # 比较基准:沪深300
    benchmark='000300.INDX',
    # 传入数据给回测模块,所有回测函数里用到的数据都要从这里传入,并通过 context.options 使用,否则可能会遇到缓存问题
    m_cached=False,
    volume_limit=0,    
)

            
[2019-02-19 11:36:53.976871] INFO: algo: TradingAlgorithm V1.4.6
[2019-02-19 11:37:04.695433] INFO: algo: trading transform...
2018-10-08
['002159.SZA', '603518.SHA', '603520.SHA', '002718.SZA']
2018-11-01
['600810.SHA', '603018.SHA', '002746.SZA', '603339.SHA']
2018-11-15
['600810.SHA', '603018.SHA', '002746.SZA', '603339.SHA']
Equity(552 [603018.SHA])
Equity(2240 [002746.SZA])
Equity(1779 [603339.SHA])
Equity(287 [600810.SHA])
[2019-02-19 11:37:04.790394] INFO: Performance: Simulated 29 trading days out of 29.
[2019-02-19 11:37:04.791298] INFO: Performance: first open: 2018-10-08 09:30:00+00:00
[2019-02-19 11:37:04.792069] INFO: Performance: last close: 2018-11-15 15:00:00+00:00
  • 收益率1.39%
  • 年化收益率12.75%
  • 基准收益率-5.71%
  • 阿尔法0.64
  • 贝塔0.96
  • 夏普比率0.42
  • 胜率0.0
  • 盈亏比0.0
  • 收益波动率40.6%
  • 信息比率0.16
  • 最大回撤12.9%

(iQuant) #2

收到您的提问,已提交给策略工程师,我们会尽快为您回复。


(达达) #3
克隆策略
In [13]:
import pandas as pd
import numpy as np

instruments = D.instruments()

# # 确定起始时间
start_date = '2018-10-08'

# 确定结束时间
end_date = '2018-11-15'

def get_buy_list(date="",portfolio_value=0.0):
    if date == '2018-10-08' :
        return ['002159.SZA', '603518.SHA', '603520.SHA', '002718.SZA']
    else :
        return ['600810.SHA' ,'603018.SHA' ,'002746.SZA', '603339.SHA']
    

# 回测参数设置,initialize函数只运行一次
def initialize(context):
    # 手续费设置
    context.set_commission(PerOrder(buy_cost=0.0001, sell_cost=0.0001, min_cost=5)) 
    # 调仓规则(每月的第一天调仓)
    context.schedule_function(rebalance, date_rule=date_rules.month_start(days_offset=0)) 
#     context.schedule_function(rebalance, date_rule=date_rules.month_end()) 
    # 传入 整理好的调仓股票数据
#     context.daily_buy_stock = daily_buy_stock
    set_long_only()
#     set_max_leverage(1)  

#     print(context.daily_buy_stock)

# handle_data函数会每天运行一次
def handle_data(context,data):
    if data.current_dt.strftime('%Y-%m-%d') == end_date :
        rebalance(context,data)
        # 打印持仓的股票
        positions = context.portfolio.positions
        for equity in positions:
            position = positions[equity]
            print(position.sid)
            
# 换仓函数
def rebalance(context, data):
    # 当前的日期
    date = data.current_dt.strftime('%Y-%m-%d')
    # 根据日期获取调仓需要买入的股票的列表
    print(date)

    stock_to_buy = get_buy_list(date,portfolio_value=context.portfolio.portfolio_value)    
    
    print(stock_to_buy)
    
    # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表
    stock_hold_now = [equity.symbol for equity in context.portfolio.positions]
    # 继续持有的股票:调仓时,如果买入的股票已经存在于目前的持仓里,那么应继续持有
    no_need_to_sell = [i for i in stock_hold_now if i in stock_to_buy]
    # 需要卖出的股票
    stock_to_sell = [i for i in stock_hold_now if i not in no_need_to_sell]
  
    # 卖出
    for stock in stock_to_sell:
        # 如果该股票停牌,则没法成交。因此需要用can_trade方法检查下该股票的状态
        # 如果返回真值,则可以正常下单,否则会出错
        # 因为stock是字符串格式,我们用symbol方法将其转化成平台可以接受的形式:Equity格式

        if data.can_trade(context.symbol(stock)):
            # order_target_percent是平台的一个下单接口,表明下单使得该股票的权重为0,
            #   即卖出全部股票,可参考回测文档
            context.order_target_percent(context.symbol(stock), 0)
    
    # 如果当天没有买入的股票,就返回
    if len(stock_to_buy) == 0:
        return

    # 等权重买入 
    weight =  1 / len(stock_to_buy)
    
    # 买入
    for stock in stock_to_buy:
        if data.can_trade(context.symbol(stock)):
            # 下单使得某只股票的持仓权重达到weight,因为
            # weight大于0,因此是等权重买入
            context.order_target_percent(context.symbol(stock), weight)

m = M.trade.v3(
    instruments=instruments,
    start_date=start_date,
    end_date=end_date,
    initialize=initialize,
    handle_data=handle_data,
    # 买入订单以开盘价成交
    order_price_field_buy='open',
    # 卖出订单以开盘价成交
    order_price_field_sell='close',
    # 策略本金    
    capital_base= 1000000,
    # 比较基准:沪深300
    benchmark='000300.INDX',
    # 传入数据给回测模块,所有回测函数里用到的数据都要从这里传入,并通过 context.options 使用,否则可能会遇到缓存问题
    m_cached=False,
    volume_limit=0,    
)

            
[2019-02-19 11:36:53.976871] INFO: algo: TradingAlgorithm V1.4.6
[2019-02-19 11:37:04.695433] INFO: algo: trading transform...
2018-10-08
['002159.SZA', '603518.SHA', '603520.SHA', '002718.SZA']
2018-11-01
['600810.SHA', '603018.SHA', '002746.SZA', '603339.SHA']
2018-11-15
['600810.SHA', '603018.SHA', '002746.SZA', '603339.SHA']
Equity(552 [603018.SHA])
Equity(2240 [002746.SZA])
Equity(1779 [603339.SHA])
Equity(287 [600810.SHA])
[2019-02-19 11:37:04.790394] INFO: Performance: Simulated 29 trading days out of 29.
[2019-02-19 11:37:04.791298] INFO: Performance: first open: 2018-10-08 09:30:00+00:00
[2019-02-19 11:37:04.792069] INFO: Performance: last close: 2018-11-15 15:00:00+00:00
  • 收益率1.39%
  • 年化收益率12.75%
  • 基准收益率-5.71%
  • 阿尔法0.64
  • 贝塔0.96
  • 夏普比率0.42
  • 胜率0.0
  • 盈亏比0.0
  • 收益波动率40.6%
  • 信息比率0.16
  • 最大回撤12.9%

(moonlit) #4

现在看到问题还是没解决啊。还是有问题。
以前是正常的。


(iQuant) #5

已提交给策略工程师了哈,稍后让他再帮您看一下。


(达达) #6

您好 依据您的逻辑是月初换仓,打印了日期和买入计划可以看到的确是每个月的月初第会下单换仓,而您说的第二天不知道是什么意思?请描述清楚问题。如果您意思是仓位管理方面早盘买入没有可用现金,这个请您自己编写逻辑。例如:早盘买尾盘卖,那么策略中每次买入的量建议是半仓,否则是无法和现实情况一致的,您可以考虑在策略中首先获取可用现金context,portfolio.cash用这个现金执行买入的逻辑资金分配,同时建议每次买入的资金用半仓这样才会满足早盘有可用资金的问题。关于早盘买尾盘卖和早盘卖尾盘买的例子您可以参考模板策略案例。
您可能会有疑问:1、卖了的股票系统会自动控制不买入么? 系统不会,请自己将卖出的股票记录在list中,在买入逻辑中过滤判断如果在list中就跳过该票的买入。
2、系统会自动判断根据早盘买还是尾盘买判断可用现金么?会自动半仓轮动么?系统不会,请参考模板策略,通过context.portfolio.cash可用现金进行记录 控制买卖流程
可以参考下面这样修改

克隆策略
In [5]:
import pandas as pd
import numpy as np

instruments = D.instruments()

# # 确定起始时间
start_date = '2018-10-08'

# 确定结束时间
end_date = '2018-11-15'

def get_buy_list(date="",portfolio_value=0.0):
    if date == '2018-10-08' :
        return ['002159.SZA', '603518.SHA', '603520.SHA', '002718.SZA']
    else :
        return ['600810.SHA' ,'603018.SHA' ,'002746.SZA', '603339.SHA']
    

# 回测参数设置,initialize函数只运行一次
def initialize(context):
    # 手续费设置
    context.set_commission(PerOrder(buy_cost=0.0001, sell_cost=0.0001, min_cost=5)) 
    # 调仓规则(每月的第一天调仓)
    context.schedule_function(rebalance, date_rule=date_rules.month_start(days_offset=0)) 
    set_long_only()

# handle_data函数会每天运行一次
def handle_data(context,data):
    pass
            
# 换仓函数
def rebalance(context, data):
    # 当前的日期
    date = data.current_dt.strftime('%Y-%m-%d')
    # 根据日期获取调仓需要买入的股票的列表

    stock_to_buy = get_buy_list(date,portfolio_value=context.portfolio.portfolio_value)    
    
    print(date,'计划买入股票',stock_to_buy)
    
    # 通过positions对象,使用列表生成式的方法获取目前持仓的股票列表
    stock_hold_now = [equity.symbol for equity in context.portfolio.positions]
    # 继续持有的股票:调仓时,如果买入的股票已经存在于目前的持仓里,那么应继续持有
    no_need_to_sell = [i for i in stock_hold_now if i in stock_to_buy]
    # 需要卖出的股票
    stock_to_sell = [i for i in stock_hold_now if i not in no_need_to_sell]
  
    # 卖出
    for stock in stock_to_sell:
        # 如果该股票停牌,则没法成交。因此需要用can_trade方法检查下该股票的状态
        # 如果返回真值,则可以正常下单,否则会出错
        # 因为stock是字符串格式,我们用symbol方法将其转化成平台可以接受的形式:Equity格式

        if data.can_trade(context.symbol(stock)):
            # order_target_percent是平台的一个下单接口,表明下单使得该股票的权重为0,
            #   即卖出全部股票,可参考回测文档
            context.order_target_percent(context.symbol(stock), 0)
    
    # 如果当天没有买入的股票,就返回
    if len(stock_to_buy) == 0:
        return

    # 等权重买入 
    weight =  1 / len(stock_to_buy)
    
    # 买入
    for stock in stock_to_buy:
        if data.can_trade(context.symbol(stock)):
            # 下单使得某只股票的持仓权重达到weight,因为
            # weight大于0,因此是等权重买入
            context.order_target_percent(context.symbol(stock), weight)

m = M.trade.v3(
    instruments=instruments,
    start_date=start_date,
    end_date=end_date,
    initialize=initialize,
    handle_data=handle_data,
    # 买入订单以开盘价成交
    order_price_field_buy='open',
    # 卖出订单以开盘价成交
    order_price_field_sell='close',
    # 策略本金    
    capital_base= 1000001,
    # 比较基准:沪深300
    benchmark='000300.INDX',
    # 传入数据给回测模块,所有回测函数里用到的数据都要从这里传入,并通过 context.options 使用,否则可能会遇到缓存问题
    m_cached=False,
    volume_limit=0,    
)
2018-10-08 计划买入股票 ['002159.SZA', '603518.SHA', '603520.SHA', '002718.SZA']
2018-11-01 计划买入股票 ['600810.SHA', '603018.SHA', '002746.SZA', '603339.SHA']
  • 收益率1.39%
  • 年化收益率12.75%
  • 基准收益率-5.71%
  • 阿尔法0.64
  • 贝塔0.96
  • 夏普比率0.42
  • 胜率0.0
  • 盈亏比0.0
  • 收益波动率40.6%
  • 信息比率0.16
  • 最大回撤12.9%

(moonlit) #7

我的意思就不涉及现实情况。

问题是:
回测中,已经满仓的前提下,trade 方法设置了开盘买入,收盘卖出,就理应交易日时候,开盘无法买入任何股票,只会在收盘的时候卖出,但实际上执行出来的结果是,既满仓买入,又全部卖出。
这个不符合trade 方法的设计原意吧?
原来是没有问题的,现在就不对了。


(达达) #8

您好,这个原来也是一样的。引擎的开盘买和收盘卖只是去拿价格,但是撮合下单没有按照真实交易过程的先后顺序,你在策略中先编写卖出的逻辑就会先触发撮合卖单,一旦卖单撮合了可用资金就产生了就会用于买单撮合。所以你设置的买入和卖出只是价格,不是单子成交顺序。引擎的设计逻辑可能和你的理解不一致。所以建议您通过控制市值、现金等变量进行控制交易。如果您是先卖后买,那么会多出来一部分卖出股票获得的可用现金,在使用order_target_percent的时候就要注意会满仓。建议使用order_target_value设置买入操作,这个order_target_value是用卖出逻辑前获取的context.portfolio.cash作为可用现金分配给买入的股票


(moonlit) #9

您好,你们的模拟交易的仓位又是正确的,策略回测里面又是不正确的。而且这个问题是最近发现的。
建议对比一下模拟交易和回测


(达达) #10

问题已经解决,引擎的撮合顺序先卖单后买单,之前的模拟交易和回测顺序不一致,现在已经一致了。