# R-Breaker短线日内交易策略
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
"""  
示例策略仅供参考，不建议直接实盘使用。  
  
R-Breaker是一种短线日内交易策略。  
策略根据前一个交易日的收盘价、最高价和最低价数据通过一定方式计算出六个价位，从大到小依次为：突破买入价、观察卖出价、反转卖出价、反转买入、观察买入价、突破卖出价。  
以此来形成当前交易日盘中交易的触发条件，追踪盘中价格走势，实时判断触发条件。  
具体条件如下：  
突破  
在空仓条件下，如果盘中价格超过突破买入价，则采取趋势策略，即在该点位开仓做多。  
在空仓条件下，如果盘中价格跌破突破卖出价，则采取趋势策略，即在该点位开仓做空。  
反转  
持多单，当开仓后的日内最高价超过观察卖出价后，盘中价格出现回落，且进一步跌破反转卖出价构成的支撑线时，采取反转策略，即在该点位反手做空。  
持空单，当开仓后的日内最低价低于观察买入价后，盘中价格出现反弹，且进一步超过反转买入价构成的阻力线时，采取反转策略，即在该点位反手做多。  
设定止损条件。当亏损达到设定值后，平仓；尾盘平仓。  
"""  
  
def init(context):  
    # 设置交易品种  
    context.symbol = 'SHFE.AG'  
    # 设置止损点数  
    context.stopLossPrice = 50      
context.holding_data = {'high':None,'low':None}      # 开仓后，持仓以来的最高价、最低价  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 如果是交易时间段，等到开盘时间确保进入algo()  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约      
if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 订阅行情  
    subscribe(context.main_contract, '60s', count=1, fields='symbol,eob,high,low,close', unsubscribe_previous=True,format='row')    
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取历史数据  
    if context.now.hour>=20:  
        # 当天夜盘和次日日盘属于同一天数据，为此当天夜盘的开盘价调用第二天的开盘价  
        next_date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
        data = history_n(symbol=context.main_contract, frequency='1d',end_time=next_date, fields='high,low,open,symbol,close', count=2, df=True)  
    else:  
        data = history_n(symbol=context.main_contract, frequency='1d',end_time=context.now, fields='high,low,open,symbol,close', count=2, df=True)  
  
    high = data['high'].iloc[0]  # 前一日的最高价  
    low = data['low'].iloc[0]  # 前一日的最低价  
    close = data['close'].iloc[0]  # 前一日的收盘价  
    pivot = (high + low + close) / 3  # 枢轴点  
  
    context.bBreak = high + 2 * (pivot - low)            # 突破买入价  
    context.sSetup = pivot + (high - low)                # 观察卖出价  
    context.sEnter = 2 * pivot - low                     # 反转卖出价  
    context.bEnter = 2 * pivot - high                    # 反转买入价  
    context.bSetup = pivot - (high - low)                # 观察买入价  
    context.sBreak = low - 2 * (high - pivot)            # 突破卖出价  
  
  
def on_bar(context, bars):  
    # 更新数据  
    new_data = context.data(symbol=bars[0].symbol, frequency='60s', count=1)[0]  
    if context.holding_data['high'] is not None:  
        context.holding_data['high'] = max(context.holding_data['high'],new_data['high'])  
    if context.holding_data['low'] is not None:  
        context.holding_data['low'] = min(context.holding_data['low'],new_data['low'])  
  
    # 尾盘平仓  
    if context.now.hour == 14 and context.now.minute >= 59 or context.now.hour == 15:  
        positions = get_position()  
        if len(positions):  
            print(context.now,'尾盘平仓')  
            order_close_all()  
  
    # 非尾盘的交易逻辑  
    else:  
        # 获取止损价  
        STOP_LOSS_PRICE = context.stopLossPrice  
  
        # 获取现有持仓  
        positions = get_position()  
        position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位  
        position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
        # 买卖逻辑  
        if not position_long and not position_short:  # 空仓条件下  
            if bars[0].close > context.bBreak:  # 做多  
                print(context.now,'突破开多仓')  
                # 在空仓的情况下，如果盘中价格超过突破买入价，则采取趋势策略，即在该点位开仓做多  
                order_volume(symbol=context.main_contract, volume=10, side=OrderSide_Buy,  
                            order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=bars[0].close)  
                context.open_position_price = bars[0].close  
                context.holding_data['high'] = bars[0].close  
                context.holding_data['low'] = bars[0].close  
  
            elif bars[0].close < context.sBreak:  # 做空  
                print(context.now,'突破开空仓')  
                # 在空仓的情况下，如果盘中价格跌破突破卖出价，则采取趋势策略，即在该点位开仓做空  
                order_volume(symbol=context.main_contract, volume=10, side=OrderSide_Sell,  
                            order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=bars[0].close)  
                context.open_position_price = bars[0].close  
                context.holding_data['high'] = bars[0].close  
                context.holding_data['low'] = bars[0].close  
  
        else:  # 有持仓时，设置止损条件  
            # 开仓价与当前行情价之差大于止损点则止损  
            if (position_long and context.open_position_price - bars[0].close >= STOP_LOSS_PRICE) or \  
                    (position_short and bars[0].close - context.open_position_price >= STOP_LOSS_PRICE):  
                print(context.now,'平仓止损')  
                order_close_all()  # 平仓  
  
            # 反转策略:  
            if position_long:  # 多仓条件下  
                if context.holding_data['high'] > context.sSetup and bars[0].close < context.sEnter:  
                    # 多头持仓,当日内最高价超过观察卖出价后，  
                    # 盘中价格出现回落，且进一步跌破反转卖出价构成的支撑线时，  
                    # 采取反转策略，即在该点位反手做空  
                    print(context.now, '多头反转，平多仓并开空仓')  
                    order_close_all()  # 平仓  
                    order_volume(symbol=context.main_contract, volume=10, side=OrderSide_Sell,  
                                order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=bars[0].close)  # 做空  
                    context.open_position_price = bars[0].close  
                    context.holding_data['high'] = bars[0].close  
                    context.holding_data['low'] = bars[0].close  
  
            elif position_short:  # 空头持仓  
                if context.holding_data['low'] < context.bSetup and bars[0].close > context.bEnter:  
                    # 空头持仓，当日内最低价低于观察买入价后，  
                    # 盘中价格出现反弹，且进一步超过反转买入价构成的阻力线时，  
                    # 采取反转策略，即在该点位反手做多  
                    print(context.now, '空头反转，平空仓并开多仓')  
                    order_close_all()  # 平仓  
                    order_volume(symbol=context.main_contract, volume=10, side=OrderSide_Buy,  
                                order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=bars[0].close)  # 做多  
                    context.open_position_price = bars[0].close  
                    context.holding_data['high'] = bars[0].close  
                    context.holding_data['low'] = bars[0].close  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='8c544016-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=200000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 双均线策略
### coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *


"""
双均线策略
当短期均线上穿长期均线时买入，当短期均线下穿长期均线时卖出
"""

def init(context):
    # 设置均线参数
    context.short_period = 5   # 短期均线周期
    context.long_period = 20   # 长期均线周期
    
    # 设置交易标的
    context.symbol = 'SHSE.600004'
    
    # 计算所需的最大周期+1
    context.period = max(context.short_period, context.long_period) + 1
    
    # 订阅行情数据
    subscribe(symbols=context.symbol, frequency='1d', count=context.period)

def on_bar(context, bars):
    # 获取历史数据
    data = context.data(symbol=context.symbol, 
                       frequency='1d', 
                       count=context.period, 
                       fields='close')
    
    # 计算均线
    short_ma = data['close'].rolling(context.short_period).mean()
    long_ma = data['close'].rolling(context.long_period).mean()
    
    # 获取当前持仓
    pos = list(filter(lambda x: x['symbol'] == context.symbol, get_position()))
    
    # 交易逻辑
    # 金叉条件：短期均线上穿长期均线
    cross_over = (short_ma.values[-2] < long_ma.values[-2]) and (short_ma.values[-1] > long_ma.values[-1])
    
    # 死叉条件：短期均线下穿长期均线
    cross_down = (short_ma.values[-2] > long_ma.values[-2]) and (short_ma.values[-1] < long_ma.values[-1])
    
    # 下单操作
    if cross_over and not pos:  # 金叉且无持仓时买入
        order_volume(symbol=context.symbol, 
                     volume=100, 
                     side=OrderSide_Buy,
                     order_type=OrderType_Market,
                     position_effect=PositionEffect_Open)
        
    elif cross_down and pos:  # 死叉且有持仓时卖出
        order_volume(symbol=context.symbol, 
                     volume=100, 
                     side=OrderSide_Sell,
                     order_type=OrderType_Market,
                     position_effect=PositionEffect_Close)

### 以下函数保持原有模板不变
def on_order_status(context, order):
    if order['status'] == 3:
        print("订单成交：", order)

def on_backtest_finished(context, indicator):
    print('回测完成')
    print(indicator)

if __name__ == '__main__':
    run(strategy_id='your_strategy_id',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='your_token',
        backtest_start_time='2020-01-01 08:00:00',
        backtest_end_time='2023-12-31 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=100000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)

# 布林线均值回归
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
  
"""  
示例策略仅供参考，不建议直接实盘使用。  
  
本策略采用布林线进行均值回归交易。当价格触及布林线上轨的时候进行卖出，当触及下轨的时候，进行买入。  
"""  
  
  
### 策略中必须有init方法  
def init(context):  
    # 设置布林线的三个参数  
    context.maPeriod = 26  # 计算BOLL布林线中轨的参数  
    context.stdPeriod = 26  # 计算BOLL 标准差的参数  
    context.stdRange = 1  # 计算BOLL 上下轨和中轨距离的参数  
    # 设置要进行回测的合约  
    context.symbol = 'SHSE.600004'  # 订阅&交易标的, 此处订阅的是600004  
    context.period = max(context.maPeriod, context.stdPeriod, context.stdRange) + 1  # 订阅数据滑窗长度  
    # 订阅行情  
    subscribe(symbols= context.symbol, frequency='1d', count=context.period)  
  
  
def on_bar(context, bars):  
    # 获取数据滑窗，只要在init里面有订阅，在这里就可以取的到，返回值是pandas.DataFrame  
    data = context.data(symbol=context.symbol, frequency='1d', count=context.period, fields='close')  
  
    ## 计算布林带  
    # 标准差  
    std = data['close'].rolling(context.stdPeriod).std()  
    # 均值  
    mean = data['close'].rolling(context.maPeriod).mean()  
    # 布林带上轨  
    bollUpper =  mean + context.stdRange*std   
    # 布林带下轨  
    bollBottom = mean - context.stdRange*std  
  
    # 获取现有持仓  
    pos = list(filter(lambda x:x['symbol']==context.symbol,get_position()))  
      
    # 交易逻辑与下单  
    # 当有持仓，且股价穿过BOLL上界的时候卖出股票。  
    if pos and data.close.values[-1] > bollUpper.values[-1] and data.close.values[-2] <= bollUpper.values[-2]:  
        order_volume(symbol=context.symbol, volume=100, side=OrderSide_Sell,  
                        order_type=OrderType_Limit, position_effect=PositionEffect_Close, price=data.close.values[-1])  
    # 当没有持仓，且股价穿过BOLL下界的时候买出股票。  
    elif not pos and data.close.values[-1] < bollBottom.values[-1] and data.close.values[-2] >= bollBottom.values[-2]:  
        order_volume(symbol=context.symbol, volume=100, side=OrderSide_Buy,  
                        order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=data.close.values[-1])  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='886b4743-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2010-01-01 08:00:00',  
        backtest_end_time='2020-12-31 15:30:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 定时下单
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
  
def init(context):  
    # 每天14:50 定时执行algo任务,  
    # algo执行定时任务函数，只能传context参数  
    # date_rule执行频率，目前暂时支持1d、1w、1m，其中1w、1m仅用于回测，实时模式1d以上的频率，需要在algo判断日期  
    # time_rule执行时间， 注意多个定时任务设置同一个时间点，前面的定时任务会被后面的覆盖  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:50:00')  
  
  
def algo(context):  
    # 以市价购买200股浦发银行股票， price为保护限价  
    order_volume(symbol='SHSE.600000', volume=200, side=OrderSide_Buy,  
                 order_type=OrderType_Market, position_effect=PositionEffect_Open, price=0)  
  
  
## 查看最终的回测结果  
def on_backtest_finished(context, indicator):  
    print(indicator)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='21678bd6-ede3-11ef-ab84-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2020-11-01 08:00:00',  
        backtest_end_time='2020-11-10 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 跨品种套利策略
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨品种套利是指利用两种不同、但相互关联的资产间的价格差异进行套利交易。  
本策略以焦炭主连和焦煤主连为标的，以布林带的形式，在价格偏离过大时开仓，在价格偏离回归正常时平仓。  
'''  
  
  
def init(context):  
    # 选择的两个合约  
    context.contract_A = 'DCE.J'  
    context.contract_B = 'DCE.JM'      
context.main_contract_A = None# 主力合约  
    context.main_contract_B = None# 主力合约  
    # 回溯周期,30个bar  
    context.periods_time = 30  
    # 布林带上下轨倍数  
    context.boll_multiple = 1  
    # 止盈止损倍数  
    context.stoppoint_multiple = 4  
    # 清仓信号  
    context.close_all = False  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_A_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        main_contract_B_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_A_list)>0:  
            context.main_contract_A_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_A_list}  
        if len(main_contract_B_list)>0:  
            context.main_contract_B_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_B_list}  
    # 设置定时任务：夜盘21点开始，日盘9点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.main_contract_A_list and date in context.main_contract_B_list:  
        main_contract_A = context.main_contract_A_list[date]  
        main_contract_B = context.main_contract_B_list[date]  
    else:  
        main_contract_A = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)[0]['symbol']  
        main_contract_B = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if main_contract_A!=context.main_contract_A or main_contract_B!=context.main_contract_B:  
        if Account_positions:  
            for posi in Account_positions:  
                if context.main_contract_A==posi['symbol'] and main_contract_A!=context.main_contract_A:  
                    print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],main_contract_A))  
                    context.close_all = True  
                if context.main_contract_B==posi['symbol'] and main_contract_B!=context.main_contract_B:  
                    print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],main_contract_B))  
                    context.close_all = True  
        # 更新主力合约  
        context.main_contract_A = main_contract_A  
        context.main_contract_B = main_contract_B   
        # 合约乘数  
        context.multiplier_A = get_symbols(sec_type1=1040, symbols=context.main_contract_A)[0]['multiplier']  
        context.multiplier_B = get_symbols(sec_type1=1040, symbols=context.main_contract_B)[0]['multiplier']  
    # 当context.close_all为True时，清仓  
    if context.close_all:  
        context.close_all = False  
        order_close_all()  
        print('{}:平仓'.format(context.now))  
  
    # 数据提取  
    close_A = history_n(symbol=context.main_contract_A,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    close_B = history_n(symbol=context.main_contract_B,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    # 提取最新价差  
    new_price = close_A.iloc[-1]*context.multiplier_A - close_B.iloc[-1]*context.multiplier_B  
  
    # 计算布林带：历史价差,上下限，止损点  
    spread_history = close_A*context.multiplier_A - close_B*context.multiplier_B# 历史价差  
    spread_history_mean = np.mean(spread_history)# 历史价差的均值  
    spread_history_std = np.std(spread_history)# 历史价差的标准差  
    upper = spread_history_mean + context.boll_multiple * spread_history_std  
    lower = spread_history_mean - context.boll_multiple * spread_history_std  
    upper_stoppoint = spread_history_mean + context.stoppoint_multiple * spread_history_std  
    lower_stoppoint = spread_history_mean - context.stoppoint_multiple * spread_history_std  
  
    # 查持仓  
    positions = get_position()  
    position_j_long = list(filter(lambda x:x['symbol']==context.main_contract_A and x['side']==PositionSide_Long,positions))        # 多头仓位  
    position_j_short = list(filter(lambda x:x['symbol']==context.main_contract_A and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
    # 设计开仓信号  
    if not position_j_short and not position_j_long:  
        if new_price > upper:  
            print('{}:做空价差组合(买入{}，卖出{})'.format(context.now,context.main_contract_B,context.main_contract_A))  
            price_A = current(symbols=context.main_contract_A)[0]['price']  
            price_B = current(symbols=context.main_contract_B)[0]['price']  
            order_volume(symbol=context.main_contract_A,side=OrderSide_Sell,volume=1,order_type=OrderType_Limit, price=price_A, position_effect=PositionEffect_Open)  
            order_volume(symbol=context.main_contract_B, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, price=price_B, position_effect=PositionEffect_Open)  
  
        if new_price < lower:  
            print('{}:做多价差组合(买入{}，卖出{})'.format(context.now,context.main_contract_A,context.main_contract_B))  
            price_A = current(symbols=context.main_contract_A)[0]['price']  
            price_B = current(symbols=context.main_contract_B)[0]['price']  
            order_volume(symbol=context.main_contract_A, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, price=price_A, position_effect=PositionEffect_Open)  
            order_volume(symbol=context.main_contract_B, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, price=price_B, position_effect=PositionEffect_Open)  
  
    # 设计平仓信号  
    # 做多价差组合时  
    if position_j_long:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if new_price >= spread_history_mean or new_price <= lower_stoppoint:  
            order_close_all()  
            print('{}:平仓'.format(context.now))  
  
    # 做空价差组合时  
    if position_j_short:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if new_price <= spread_history_mean or new_price >= upper_stoppoint:  
            order_close_all()  
            print('{}:平仓'.format(context.now))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='90051f52-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=200000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 跨期套利策略
## coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *

import datetime
import numpy as np
import pandas as pd

'''
示例策略仅供参考，不建议直接实盘使用。

跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差突破上轨时做空价差组合,突破下轨时做多价差组合。
'''


def init(context):
    context.contract_A = 'DCE.J'# 主力合约
    context.contract_B = 'DCE.J22'# 次主力合约
    context.main_contract = None# 具体的主力合约
    context.minor_contract = None# 具体的次主力合约
    # 设置回溯周期
    context.periods_time = 31
    # 布林带上下轨倍数
    context.boll_multiple = 1
    # 止盈止损倍数
    context.stoppoint_multiple = 4        
    # 清仓信号
    context.close_all = False
    # 数据一次性获取
    if context.mode==MODE_BACKTEST:
        main_contract_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])
        minor_contract_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])
        if len(main_contract_list)>0:
            context.main_contract_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_list}
        if len(minor_contract_list)>0:
            context.minor_contract_list = {dic['trade_date']:dic['symbol'] for dic in minor_contract_list}
    # 设置定时任务：夜盘21点开始
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')


def algo(context):    
    now_str = context.now.strftime('%Y-%m-%d')
    # 主力合约和次主力合约
    if context.now.hour>15:
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0] 
    else:
        date = context.now.strftime('%Y-%m-%d')
    if context.mode==MODE_BACKTEST and date in context.main_contract_list and date in context.minor_contract_list:
        main_contract = context.main_contract_list[date]
        minor_contract = context.minor_contract_list[date]
    else:
        main_contract = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)[0]['symbol']
        minor_contract = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)[0]['symbol']
    # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约
    Account_positions = get_position()
    if main_contract!=context.main_contract or minor_contract!=context.minor_contract:
        if Account_positions:
            for posi in Account_positions:
                if posi['symbol']==context.main_contract and main_contract!=context.main_contract:
                    print('{}：主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract))
                    context.close_all = True
                if posi['symbol']==context.minor_contract and minor_contract!=context.minor_contract:
                    print('{}：次主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract))
                    context.close_all = True
        # 更新主力合约
        context.main_contract = main_contract
        context.minor_contract = minor_contract
    # 当context.close_all为True时，清仓
    if context.close_all:
        context.close_all = False
        order_close_all()
        print('{}:平仓'.format(context.now))

    # 获取历史数据
    close_main = history_n(symbol=context.main_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']
    close_minor = history_n(symbol=context.minor_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']
    # 计算布林带
    spread_history = close_main - close_minor# 历史价差
    spread_new = close_main.iloc[-1] - close_minor.iloc[-1]# 最新价差
    spread_history_mean = np.mean(spread_history)# 历史价差的均值
    spread_history_std = np.std(spread_history)# 历史价差的标准差
    upper = spread_history_mean + context.boll_multiple * spread_history_std# 布林带上轨
    lower = spread_history_mean - context.boll_multiple * spread_history_std# 布林带下轨
    upper_stoppoint = spread_history_mean + context.stoppoint_multiple * spread_history_std# 上轨止损线
    lower_stoppoint = spread_history_mean - context.stoppoint_multiple * spread_history_std# 下轨止损线

    # 获取仓位
    positions = get_position()
    position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位
    position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位

    # 没有仓位时
    if not position_short and not position_long:
        price_main = current(symbols=context.main_contract)[0]['price']
        price_minor = current(symbols=context.minor_contract)[0]['price']
        if spread_new > upper:
            print('{}:做空价差组合(买入{}，卖出{})'.format(context.now,context.minor_contract,context.main_contract))
            order_volume(symbol=context.main_contract,side=OrderSide_Sell,volume=1,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_main)
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_minor)

        if spread_new < lower:
            print('{}:做多价差组合(买入{}，卖出{})'.format(context.now,context.main_contract,context.minor_contract))
            order_volume(symbol=context.main_contract, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_main)
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_minor)

    # 设计平仓信号
    # 做多价差组合时
    if position_long:
        # 价差回归到均值水平时或偏离达到止损位时，平仓
        if spread_new >= spread_history_mean or spread_new <= lower_stoppoint:
            order_close_all()
            print('{}:平仓'.format(context.now))

    # 做空价差组合时
    if position_short:
        # 价差回归到均值水平时或偏离达到止损位时，平仓
        if spread_new <= spread_history_mean or spread_new >= upper_stoppoint:
            order_close_all()
            print('{}:平仓'.format(context.now))


def on_backtest_finished(context, indicator):
    print('*'*50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
    '''    
    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]
    backtest_end_time = str(datetime.datetime.now())[:19]
    run(strategy_id='a0f7768c-edf5-11ef-a4cf-00ff2dbfd94c',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='c2a347464c31056c84165dff766750fbf2ec67b4',
        backtest_start_time=backtest_start_time,
        backtest_end_time=backtest_end_time,
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=200000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)

# Dual Thrust趋势跟踪策略
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
"""  
示例策略仅供参考，不建议直接实盘使用。  
  
Dual Thrust是一个趋势跟踪策略，当现价突破上轨时做多，当现价跌穿下轨时做空。  
上轨：开盘价+K*波动  
下轨：开盘价-K*波动  
波动：max(HH - LC, HC - LL)  
其中HH为N天最高价的最大值，LC为N天收盘价的最小值，HC为N天收盘价的最大值，LL为N天最低价的最小值  
"""  
  
  
## 策略中必须有init方法  
def init(context):  
    # 设置要进行回测的合约（可以在掘金终端的仿真交易中查询标的代码）  
    context.symbol = 'SHFE.RB'  # 订阅&交易标的  
  
    # 设置参数  
    context.N = 5  
    context.k1 = 0.2  
    context.k2 = 0.2  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 设置定时任务：夜盘21点开始，日盘9点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
  
  
def algo(context):  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 订阅行情  
    subscribe(context.main_contract, '60s', count=1, unsubscribe_previous=True)    
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 取历史数据  
    if context.now.hour>=20:  
        # 当天夜盘和次日日盘属于同一天数据，为此当天夜盘的开盘价调用第二天的开盘价  
        next_date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
        data = history_n(symbol=context.main_contract, frequency='1d', end_time=next_date,  
                        fields='symbol,open,high,low,close', count=context.N + 1, df=True)  
    else:  
        data = history_n(symbol=context.main_contract, frequency='1d', end_time=context.now,  
                        fields='symbol,open,high,low,close', count=context.N + 1, df=True)  
    # 取开盘价  
    # 回测模式下，开盘价可以直接用history_n取到  
    if context.mode == 2:  
        current_open = data.open.iloc[-1]  
    else:# 如果是实时模式，开盘价需要用current取到  
        current_open = current(context.main_contract)[0]['open']  
  
    # 先去掉当天的实时数据，再计算其他指标  
    data.drop(context.N, inplace=True)  
    # 计算Dual Thrust 的上下轨  
    HH = data['high'].max()  
    HC = data['close'].max()  
    LC = data['close'].min()  
    LL = data['low'].min()  
    range = max(HH - LC, HC - LL)  
    context.buy_line = current_open + range * context.k1  # 上轨  
    context.sell_line = current_open - range * context.k2  # 下轨  
  
  
def on_bar(context, bars):  
    # 取出订阅的这一分钟的bar  
    bar = bars[0]  
  
    # 获取现有持仓  
    positions = get_position()  
    position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位  
    position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
    # 交易逻辑部分  
    # 如果最新价突破上轨  
    if bar.close > context.buy_line:  
        # 情况1：已经持有多仓，直接返回  
        if position_long:    
            return  
        # 情况2：已经持有空仓，先平空仓再开多仓（再开多仓的操作再on_order_status()中实现）  
        elif position_short:    
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=bar.close)  
        # 情况3：没有持仓时，直接开多仓              
else:    
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=bar.close)  
    # 如果最新价跌破下轨  
    elif bar.close < context.sell_line:  
        # 情况1：已经持有空仓，直接返回:  
        if position_short:    
            return  
        # 情况2：已经持有多仓，先平多仓再开空仓（再开空仓的操作再on_order_status()中实现）  
        elif position_long:    
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=bar.close)  
        # 情况3：没有持仓，直接开空仓  
        else:   
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=bar.close)  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
        # 平仓后，接着开相反方向的仓位  
        if effect==2:  
            order_volume(symbol=context.main_contract, volume=1, side=side,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price)  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
      
if __name__ == '__main__':  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=160))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='a4cf48f4-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=50000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 风格轮动策略
## coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *

import datetime
import numpy as np
import pandas as pd

'''
示例策略仅供参考，不建议直接实盘使用。

风格轮动策略
逻辑：以上证50、沪深300、中证500作为市场三个风格的代表，每次选取表现做好的一种风格，买入其成分股中最大市值的N只股票，每月月初进行调仓换股
'''

def init(context):
    # 待轮动的风格指数(分别为：上证50、沪深300、中证500)
    context.index = ['SHSE.000016', 'SHSE.000300', 'SZSE.399625']
    # 用于统计数据的天数
    context.days = 20
    # 持股数量
    context.holding_num = 10

    # 每日定时任务
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')


def algo(context):
    # 当天日期
    now_str = context.now.strftime('%Y-%m-%d')
    # 获取上一个交易日
    last_day = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0] 
    # 判断是否为每个月第一个交易日
    if context.now.month!=pd.Timestamp(last_day).month:
        return_index = pd.DataFrame(columns=['return'])
        # 获取并计算指数收益率
        for i in context.index:
            return_index_his = history_n(symbol=i, frequency='1d', count=context.days+1, fields='close,bob',
                                        fill_missing='Last', adjust=ADJUST_PREV, end_time=last_day, df=True)
            return_index_his = return_index_his['close'].values
            return_index.loc[i,'return'] = return_index_his[-1] / return_index_his[0] - 1
        
        # 获取指定数内收益率表现最好的指数
        sector = return_index.index[np.argmax(return_index)]
        print('{}:最佳指数是:{}'.format(now_str,sector))

        # 获取最佳指数成份股
        symbols = list(stk_get_index_constituents(index=sector, trade_date=last_day)['symbol'])
        
        # 过滤停牌的股票
        stocks_info =  get_symbols(sec_type1=1010, symbols=symbols, trade_date=now_str, skip_suspended=True, skip_st=True)
        symbols = [item['symbol'] for item in stocks_info if item['listed_date']<context.now and item['delisted_date']>context.now]
        # 获取最佳指数成份股的市值，选取市值最大的N只股票
        fin = stk_get_daily_mktvalue_pt(symbols=symbols, fields='tot_mv', trade_date=last_day, df=True).sort_values(by='tot_mv',ascending=False)
        to_buy = list(fin.iloc[:context.holding_num]['symbol'])
        
        # 计算权重(预留出2%资金，防止剩余资金不够手续费抵扣)
        percent = 0.98 / len(to_buy)
        # 获取当前所有仓位
        positions = get_position()

        # 平不在标的池的股票（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）
        for position in positions:
            symbol = position['symbol']
            if symbol not in to_buy:
                # 开盘价（日频数据）
                new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']
                # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）
                # new_price = current(symbols=symbol)[0]['price']
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)

        # 买入标的池中的股票（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）
        for symbol in to_buy:
            # 开盘价（日频数据）
            new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']
            # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）
            # new_price = current(symbols=symbol)[0]['price']
            order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            else:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            else:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type==1 else '市价'
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))
       
       

def on_backtest_finished(context, indicator):
    print('*'*50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
    '''
    run(strategy_id='aa5b672e-edf4-11ef-a4cf-00ff2dbfd94c',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='c2a347464c31056c84165dff766750fbf2ec67b4',
        backtest_start_time='2019-01-01 08:00:00',
        backtest_end_time='2020-12-31 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)

# 菲阿里四价策略
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
"""  
示例策略仅供参考，不建议直接实盘使用。  
  
菲阿里四价策略是一种简单趋势型日内交易策略。昨天最高点、昨天最低点、昨日收盘价、今天开盘价,可并称为菲阿里四价。  
没有持仓下，当现价突破上轨时做多，当现价跌穿下轨时做空；以开盘价作为止损价，尾盘平仓，其中  
上轨=昨日最高点；  
下轨=昨日最低点；  
止损=今日开盘价。  
注：受目前回测机制限制，期货主力合约只能回测最近三年的数据，连续合约不受影响  
"""  
  
  
def init(context):  
    # 设置标的  
    context.symbol = 'DCE.JM'  
    # 记录开仓次数，当前设置夜盘和日盘各最多一次  
    context.count = 0  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 定时任务：夜盘21点开始，日盘9点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
  
  
def algo(context):  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 订阅一分钟线  
    subscribe(symbols = context.main_contract, frequency = '60s', count = 1, unsubscribe_previous=True)  
    # 获取数据  
    if context.now.hour>=20:  
        # 当天夜盘和次日日盘属于同一天数据，为此当天夜盘的开盘价调用第二天的开盘价  
        next_date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
        # 获取历史的n条信息  
        context.history_data = history_n(symbol=context.main_contract, frequency='1d', end_time=next_date,  
                                        fields='symbol,open,high,low,eob', count=2, adjust_end_time=context.now, df=True)  
    else:  
        # 获取历史的n条信息  
        context.history_data = history_n(symbol=context.main_contract, frequency='1d', end_time=context.now,  
                                        fields='symbol,open,high,low,eob', count=2, adjust_end_time=context.now, df=True)  
  
  
def on_bar(context,bars):  
    # 现有持仓情况  
    positions = get_position()  
    position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位  
    position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位  
    # 尾盘平仓  
    if context.now.hour == 14 and context.now.minute >= 59 or context.now.hour == 15:  
        # 有持仓时才触发平仓操作  
        if position_long or position_short:  
            order_close_all()  
            print('{}:尾盘平仓'.format(context.now))  
        context.count = 0  
  
    # 非尾盘交易时间  
    else:  
        ## 数据获取  
        bar = bars[0]  
        data = context.history_data.copy()  
        # 如果是回测模式  
        if context.mode == 2:  
            # 开盘价直接在data最后一个数据里取到,前一交易日的最高和最低价为history_data里面的倒数第二条中取到  
            open = data[ 'open'].iloc[-1]  
            high = data['high'].iloc[-2]  
            low = data['low'].iloc[-2]  
        # 如果是实时模式  
        else:  
            # 开盘价通过current取到,实时模式不会返回当天的数据，所以history_data里面的最后一条数据是前一交易日的数据  
            open = current(context.main_contract)[0]['open']  
            high = data['high'].iloc[-1]  
            low = data['low'].iloc[-1]  
  
        ## 交易逻辑部分  
        if position_long:    
            if bar.close < open:# 平多仓：最新价小于开盘价时止损。  
                order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell,  
                            order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=bar.close)  
        elif position_short:  
            if bar.close > open:# 平空仓：最新价大于开盘价时止损。  
                order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy,  
                            order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=bar.close)  
        else:  # 没有持仓  
            if bar.close > high and not context.count:  # 开多仓：最新价大于了前一天的最高价  
                order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy,  
                            order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=bar.close)  
                context.count = 1  
            elif bar.close < low and not context.count:  # 开空仓：最新价小于了前一天的最低价  
                order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell,  
                            order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=bar.close)  
                context.count = 1  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='aa144667-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 风格轮动策略
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
回测模式不支持算法交易，仅部分券商版本的实盘模式支持；此策略仅供参考，不具有投资建议，各项数值需客户自行填写后方可运行。  
  
风格轮动策略  
逻辑：以上证50、沪深300、中证500作为市场三个风格的代表，每次选取表现做好的一种风格，买入其成分股中最大市值的N只股票，每月月初进行调仓换股  
'''  
  
def init(context):  
    # 待轮动的风格指数(分别为：上证50、沪深300、中证500)  
    context.index = ['SHSE.000016', 'SHSE.000300', 'SZSE.399625']  
    # 用于统计数据的天数  
    context.days = 20  
    # 持股数量  
    context.holding_num = 10  
    # 每日定时任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')  
  
  
def algo(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 获取上一个交易日  
    last_day = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]  
    # 判断是否为每个月第一个交易日  
    if context.now.month!=pd.Timestamp(last_day).month:  
        return_index = pd.DataFrame(columns=['return'])  
        # 获取并计算指数收益率  
        for i in context.index:  
            return_index_his = history_n(symbol=i, frequency='1d', count=context.days+1, fields='close,bob',  
                                        fill_missing='Last', adjust=ADJUST_PREV, end_time=last_day, df=True)  
            return_index_his = return_index_his['close'].values  
            return_index.loc[i,'return'] = return_index_his[-1] / return_index_his[0] - 1  
        # 获取指定数内收益率表现最好的风格  
        sector = return_index.index[np.argmax(return_index)]  
        print('{}:最佳指数是:{}'.format(now_str,sector))  
  
        # 获取最佳指数成份股  
        symbols = list(stk_get_index_constituents(index=sector, trade_date=last_day)['symbol'])  
          
        # 过滤停牌的股票  
        stocks_info =  get_symbols(sec_type1=1010, symbols=symbols, trade_date=now_str, skip_suspended=True, skip_st=True)  
        symbols = [item['symbol'] for item in stocks_info if item['listed_date']<context.now and item['delisted_date']>context.now]  
        # 获取最佳指数成份股的市值，选取市值最大的N只股票  
        fin = stk_get_daily_mktvalue_pt(symbols=symbols, fields='tot_mv', trade_date=last_day, df=True).sort_values(by='tot_mv',ascending=False)  
        to_buy = list(fin.iloc[:context.holding_num]['symbol'])  
          
        # 计算权重(预留出2%资金，防止剩余资金不够手续费抵扣)  
        percent = 0.98 / len(to_buy)  
        # 获取当前所有仓位  
        positions = get_position()  
  
        # 平不在标的池的股票（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in to_buy:  
                # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
                new_price = current(symbols=symbol)[0]['price']  
                # # 普通下单  
                # order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)  
  
                # 交易算法下单  
                trade_volume = position['available_now']                                            # 获取股票的可用数量  
                # 下单  
                if trade_volume>0:  
                    algo_param = {'start_time': '09:00:00', 'end_time_referred':'14:55:00', 'end_time': '14:55:00', 'end_time_valid': 1, 'stop_sell_when_dl': 1,'cancel_when_pl': 0, 'min_trade_amount': 100000}  
                    algo_order(symbol=symbol, volume=trade_volume, side=OrderSide_Sell, order_type=OrderType_Limit, position_effect=PositionEffect_Close, price=new_price, algo_name='ATS-SMART', algo_param=algo_param)  
                    print('{}:{}卖出委托，委托价格：{}，委托市值：{:.0f}'.format(now_str,symbol,new_price,trade_volume*new_price))   
                else:  
                    print('{}:{}可用数量不足，未成功下单！'.format(now_str,symbol))  
  
        # 买入标的池中的股票（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）  
        for symbol in to_buy:  
            # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
            new_price = current(symbols=symbol)[0]['price']  
            # 普通下单  
            # order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)  
            # 交易算法下单  
            Account_cash = get_cash()                                                                # 获取账户资金信息  
            available_amount = min(Account_cash['nav']*percent,Account_cash['available'])            # 计算委托金额   
trade_volume = int(np.floor(available_amount/new_price/100)*100)                         # 计算下单数量  
            if symbol[:7]=='SHSE.68' and trade_volume<200:trade_volume = 0                           # 调整下单数量：科创板最小买入单位是200股  
            # 下单  
            if trade_volume>0:  
                algo_param = {'start_time': '09:00:00', 'end_time_referred':'14:55:00', 'end_time': '14:55:00', 'end_time_valid': 1, 'stop_sell_when_dl': 1,'cancel_when_pl': 0, 'min_trade_amount': 100000}  
                algo_order(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price, algo_name='ATS-SMART', algo_param=algo_param)  
                print('{}:{}买入委托，委托价格：{}，委托市值：{:.0f}'.format(now_str,symbol,new_price,trade_volume*new_price))   
            else:  
                print('{}:{}可用资金不足，未成功下单！'.format(now_str,symbol))  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
         
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='b765ccb8-edf4-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2019-01-01 08:00:00',  
        backtest_end_time='2020-12-31 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 定时下单
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
  
def init(context):  
    # 每天14:50 定时执行algo任务,  
    # algo执行定时任务函数，只能传context参数  
    # date_rule执行频率，目前暂时支持1d、1w、1m，其中1w、1m仅用于回测，实时模式1d以上的频率，需要在algo判断日期  
    # time_rule执行时间， 注意多个定时任务设置同一个时间点，前面的定时任务会被后面的覆盖  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:50:00')  
  
  
def algo(context):  
    # 以市价购买200股浦发银行股票， price为保护限价  
    order_volume(symbol={}.format('SHSE.600000'), volume=200, side=OrderSide_Buy,  
                 order_type=OrderType_Market, position_effect=PositionEffect_Open, price=0)  
  
  
## 查看最终的回测结果  
def on_backtest_finished(context, indicator):  
    print(indicator)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='b816f68c-ede0-11ef-ab84-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2020-11-01 08:00:00',  
        backtest_end_time='2020-11-10 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 分钟级别双均线策略
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import re  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
本策略以分钟级别数据建立双均线模型，短周期为20，长周期为60  
当短期均线由上向下穿越长期均线时做空  
当短期均线由下向上穿越长期均线时做多  
'''  
  
def init(context):  
    context.frequency = '300s'# 使用的频率，300s为5分钟bar  
    context.short = 20  # 短周期均线  
    context.long = 60  # 长周期均线  
    context.symbol = ['CZCE.SA','CZCE.MA'] # 订阅交易标的   
context.volume = 1 # 每次交易数量，手（注意资金是否充足）  
    context.period = context.long + 1  # 订阅数据滑窗长度  
  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        context.contract_list = {}  
        for symbol in context.symbol:  
            contract_list = fut_get_continuous_contracts(csymbol=symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
            if len(contract_list)>0:  
                context.contract_list[symbol] = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 定时任务：夜盘21点开始，日盘9点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
  
  
def algo(context):  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST:  
        try:  
            context.main_contract = {symbol:context.contract_list[symbol][date] for symbol in context.symbol}  
        except:  
            context.main_contract = {symbol:fut_get_continuous_contracts(csymbol=symbol, start_date=date, end_date=date)[0]['symbol'] for symbol in context.symbol}  
        context.main_contract_list = list(context.main_contract.values())  
    else:  
        context.main_contract = {symbol:fut_get_continuous_contracts(csymbol=symbol, start_date=date, end_date=date)[0]['symbol'] for symbol in context.symbol}  
        context.main_contract_list = list(context.main_contract.values())  
    # 订阅行情  
    subscribe(context.main_contract_list, context.frequency, count=context.period, unsubscribe_previous=True)    
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            holding_symbol_prefix = re.findall(r'\D+',posi['symbol'])[0].upper()  
            if holding_symbol_prefix in context.symbol and posi['symbol'] not in context.main_contract_list:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract[holding_symbol_prefix]))                  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
  
def on_bar(context, bars):  
    # 获取通过subscribe订阅的数据  
    symbol = bars[0]['symbol']  
    if symbol in context.main_contract_list:  
        prices = context.data(symbol,  
                            context.frequency,  
                            context.period,  
                            fields='close')  
  
        # 计算长短周期均线  
        short_avg = prices.rolling(context.short).mean().values  
        long_avg = prices.rolling(context.long).mean().values  
  
        # 查询持仓  
        positions = get_position()  
        position_long = list(filter(lambda x:x['symbol']==symbol and x['side']==PositionSide_Long,positions))        # 多头仓位  
        position_short = list(filter(lambda x:x['symbol']==symbol and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
        # 短均线下穿长均线，做空(即当前时间点短均线处于长均线下方，前一时间点短均线处于长均线上方)  
        if long_avg[-2] <= short_avg[-2] and long_avg[-1] > short_avg[  
                -1] and not position_short:  
            # 无多仓情况下，直接开空  
            if not position_long:  
                order_volume(symbol=symbol,  
                            volume=context.volume,  
                            side=OrderSide_Sell,  
                            position_effect=PositionEffect_Open,  
                            order_type=OrderType_Market)  
            # 有多仓情况下，先平多，再开空(开空命令放在on_order_status里面)  
            else:  
                # 以市价平多仓  
                order_volume(symbol=symbol,  
                            volume=context.volume,  
                            side=OrderSide_Sell,  
                            position_effect=PositionEffect_Close,  
                            order_type=OrderType_Market)  
  
        # 短均线上穿长均线，做多（即当前时间点短均线处于长均线上方，前一时间点短均线处于长均线下方）  
        if short_avg[-2] <= long_avg[-2] and short_avg[-1] > long_avg[  
                -1] and not position_long:  
            # 无空仓情况下，直接开多  
            if not position_short:  
                order_volume(symbol=symbol,  
                            volume=context.volume,  
                            side=OrderSide_Buy,  
                            position_effect=PositionEffect_Open,  
                            order_type=OrderType_Market)  
            # 有空仓的情况下，先平空，再开多(开多命令放在on_order_status里面)  
            else:  
                # 以市价平空仓  
                order_volume(symbol=symbol,  
                            volume=context.volume,  
                            side=OrderSide_Buy,  
                            position_effect=PositionEffect_Close,  
                            order_type=OrderType_Market)  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type == 1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(  
            context.now, symbol, order_type_word, side_effect, price, volume))  
        # 平仓后，接着开相反方向的仓位  
        if effect == 2 and symbol in context.main_contract_list:  
            order_volume(symbol=symbol,  
                         volume=volume,  
                         side=side,  
                         order_type=OrderType_Market,  
                         position_effect=PositionEffect_Open)  
  
  
def on_backtest_finished(context, indicator):  
    print('*' * 50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=100))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='ba3bfce7-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 定时国债逆回购
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
  
"""  
示例策略仅供参考，不建议直接实盘使用。  
  
国债逆回购助手，逆回购不支持回测场景  
定时进行国债逆回购，并可以设置最低利率阈值，低于该阈值不进行国债逆回购，而进行券商的余额理财（需在券商中开通，具体咨询券商客户经理）。  
"""  
## 策略中必须有init方法  
def init(context):  
    # 逆回购定时时间  
    context.brr_time = '15:18:00'  
    # 逆回购目标债券(沪市1天期逆回购代码为204001，深市1天期逆回购代码为131810，沪市和深市均为1000起，10张为1手，每张100元，一般沪市相对深市利率高一些)  
    context.symbol = 'SHSE.204001'  
    # 逆回购最低利率（低于该利率不进行逆回购，可开启券商的余额理财产品，2023年10月的某券商余额理财年化约为2.3%）  
    context.min_rate = 2.3  
  
    # 定时任务  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule=context.brr_time)  
  
  
def algo_1(context):  
    current_data = current(symbols=context.symbol)[0]  
    if current_data['price']<context.min_rate:  
        print('{}:当前逆回购价格低于{}，不进行逆回购，建议开启余额理财产品'.format(context.now,context.min_rate))  
    else:  
        trade_price = current_data['price']# 当前价格，也可以使用买五价格作为交易价格:current_data['quotes'][4]['bid_p']  
        account_cash = get_cash()  
        volume = int(np.floor(account_cash['available']/1000)*10)  
        if volume>0:  
            bond_reverse_repurchase_agreement(symbol=context.symbol, volume=volume, price=trade_price, order_type=OrderType_Limit)  
            print('{}:进行逆回购，卖出市值：{}，当前价格：{}'.format(context.now,volume*100,current_data['price']))  
        else:  
            print('{}:金额不足，未进行逆回购'.format(context.now))  
      
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='c4c0ff91-edf4-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2023-10-01 08:00:00',  
        backtest_end_time='2023-11-07 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 二分类（上涨/下跌）模型
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
try:  
    from sklearn import svm  
except:  
    import os  
    print('正在安装scikit-learn库...')  
    os.system('pip install scikit-learn')  
    from sklearn import svm  
    print('安装scikit-learn库完成！')  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
本策略以支持向量机算法为基础，训练一个二分类（上涨/下跌）的模型，模型以历史N天数据的数据预测未来M天的涨跌与否。  
特征变量为:1.收盘价/均值、2.现量/均量、3.最高价/均价、4.最低价/均价、5.现量、6.区间收益率、7.区间标准差。  
若没有仓位，则在每个星期一预测涨跌,并在预测结果为上涨的时候购买标的.  
若已经持有仓位，则在盈利大于10%的时候止盈,在星期五涨幅小于2%的时候止盈止损.  
'''  
  
def init(context):  
    # 股票标的  
    context.symbol = 'SHSE.600000'  
    # 历史窗口长度，N  
    context.history_len = 10  
    # 预测窗口长度，M  
    context.forecast_len = 5  
    # 训练样本长度  
    context.training_len = 90# 20天为一个交易月  
    # 止盈幅度  
    context.earn_rate = 0.10  
    # 最小涨幅卖出幅度  
    context.sell_rate = 0.02  
    # 订阅行情  
    subscribe(symbols=context.symbol, frequency='60s')  
  
  
def on_bar(context, bars):  
    bar = bars[0]  
    # 当前时间  
    now = context.now  
    now_str = now.strftime('%Y-%m-%d')  
    # 获取当前时间的星期  
    weekday = now.isoweekday()  
    # 历史交易日  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=context.training_len)  
    # 上一交易日  
    last_date = date_list[-1]   
    # 上一年的交易日  
    last_year_date = date_list[0]   
    # 获取持仓  
    position = get_position()  
  
    # 如果当前时间是星期一且没有仓位，则开始预测  
    if weekday == 1 and now.hour==9 and now.minute==31 and not position:  
        # 获取预测用的历史数据  
        features = clf_fit(context,last_year_date,last_date)  
        features = np.array(features).reshape(1, -1)  
        prediction = context.clf.predict(features)[0]  
  
        # 若预测值为上涨则买入  
        if prediction == 1:  
            order_target_percent(symbol=context.symbol, percent=1, order_type=OrderType_Limit, position_side=PositionSide_Long, price=bar.close)  
              
    # 当涨幅大于10%,平掉所有仓位止盈  
    elif position and bar.close/position[0]['vwap'] >= 1+context.earn_rate:  
        order_close_all()  
  
    # 当时间为周五尾盘并且涨幅小于2%时,平掉所有仓位止损  
    elif position and weekday == 5 and bar.close/position[0]['vwap'] < 1+context.sell_rate and now.hour==14 and now.minute==55:  
        order_close_all()  
  
  
def clf_fit(context,start_date,end_date):  
    """  
    训练支持向量机模型  
    :param start_date:训练样本开始时间  
    :param end_date:训练样本结束时间  
    """    # 获取目标股票的daily历史行情  
    recent_data = history(context.symbol, frequency='1d', start_time=start_date, end_time=end_date, fill_missing='last',df=True).set_index('eob')  
    days = list(recent_data['bob'])  
  
    x_train = []  
    y_train = []  
    # 整理训练数据  
    for index in range(context.history_len, len(recent_data)):  
        ## 自变量 X        # 回溯N个交易日相关数据  
        start_date = recent_data.index[index-context.history_len]  
        end_date = recent_data.index[index]  
        data = recent_data.loc[start_date:end_date,:]  
        # 准备训练数据  
        close = data['close'].values  
        max_x = data['high'].values  
        min_n = data['low'].values  
        volume = data['volume'].values  
        close_mean = close[-1] / np.mean(close)  # 收盘价/均值  
        volume_mean = volume[-1] / np.mean(volume)  # 现量/均量  
        max_mean = max_x[-1] / np.mean(max_x)  # 最高价/均价  
        min_mean = min_n[-1] / np.mean(min_n)  # 最低价/均价  
        vol = volume[-1]  # 现量  
        return_now = close[-1] / close[0]  # 区间收益率  
        std = np.std(np.array(close), axis=0)  # 区间标准差  
        # 将计算出的指标添加到训练集X  
        x_train.append([close_mean, volume_mean, max_mean, min_mean, vol, return_now, std])  
          
        ## 因变量 Y        if index<len(recent_data)-context.forecast_len:  
            y_start_date = recent_data.index[index+1]  
            y_end_date = recent_data.index[index+context.forecast_len]  
            y_data = recent_data.loc[y_start_date:y_end_date,'close']  
            if y_data.iloc[-1] > y_data.iloc[0]:  
                label = 1  
            else:  
                label = 0  
            y_train.append(label)  
          
        # 最新一期的数据(返回该数据，作为待预测的数据)  
        if index==len(recent_data)-1:  
            new_x_traain = [close_mean, volume_mean, max_mean, min_mean, vol, return_now, std]  
    else:  
        # 剔除最后context.forecast_len期的数据  
        x_train = x_train[:-context.forecast_len]  
  
    # 训练SVM  
    context.clf = svm.SVC(C=1.0, kernel='rbf', degree=3, gamma='auto', coef0=0.0, shrinking=True, probability=False,  
                          tol=0.001, cache_size=200, verbose=False, max_iter=-1,decision_function_shape='ovr', random_state=None)  
    context.clf.fit(x_train, y_train)  
  
    # 返回最新数据  
    return new_x_traain  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='c8a2957f-edf4-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 网格交易
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
网格交易法是一种把行情的所有日间上下的波动全部囊括，不会放过任何一次行情上下波动的策略。  
本策略标的为：SHFE.RB  
价格中枢设定为：每日前一交易日的收盘价，每个网格间距3%；每变动一次，交易一手  
'''  
  
  
def init(context):  
    # 策略标的为SHFE.RB  
    context.symbol = 'SHFE.RB'  
    # 设置每变动一格，增减的数量  
    context.volume = 1  
    # 储存前一个网格所处区间，用来和最新网格所处区间作比较  
    context.last_grid = 0  
    # 记录上一次交易时网格范围的变化情况（例如从4区到5区，记为4,5）  
    context.grid_change_last = [0, 0]  
    # 止损条件:最大持仓  
    context.max_volume = 15  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 定时任务，日频，盘前运行  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
      
  
def algo(context):     
    now_str = context.now.strftime('%Y-%m-%d')   
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHFE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 订阅行情  
    subscribe(context.main_contract, '60s', count=1, unsubscribe_previous=True)    
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取前一交易日的收盘价作为价格中枢  
    if context.now.hour>=20:  
        # 当天夜盘和次日日盘属于同一天数据，为此当天夜盘的上一交易日收盘价应调用当天的收盘价  
        context.center = history_n(symbol=context.main_contract, frequency='1d', end_time=context.now, count=1, fields='close')[0]['close']  
    else:  
        last_date = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
        context.center = history_n(symbol=context.main_contract, frequency='1d', end_time=last_date, count=1, fields='close')[0]['close']  
      
    # 设置网格  
    context.band = np.array([0.92, 0.94, 0.96, 0.98, 1, 1.02, 1.04, 1.06, 1.08]) * context.center  
  
  
def on_bar(context, bars):  
    bar = bars[0]  
    # 获取仓位  
    positions = get_position()  
    position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位  
    position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
    # 当前价格所处的网格区域  
    grid = pd.cut([bar.close], context.band, labels=[1, 2, 3, 4, 5, 6, 7, 8])[0]# 1代表(0.88%,0.91%]区间，2代表(0.91%,0.94%]区间...  
  
    # 如果价格超出网格设置范围，则提示调节网格宽度和数量  
    if np.isnan(grid):  
        # print('价格波动超过网格范围，可适当调节网格宽度和数量')  
        return    
    # 如果新的价格所处网格区间和前一个价格所处的网格区间不同，说明触碰到了网格线，需要进行交易  
    # 如果新网格大于前一天的网格，做空或平多  
    if context.last_grid < grid:  
        # 记录新旧格子范围（按照大小排序）  
        grid_change_new = [context.last_grid,grid]  
  
        # 当last_grid = 0 时是初始阶段，不构成信号  
        if context.last_grid == 0:  
            context.last_grid = grid  
            return  
  
        # 如果前一次开仓是4-5，这一次是5-4，算是没有突破，不成交  
        if grid_change_new != context.grid_change_last:  
            # 如果有多仓，平多  
            if position_long:  
                order_volume(symbol=context.main_contract, volume=context.volume, side=OrderSide_Sell, order_type=OrderType_Market,  
                                position_effect=PositionEffect_Close)  
                print('{}:从{}区调整至{}区，以市价单平多仓{}手'.format(context.now,context.last_grid,grid,context.volume))  
  
            # 否则，做空  
            if not position_long:  
                order_volume(symbol=context.main_contract, volume=context.volume, side=OrderSide_Sell, order_type=OrderType_Market,  
                                position_effect=PositionEffect_Open)  
                print('{}:从{}区调整至{}区，以市价单开空{}手'.format(context.now,context.last_grid,grid,context.volume))  
  
            # 更新前一次的数据  
            context.last_grid = grid  
            context.grid_change_last = grid_change_new  
        else:  
            print('{}:从{}区调整至{}区，无交易'.format(context.now,context.last_grid,grid))  
            context.last_grid = grid  
  
  
    # 如果新网格小于前一天的网格，做多或平空  
    if context.last_grid > grid:  
        # 记录新旧格子范围（按照大小排序）  
        grid_change_new = [grid, context.last_grid]  
  
        # 当last_grid = 0 时是初始阶段，不构成信号  
        if context.last_grid == 0:  
            context.last_grid = grid  
            return  
  
        # 如果前一次开仓是4-5，这一次是5-4，算是没有突破，不成交  
        if grid_change_new != context.grid_change_last:  
            # 如果有空仓，平空  
            if position_short:  
                order_volume(symbol=context.main_contract, volume=context.volume, side=OrderSide_Buy,  
                                order_type=OrderType_Market,position_effect=PositionEffect_Close)  
                print('{}:从{}区调整至{}区，以市价单平空仓{}手'.format(context.now,context.last_grid,grid,context.volume))  
  
            # 否则，做多  
            if not position_short:  
                order_volume(symbol=context.main_contract, volume=context.volume, side=OrderSide_Buy,  
                                order_type=OrderType_Market,position_effect=PositionEffect_Open)  
                print('{}:从{}区调整至{}区，以市价单开多{}手'.format(context.now,context.last_grid,grid,context.volume))  
  
            # 更新前一次的数据  
            context.last_grid = grid  
            context.grid_change_last = grid_change_new  
        else:  
            print('{}:从{}区调整至{}区，无交易'.format(context.now,context.last_grid,grid))  
            context.last_grid = grid  
  
  
    # 设计一个止损条件：当持仓量达到20手，全部平仓  
    if (position_short and position_short[0]['volume'] == context.max_volume) or (position_long and position_long[0]['volume'] == context.max_volume):  
        order_close_all()  
        print('{}:触发止损，全部平仓'.format(context.now))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='c362a1cf-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=150000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 海龟交易法
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
本策略基于海龟交易法的唐奇安通道。  
以价格突破唐奇安通道的上下轨作为开仓信号，N倍ATR作为加仓或止损点。  
'''  
  
def init(context):  
    # 设置合约标的  
    context.symbol = 'DCE.I'  
    # 设置计算唐奇安通道的参数  
    context.n = 20  
    # 设置ATR倍数  
    context.atr_multiple = 0.5  
    # 设置单笔开仓数量  
    context.order_volume = 2  
    # 设置单笔加减仓数量  
    context.change_volume = 2  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 定时函数，开盘时运行  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):  
    now_str = context.now.strftime('%Y-%m-%d')   
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 订阅行情  
    subscribe(context.main_contract, '60s', count=2, unsubscribe_previous=True)    
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,price=new_price)  
  
    # 上一交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    # 调取数据  
    data = history_n(symbol=context.main_contract, frequency='1d', count=context.n+1, end_time=last_date, fields='close,high,low,bob', df=True) # 计算ATR  
    # 计算ATR  
    tr_list = []  
    for i in range(1, len(data)-1):  
        tr = max(max((data['high'].iloc[i]-data['low'].iloc[i]), abs(data['close'].shift(1).iloc[i]-data['high'].iloc[i])),  
                    abs(data['close'].shift(1).iloc[i] - data['low'].iloc[i]))  
        tr_list.append(tr)  
    context.atr = int(np.floor(np.mean(tr_list)))  
    context.atr_half = int(np.floor(context.atr_multiple * context.atr))  
    # 计算唐奇安通道  
    context.don_upper = np.max(data['high'].values[-context.n-1:-1])  
    context.don_lower = np.min(data['low'].values[-context.n-1:-1])  
    # 计算加仓点和止损点  
    context.long_add_point = context.don_upper + context.atr_half# 多仓加仓点  
    context.long_stop_loss = context.don_upper - context.atr_half# 多仓止损点  
    context.short_add_point = context.don_lower - context.atr_half# 空仓加仓点  
    context.short_stop_loss = context.don_lower + context.atr_half# 空仓止损点  
  
  
def on_bar(context, bars):  
    # 提取数据  
    symbol = bars[0]['symbol']  
    recent_data = context.data(symbol=context.main_contract, frequency='60s', count=2, fields='close,high,low')  
    new_price = recent_data['close'].values[-1]  
  
    # 账户仓位情况  
    positions = get_position()  
    position_long = list(filter(lambda x:x['symbol']==symbol and x['side']==PositionSide_Long,positions))        # 多头仓位  
    position_short = list(filter(lambda x:x['symbol']==symbol and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
    # 当无持仓时  
    if not position_long and not position_short:  
        # 如果向上突破唐奇安通道上轨，则开多  
        if new_price > context.don_upper:  
            order_volume(symbol=symbol, side=OrderSide_Buy, volume=context.order_volume, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=new_price)  
        # 如果向下突破唐奇安通道下轨，则开空  
        if new_price < context.don_lower:  
            order_volume(symbol=symbol, side=OrderSide_Sell, volume=context.order_volume, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=new_price)  
              
    # 有持仓时  
    # 持多仓  
    if position_long:  
        # 当突破加仓点时：加仓  
        if new_price > context.long_add_point:  
            order_volume(symbol=symbol, volume=context.change_volume, side=OrderSide_Buy, order_type=OrderType_Limit,position_effect=PositionEffect_Open,price=new_price)  
            context.long_add_point += context.atr_half  
            context.long_stop_loss += context.atr_half  
        # 当跌破止损点时：减仓或清仓  
        if new_price < context.long_stop_loss:  
            volume_hold = position_long[0]['volume']  
            if volume_hold > context.order_volume:  
                order_volume(symbol=symbol, volume=context.change_volume, side=OrderSide_Sell, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=new_price)  
            else:  
                order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Sell, order_type=OrderType_Limit,position_effect=PositionEffect_Close,price=new_price)  
            context.long_add_point -= context.atr_half  
            context.long_stop_loss -= context.atr_half  
  
    # 持空仓  
    if position_short:  
        # 当跌破加仓点时：加仓  
        if new_price < context.short_add_point:  
            order_volume(symbol = symbol, volume=context.change_volume, side=OrderSide_Sell, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=new_price)  
            context.short_add_point -= context.atr_half  
            context.short_stop_loss -= context.atr_half  
        # 当突破止损点时：减仓或清仓  
        if new_price > context.short_stop_loss:  
            volume_hold = position_short[0]['volume']  
            if volume_hold > context.atr_half:  
                order_volume(symbol=symbol, volume=context.change_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=new_price)  
            else:  
                order_volume(symbol=symbol, volume=volume_hold, side=OrderSide_Buy, order_type=OrderType_Limit,position_effect=PositionEffect_Close,price=new_price)  
            context.short_add_point += context.atr_half  
            context.short_stop_loss += context.atr_half  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='c6710e73-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 做市策略
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
做市策略是一种风险中性策略，基于盘口价差套利。  
本策略基于盘口价格，当卖一价和买一价之间价差大于阈值时，在盘口的卖一价和买一价之间插入委卖限价单和委买限价单,并以此赚取差价  
注：  
1.由于回测不支持限价单，限价单并未根据盘口情况，而是以理想状态立刻成交；  
2.目前只支持最近三个月的tick数据，回测时间需手动调整。  
'''  
  
def init(context):  
    # 标的合约  
    context.symbol = 'SHFE.RB'  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 定时函数，开盘时运行  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:00:00')  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):  
    now_str = context.now.strftime('%Y-%m-%d')   
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 最小变动单位  
    context.price_tick = get_symbols(sec_type1=1040, symbols=context.main_contract, trade_date=date)[0]['price_tick']  
    # 开仓阈值  
    context.threshold = 3*context.price_tick    
    # 订阅行情  
    subscribe(context.main_contract, 'tick', count=1, unsubscribe_previous=True)    
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
  
def on_tick(context, tick):  
    quotes = tick['quotes'][0]  
    # 获取买一价  
    bid_p = quotes['bid_p']  
    # 获取卖一价  
    ask_p = quotes['ask_p']  
    # 盘口价差大于阈值(买一价和卖一价不等于0，涨跌停时没有买卖价，以0填充)，开仓  
    if ask_p-bid_p>=context.threshold and bid_p!=0 and ask_p!=0:  
        # 买入价格：买一价+最小变动单位  
        new_bid_p = bid_p+context.price_tick  
        # 卖出价格：卖一价-最小变动单位  
        new_ask_p = ask_p-context.price_tick  
        # 获取持仓  
        positions = get_position()  
        position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位  
        position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位  
  
        # 无多头持仓时，挂多头限价单开多仓  
        if not position_long:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy, price=new_bid_p, order_type=OrderType_Limit,position_effect=PositionEffect_Open)  
              
        # 有多头持仓时，挂空头限价单平多仓  
        else:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell, price=new_ask_p, order_type=OrderType_Limit,position_effect=PositionEffect_Close)  
              
        # 无空头持仓时，挂空头限价单开空仓  
        if not position_short:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell, price=new_ask_p, order_type=OrderType_Limit,position_effect=PositionEffect_Open)  
              
        # 有空头持仓时，挂多头限价单平空仓  
        else:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy, price=new_bid_p, order_type=OrderType_Limit,position_effect=PositionEffect_Close)  
              
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_transaction_ratio回测成交比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=7))[:19]  
    backtest_end_time = str(datetime.datetime.now())[:19]  
    run(strategy_id='cb8fdbaa-edf5-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time=backtest_start_time,  
        backtest_end_time=backtest_end_time,  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=50000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0,  
        backtest_transaction_ratio=1,  
        backtest_match_mode=1)

# 小市值策略
## coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *

import datetime
import pandas as pd
import numpy as np

"""
示例策略仅供参考，不建议直接实盘使用。

小市值策略，等权买入全A市场中市值最小的前N只股票，月初调仓换股
"""

def init(context):
    # 定义股票池数量
    context.num = 10
    # 定时任务，日频
    schedule(schedule_func=algo, date_rule='1d', time_rule='15:00:00')


def algo(context):
    # 当前时间
    now_str = context.now.strftime('%Y-%m-%d')
    # 获取上一个交易日
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]

    # 判断是否为每个月第一个交易日
    if context.now.month!=pd.Timestamp(last_date).month:
        # 获取A股代码（剔除停牌股、ST股、次新股（365天））
        all_stock,all_stock_str = get_normal_stocks(now_str)
        # 获取所有股票市值,并按升序排序
        fundamental = stk_get_daily_mktvalue_pt(symbols=all_stock, fields='tot_mv', trade_date=last_date, df=True).sort_values(by='tot_mv')
        # 获取前N只股票
        to_buy = list(fundamental.iloc[:context.num,:]['symbol'])
        print('本次股票池有股票数目: ', len(to_buy))

        positions = get_position()
        # 平不在标的池的股票（注：本策略交易以收盘价为交易价格，当调整定时任务时间时，需调整对应价格）
        for position in positions:
            symbol = position['symbol']
            if symbol not in to_buy:
                new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['close']
                # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）
                # new_price = current(symbols=symbol)[0]['price']
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)

        # 获取股票的权重(预留出2%资金，防止剩余资金不够手续费抵扣)
        percent = 0.98 / len(to_buy)
        # 买在标的池中的股票（注：本策略交易以收盘价为交易价格，当调整定时任务时间时，需调整对应价格）
        for symbol in to_buy:
            # 收盘价（日频数据）
            new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=now_str, fields='close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['close']
            # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）
            # new_price = current(symbols=symbol)[0]['price']
            order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            else:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            else:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type==1 else '市价'
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))
       

def get_normal_stocks(date,new_days=365,skip_suspended=True, skip_st=True):
    """
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））
    :param date：目标日期
    :param new_days:新股上市天数，默认为365天
    """
    date = pd.Timestamp(date).replace(tzinfo=None)
    # A股，剔除停牌和ST股票
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)
    stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))
    stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))
    # 剔除次新股和退市股
    stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>date)]
    all_stocks = list(stocks_info['symbol'])
    all_stocks_str = ','.join(all_stocks)
    return all_stocks,all_stocks_str


def on_backtest_finished(context, indicator):
    print('*'*50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
    '''
    run(strategy_id='cf49797a-edf4-11ef-a4cf-00ff2dbfd94c',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='c2a347464c31056c84165dff766750fbf2ec67b4',
        backtest_start_time='2021-01-01 08:00:00',
        backtest_end_time='2021-12-31 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)

# 日内回转交易
## coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *

import datetime
import pandas as pd
import numpy as np

'''
示例策略仅供参考，不建议直接实盘使用。

日内回转交易是指投资者就同一个标的（如股票）在同一个交易日内各完成多次买进和卖出的行为
其目的为维持股票数量不变，降低股票成本
本策略以1分钟MACD为基础，金叉时买入，死叉时卖出，尾盘回转至初始仓位
'''


def init(context):
    # 设置标的股票
    context.all_symbols = ['SHSE.600000','SHSE.601963']
    # 用于判定第一个仓位是否成功开仓
    context.first = {symbol:0 for symbol in context.all_symbols}
    # 需要保持的总仓位
    context.total = 10000
    # 日内回转每次交易数量
    context.trade_n = 500
    # 使用的频率，60s为1分钟bar，300s为5分钟bar
    context.frequency = '60s'
    # 回溯数据长度（计算MACD)
    context.periods_time = 100
    # 订阅数据
    subscribe(symbols=context.all_symbols,
              frequency=context.frequency,
              count=context.periods_time,
              fields='symbol,eob,close')


def on_bar(context, bars):
    bar = bars[0]
    symbol = bar['symbol']
    # 配置底仓
    if context.first[symbol] == 0:
        context.first[symbol] = 1
        # 购买10000股浦发银行股票
        order_volume(symbol=symbol,
                     volume=context.total,
                     side=OrderSide_Buy,
                     order_type=OrderType_Market,
                     position_effect=PositionEffect_Open)
        print('{}：{}建底仓，以市价单开多仓{}股'.format(context.now, symbol, context.total))
        return

    # 获取持仓
    position = list(filter(lambda x:x['symbol']==symbol,get_position()))
    if len(position)==0:return

    # 可用仓位
    available_volume = position[0]['volume'] - position[0]['available_today']

    # 尾盘回转仓位
    if context.now.hour == 14 and context.now.minute >= 57 or context.now.hour == 15:
        if position[0]['volume'] != context.total:
            order_target_volume(symbol=symbol,
                                volume=context.total,
                                order_type=OrderType_Market,
                                position_side=PositionSide_Long)
    # 非尾盘时间，正常交易(首日不交易，可用仓位为0)
    elif available_volume > 0:
        # 调用收盘价
        close = context.data(symbol=symbol,
                             frequency=context.frequency,
                             count=context.periods_time,
                             fields='close')['close'].values
        # 计算MACD线
        macd = MACD(close)[-1]
        # MACD由负转正时,买入
        if macd[-2] <= 0 and macd[-1] > 0:
            order_volume(symbol=symbol,
                         volume=context.trade_n,
                         side=OrderSide_Buy,
                         order_type=OrderType_Market,
                         position_effect=PositionEffect_Open)

        # MACD由正转负时,卖出
        elif macd[-2] >= 0 and macd[
                -1] < 0 and available_volume >= context.trade_n:
            order_volume(symbol=symbol,
                         volume=context.trade_n,
                         side=OrderSide_Sell,
                         order_type=OrderType_Market,
                         position_effect=PositionEffect_Close)


def EMA(S: np.ndarray, N: int) -> np.ndarray:
    '''指数移动平均,为了精度 S>4*N  EMA至少需要120周期     
    alpha=2/(span+1)

    Args:
        S (np.ndarray): 时间序列
        N (int): 指标周期

    Returns:
        np.ndarray: EMA
    '''
    return pd.Series(S).ewm(span=N, adjust=False).mean().values


def MACD(CLOSE: np.ndarray,
         SHORT: int = 12,
         LONG: int = 26,
         M: int = 9) -> tuple:
    '''计算MACD
    EMA的关系，S取120日

    Args:
        CLOSE (np.ndarray): 收盘价时间序列
        SHORT (int, optional): ema 短周期. Defaults to 12.
        LONG (int, optional): ema 长周期. Defaults to 26.
        M (int, optional): macd 平滑周期. Defaults to 9.

    Returns:
        tuple: _description_
    '''
    DIF = EMA(CLOSE, SHORT) - EMA(CLOSE, LONG)
    DEA = EMA(DIF, M)
    MACD = (DIF - DEA) * 2
    return DIF, DEA, MACD


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            else:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            else:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type == 1 else '市价'
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(
            context.now, symbol, order_type_word, side_effect, price, volume))


def on_backtest_finished(context, indicator):
    print('*' * 50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
    '''
    backtest_start_time = str(datetime.datetime.now() - datetime.timedelta(days=180))[:19]
    backtest_end_time = str(datetime.datetime.now())[:19]
    run(strategy_id='d24a90cb-edf4-11ef-a4cf-00ff2dbfd94c',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='c2a347464c31056c84165dff766750fbf2ec67b4',
        backtest_start_time=backtest_start_time,
        backtest_end_time=backtest_end_time,
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=200000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)
# 高权重成分股跟踪策略
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
本策略以0.8为初始权重跟踪指数标的沪深300中权重大于0.35%的成份股.  
个股所占的百分比为(0.8*成份股权重)*100%.然后根据个股是否:  
1.连续上涨5天 2.连续下跌5天  
来判定个股是否为强势股/弱势股,并对其把权重由0.8调至1.0或0.6  
'''  
  
  
def init(context):  
    # 强势股/弱势股判断周期，5天  
    context.days_num = 5  
    # 资产配置的初始权重,配比为0.6-0.8-1.0  
    context.high_ratio = 1.0  
    context.middle_ratio = 0.8  
    context.low_ratio = 0.6  
    context.index_symbol = 'SHSE.000300'  
    # 权重阈值  
    context.Threshold_weight = 0.0035  
    # 定时任务，日频  
    schedule(schedule_func=algo, date_rule='1d', time_rule='15:00:00')  
  
  
def algo(context):    
    # 当前时间  
    now_str = context.now.strftime('%Y-%m-%d')    
    # 历史交易日  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=context.days_num+1)  
    # 上一交易日  
    last_date = date_list[-1]   
      
    # 获取沪深300当时的成份股和相关数据  
    stock300 = stk_get_index_constituents(index=context.index_symbol, trade_date=last_date).set_index('symbol')  
    stock300['weight'] = stock300['market_value_circ']/stock300['market_value_circ'].sum()  
    stock300 = stock300[stock300['weight']>context.Threshold_weight]  
    to_buy = list(stock300.index)  
    print('{},选择的成分股权重总和为:{:.4f}'.format(context.now,np.sum(stock300['weight'])))  
      
    # 获取context.days_num+1个交易日前的日期  
    pre_n_day = date_list[0]  
    # 获取数据并转换成date*symbol的形式  
    history_data = history(symbol=','.join(to_buy), frequency='1d', start_time=pre_n_day,  end_time=last_date, fields='symbol, close, eob', adjust=ADJUST_PREV, df= True)  
    if len(history_data)==0:return  
    history_data = history_data.set_index(['eob','symbol'])  
    history_data = history_data.unstack().fillna(0)  
    history_data.columns = history_data.columns.droplevel(level=0)  
    # 筛选强势股  
    continur_up_info = (history_data>history_data.shift(1)).iloc[-context.days_num:,:].sum()  
    up_symbol = continur_up_info[continur_up_info==context.days_num]  
    up_symbol = list(up_symbol.index) if len(up_symbol)>0 else []  
    # 筛选弱势股  
    continur_down_info = (history_data<history_data.shift(1)).iloc[-context.days_num:,:].sum()  
    down_symbol = continur_down_info[continur_down_info==context.days_num]  
    down_symbol = list(down_symbol.index) if len(down_symbol)>0 else []  
    # 普通股票（非强势股，非弱势股）  
    common_symbol = list(set(history_data.columns)-set(up_symbol)-set(down_symbol))  
  
    ## 股票交易（注：本策略交易以收盘价为交易价格，当调整定时任务时间时，需调整对应价格）  
    # 获取持仓  
    positions = get_position()  
    holding = [position['symbol'] for position in positions]  
  
    # 卖出不在to_buy中的持仓  
    for position in positions:  
        symbol = position['symbol']  
        if symbol not in to_buy:              
            # 收盘价（日频数据）  
            new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=context.now, fields='close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['close']  
            # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
            # new_price = current(symbols=symbol)[0]['price']  
            order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
  
    # 买入股票（强势股）  
    for symbol in set(up_symbol)-set(holding):  
        buy_percent = stock300['weight'][symbol] * context.high_ratio  
        # 收盘价（日频数据）  
        new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=context.now, fields='close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['close']  
        # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
        # new_price = current(symbols=symbol)[0]['price']  
        order_target_percent(symbol=symbol, percent=buy_percent, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
  
    # 买入股票（弱势股）  
    for symbol in set(down_symbol)-set(holding):  
        buy_percent = stock300['weight'][symbol] * context.low_ratio  
        # 收盘价（日频数据）  
        new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=context.now, fields='close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['close']  
        # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
        # new_price = current(symbols=symbol)[0]['price']  
        order_target_percent(symbol=symbol, percent=buy_percent, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
          
    # 买入股票（普通股）  
    for symbol in set(common_symbol)-set(holding):  
        buy_percent = stock300['weight'][symbol] * context.middle_ratio  
        # 收盘价（日频数据）  
        new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=context.now, fields='close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['close']  
        # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
        # new_price = current(symbols=symbol)[0]['price']  
        order_target_percent(symbol=symbol, percent=buy_percent, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，目标仓位：{:.2%}'.format(context.now,symbol,order_type_word,side_effect,price,target_percent))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='e560072b-edf4-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2019-01-01 08:00:00',  
        backtest_end_time='2019-04-30 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# Fama-French三因子模型
## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
本策略基于Fama-French三因子模型。  
假设三因子模型可以完全解释市场，以三因子模型对每股股票进行回归计算其Alpha值，当alpha为负表明市场低估该股，因此应该买入。  
策略思路：  
计算市场收益率、个股的账面市值比和市值,并对后两个进行了分类,  
根据分类得到的组合分别计算其市值加权收益率、SMB和HML. 对各个股票进行回归(假设无风险收益率等于0)得到Alpha值.  
选取Alpha值小于0并为最小的10只股票进入标的池，每月初移仓换股  
'''  
  
  
def init(context):  
    # 成分股指数  
    context.index_symbol = 'SHSE.000300'  
    # 数据滑窗  
    context.date = 20  
    # 设置开仓的最大资金量  
    context.ratio = 0.8  
    # 账面市值比的大/中/小分类  
    context.BM_HIGH = 3.0  
    context.BM_MIDDLE = 2.0  
    context.BM_LOW = 1.0  
    # 市值大/小分类  
    context.MV_BIG = 2.0  
    context.MV_SMALL = 1.0  
    # 每个交易日的09:40 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')  
      
  
def algo(context):  
    # 当前时间  
    now = context.now  
    now_str = now.strftime('%Y-%m-%d')    
    # 获取上一个交易日的日期  
    last_day = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]  
    # 判断是否为每个月第一个交易日  
    if now.month!=pd.Timestamp(last_day).month:  
        # 获取沪深300成份股  
        stock300 = stk_get_index_constituents(index=context.index_symbol, trade_date=last_day)['symbol'].tolist()  
        # 过滤停牌、ST、退市及未上市的股票  
        stocks_info =  get_symbols(sec_type1=1010, symbols=stock300, trade_date=now.strftime('%Y-%m-%d'), skip_suspended=True, skip_st=True)  
        stock300 = [item['symbol'] for item in stocks_info if item['listed_date']<now and item['delisted_date']>now]  
        # 获取所有股票市值  
        fin = stk_get_daily_mktvalue_pt(symbols=stock300, fields='tot_mv', trade_date=last_day, df=True).sort_values(by='tot_mv')  
        # 净资产  
        ttl_eqy = stk_get_fundamentals_balance_pt(symbols=stock300, date=last_day, fields='ttl_eqy', df=True)  
        ttl_eqy['max_rpt_date'] = ttl_eqy.groupby(['symbol'])['rpt_date'].max == ttl_eqy['rpt_date']  
        ttl_eqy = ttl_eqy[ttl_eqy['max_rpt_date'] == True]  
        # 计算PB  
        fin = fin.merge(ttl_eqy,on=['symbol'],how='left')  
        fin['PB'] = fin['tot_mv']/fin['ttl_eqy']      
  
        # 计算账面市值比,为P/B的倒数  
        fin.loc[:,'PB'] = (fin['PB'] ** -1)  
  
        # 计算市值的50%的分位点,用于后面的分类  
        size_gate = fin['tot_mv'].quantile(0.50)  
  
        # 计算账面市值比的30%和70%分位点,用于后面的分类  
        bm_gate = [fin['PB'].quantile(0.30), fin['PB'].quantile(0.70)]  
        fin.index = fin.symbol  
  
        # 设置存放股票收益率的变量  
        data_df = pd.DataFrame()  
        # 对未停牌的股票进行处理  
        for symbol in fin.symbol:  
            # 计算收益率  
            close = history_n(symbol=symbol, frequency='1d', count=context.date + 1, end_time=last_day, fields='close',  
                            skip_suspended=True, fill_missing='Last', adjust=ADJUST_PREV, df=True)['close'].values  
            stock_return = close[-1] / close[0] - 1  
            pb = fin['PB'][symbol]  
            market_value = fin['tot_mv'][symbol]  
  
            # 获取[股票代码， 股票收益率, 账面市值比的分类, 市值的分类, 市值]  
            # 其中账面市值比的分类为：高（3）、中（2）、低（1）  
            # 市值的分类：大（2）、小（1）  
            if pb < bm_gate[0]:  
                if market_value < size_gate:  
                    label = [symbol, stock_return, context.BM_LOW, context.MV_SMALL, market_value]# 小市值/低BM                  
else:  
                    label = [symbol, stock_return, context.BM_LOW, context.MV_BIG, market_value]# 大市值/低BM  
            elif pb < bm_gate[1]:  
                if market_value < size_gate:  
                    label = [symbol, stock_return, context.BM_MIDDLE, context.MV_SMALL, market_value]# 小市值/中BM  
                else:  
                    label = [symbol, stock_return, context.BM_MIDDLE, context.MV_BIG, market_value]# 大市值/中BM  
            elif market_value < size_gate:  
                label = [symbol, stock_return, context.BM_HIGH, context.MV_SMALL, market_value]# 小市值/高BM  
            else:  
                label = [symbol, stock_return, context.BM_HIGH, context.MV_BIG, market_value]# 大市值/高BM  
            data_df = pd.concat([data_df,pd.DataFrame(label,index=['symbol', 'return', 'BM', 'tot_mv', 'mv']).T])  
        data_df.set_index('symbol',inplace=True)  
  
        # 调整数据类型  
        for column in data_df.columns:  
            data_df[column] = data_df[column].astype(np.float64)  
  
        # 计算小市值组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        smb_s = (market_value_weighted(data_df, context.MV_SMALL, context.BM_LOW) +  
                market_value_weighted(data_df, context.MV_SMALL, context.BM_MIDDLE) +  
                market_value_weighted(data_df, context.MV_SMALL, context.BM_HIGH)) / 3  
        # 计算大市值组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        smb_b = (market_value_weighted(data_df, context.MV_BIG, context.BM_LOW) +  
                market_value_weighted(data_df, context.MV_BIG, context.BM_MIDDLE) +  
                market_value_weighted(data_df, context.MV_BIG, context.BM_HIGH)) / 3  
        # 计算规模因子的收益率（小市值组收益率-大市值组收益率）  
        smb = smb_s - smb_b  
  
        # 计算高BM组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        hml_b = (market_value_weighted(data_df, context.MV_SMALL, context.BM_HIGH) +  
                market_value_weighted(data_df, context.MV_BIG, context.BM_HIGH)) / 2  
        # 计算低BM组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        hml_s = (market_value_weighted(data_df, context.MV_SMALL, context.BM_LOW) +  
                market_value_weighted(data_df, context.MV_BIG, context.BM_LOW)) / 2  
        # 计算价值因子的收益率（高BM组收益率-低BM市值组收益率）  
        hml = hml_b - hml_s  
  
        # 获取市场收益率  
        close = history_n(symbol=context.index_symbol, frequency='1d', count=context.date + 1,  
                        end_time=last_day, fields='close', skip_suspended=True,  
                        fill_missing='Last', adjust=ADJUST_PREV, df=True)['close'].values  
        market_return = close[-1] / close[0] - 1  
        coff_pool = []  
  
        # 对每只股票进行回归获取其alpha值  
        for stock in data_df.index:  
            x_value = np.array([[market_return], [smb], [hml], [1.0]])  
            y_value = np.array([data_df['return'][stock]])  
            # OLS估计系数  
            coff = np.linalg.lstsq(x_value.T, y_value, rcond=None)[0][3]  
            coff_pool.append(coff)  
  
        # 获取alpha最小并且小于0的10只的股票进行操作(若少于10只则全部买入)  
        data_df.loc[:,'alpha'] = coff_pool  
        symbols_pool = data_df[data_df.alpha < 0].sort_values(by='alpha').head(10).index.tolist()  
        positions = get_position()  
  
        # 平不在标的池的股票（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in symbols_pool:  
                # 开盘价（日频数据）  
                new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=context.now, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']  
                # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
                # new_price = current(symbols=symbol)[0]['price']  
                order_info = order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)  
  
        # 获取股票的权重  
        percent = context.ratio / len(symbols_pool)  
  
        # 买在标的池中的股票（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）  
        for symbol in symbols_pool:  
            # 开盘价（日频数据）  
            new_price = history_n(symbol=symbol, frequency='1d', count=1, end_time=context.now, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']  
            # # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
            # new_price = current(symbols=symbol)[0]['price']  
            order_info = order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Limit,position_side=PositionSide_Long,price=new_price)  
  
  
def market_value_weighted(df, MV, BM):  
    """  
    计算市值加权下的收益率  
    :param MV：MV为市值的分类对应的组别  
    :param BM：BM账目市值比的分类对应的组别  
    """    select = df[(df['tot_mv'] == MV) & (df['BM'] == BM)] # 选出市值为MV，账目市值比为BM的所有股票数据  
    mv_weighted = select['mv']/np.sum(select['mv'])# 市值加权的权重  
    return_weighted = select['return']*mv_weighted# 市值加权下的收益率  
    return np.sum(return_weighted)  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，目标仓位：{:.2%}'.format(context.now,symbol,order_type_word,side_effect,price,target_percent))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='f0b1b3f8-edf4-11ef-a4cf-00ff2dbfd94c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='c2a347464c31056c84165dff766750fbf2ec67b4',  
        backtest_start_time='2021-08-01 08:00:00',  
        backtest_end_time='2022-02-10 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 打板助手
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
from StrategyAPIcom import toml_file,MyLog,QQemail  
from StrategyAPIgm import change_stock_code,cal_stock_buy_volume  
  
  
"""  
  
2024.04.28：  
1.新增开盘涨幅9%，回落后冲击涨停的股票监控。  
"""  
  
  
### 策略中必须有init方法  
def init(context):              
    # 1、日志组件  
    context.Logger = MyLog()   
  
    # 1、读取策略参数  
    config_path = r'C:\Users\Administrator\Desktop\XQ\打板-config.toml'           # 参数文件的地址,参数文件可以放在任何一个地方，但文件路径要填对  
    toml_file_fun = toml_file(file_path=config_path)  
    context.strategy_params = toml_file_fun.read_toml()  
    # 处理股票代码  
    i = 0  
    context.buy_stocks = []  
    context.symbols_callback = []  
    for key in context.strategy_params['buy_stock'].keys():  
        i += 1  
        context.strategy_params['buy_stock'][key]['交易标的'] = [change_stock_code(code) for code in context.strategy_params['buy_stock'][key]['交易标的'].split(',')]  
        context.strategy_params['buy_stock'][key]['callback'] = [change_stock_code(code) for code in context.strategy_params['buy_stock'][key]['callback'].split(',')]  
        context.symbols_callback += context.strategy_params['buy_stock'][key]['callback']  
        context.Logger.print_LogInfo('{}:当前监控板块 {} 股票：{}, callback股票：{}'.format(context.now,key,context.strategy_params['buy_stock'][key]['交易标的'], context.strategy_params['buy_stock'][key]['callback']))  
        context.buy_stocks = context.buy_stocks+context.strategy_params['buy_stock'][key]['交易标的']  
    context.buy_stocks += context.symbols_callback  
  
    context.holding_stocks =  [change_stock_code(code) for code in context.strategy_params['sell_stock']['symbols'].split(',')]  
    print('持仓监控股票：{}'.format(context.holding_stocks))  
    context.all_symbols = context.buy_stocks+context.holding_stocks  
  
    position = context.account().positions()  
    context.holding_symbol = [pos['symbol'] for pos in position]  
      
    # 2、定时任务：获取涨停价  
    get_limit_up(context)  
    # schedule(schedule_func=get_limit_up, date_rule='1d', time_rule='09:10:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.strategy_params['trade_parameter']['cond2_sell_time1'])  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.strategy_params['trade_parameter']['cond2_sell_time2'])  
  
    # 3、订阅数据  
    subscribe(symbols=context.all_symbols, frequency='tick', count=context.strategy_params['trade_parameter']['cond1_monitor_diff_num']+1,   
                unsubscribe_previous=True,fields='created_at,symbol,price')  
  
  
def get_limit_up(context):  
    # 获取涨跌停价格  
    trade_date = context.now.strftime('%Y-%m-%d')  
    dicts = get_symbols(sec_type1=1010, symbols=context.all_symbols, skip_suspended=False, skip_st=False, trade_date=trade_date, df=False)  
    context.monitor_data = {dic['symbol']:{'pre_close':dic['pre_close'], 'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                            'monitor_buy_price':np.round(dic['pre_close']*(context.strategy_params['trade_parameter']['cond1_monitor_rate']+1),2),  
                                            'monitor_sell_price':np.round(dic['pre_close']*(context.strategy_params['trade_parameter']['cond1_sell_rate']+1),2),  
                                            'monitor_sell3_status':False} for dic in dicts}  
    context.Logger.print_LogInfo(context.monitor_data)  
      
  
def on_tick(context,tick):  
    try:  
        if tick['symbol'] in context.buy_stocks and tick['symbol'] in context.monitor_data.keys() and tick['price']>=context.monitor_data[tick['symbol']]['monitor_buy_price'] and tick['created_at'].time()<datetime.datetime.strptime(context.strategy_params['trade_parameter']['cond1_end_time'], '%H:%M:%S').time():  
            # 条件1：持仓中无该股票  
            # position = context.account().positions()  
            # holding_symbol = [pos['symbol'] for pos in position]            cond1 = tick['symbol'] not in context.holding_symbol  
  
            # 条件2：最近N个tick的涨速超过阈值  
            data = context.data(symbol=tick['symbol'], frequency='tick', count=context.strategy_params['trade_parameter']['cond1_monitor_diff_num']+1,fields='created_at,price')  
            data['rate'] = (data['price']-data['price'].shift(1))/context.monitor_data[tick['symbol']]['pre_close']  
            cond2 = (data['rate']>=context.strategy_params['trade_parameter']['cond1_monitor_diff_rise']).any()  
  
            # 条件3:9:30之后  
            cond3 = tick['created_at'].time()>datetime.datetime.strptime('09:30:00', '%H:%M:%S').time()  
            if cond1 and cond2 and cond3:  
                volume = cal_stock_buy_volume(tick['symbol'],context.strategy_params['trade_parameter']['cond1_buy_amount'],context.monitor_data[tick['symbol']]['upper_limit'])  
  
                # 非类一字板  
                if tick['symbol'] not in context.symbols_callback:  
                    context.holding_symbol.append(tick['symbol'])  
                    order_volume(symbol=tick['symbol'], volume=volume, side=OrderSide_Buy, order_type=OrderType_Limit, price=context.monitor_data[tick['symbol']]['upper_limit'], position_effect=PositionEffect_Open)  
                else:  
                    # 类一字板的条件：盘中价格需要低于9%后，才能打板  
                    cond4 = tick['low']<np.round(context.monitor_data[tick['symbol']]['pre_close']*(context.strategy_params['trade_parameter']['callback_rate']+1),2)  
                    if cond4:  
                        context.holding_symbol.append(tick['symbol'])  
                        order_volume(symbol=tick['symbol'], volume=volume, side=OrderSide_Buy, order_type=OrderType_Limit, price=context.monitor_data[tick['symbol']]['upper_limit'], position_effect=PositionEffect_Open)  
                    else:  
                        return  
  
                # 取消该板块下其他股票的监控  
                for key in context.strategy_params['buy_stock'].keys():  
                    if  tick['symbol'] in context.strategy_params['buy_stock'][key]['交易标的'] or tick['symbol'] in context.strategy_params['buy_stock'][key]['callback']:  
                        context.strategy_params['buy_stock'][key]['交易数量'] -= 1  
                        context.strategy_params['trade_parameter']['total_buy_num'] -= 1  
                        if context.strategy_params['trade_parameter']['total_buy_num']==0:  
                            unsubscribe(symbols='*', frequency='tick')  
                            subscribe(symbols=context.strategy_params['sell_stock']['symbols'], frequency='tick', count=context.strategy_params['trade_parameter']['cond1_monitor_diff_num']+1, unsubscribe_previous=True,fields='created_at,symbol,price')  
                            context.Logger.print_LogInfo('{}:交易数量耗尽，取消所有订阅'.format(context.now))  
  
                        if context.strategy_params['buy_stock'][key]['交易数量']==0:  
                            unsubscribe(symbols=context.strategy_params['buy_stock'][key]['交易标的'], frequency='tick')  
                            context.Logger.print_LogInfo('{}:取消 {} 板块的订阅：{}'.format(context.now,key,context.strategy_params['buy_stock'][key]['交易标的']))  
          
        # 卖出  
        if tick['symbol'] in context.holding_stocks and tick['symbol'] in context.monitor_data.keys() and tick['created_at'].time()>=datetime.datetime.strptime('09:30:00', '%H:%M:%S').time():  
            # 卖出条件1，跌幅超过**  
            sell_cond1 = tick['price']<=context.monitor_data[tick['symbol']]['monitor_sell_price']  
            if sell_cond1:  
                sell_order(context,symbol=tick['symbol'],sell_price=context.monitor_data[tick['symbol']]['lower_limit'])  
                return  
  
            # # 卖出条件2，超过时间未涨停  
            # sell_cond2 = tick['created_at'].time()>datetime.datetime.strptime(context.strategy_params['trade_parameter']['cond2_sell_time2'], '%H:%M:%S').time() and tick['price']<context.monitor_data[tick['symbol']]['upper_limit']  
            # if sell_cond2:            #     sell_order(context,symbol=tick['symbol'],sell_price=context.monitor_data[tick['symbol']]['lower_limit'])            #     return  
            # 卖出条件3，规定时间内，开板  
            data = context.data(symbol=tick['symbol'], frequency='tick', count=context.strategy_params['trade_parameter']['cond1_monitor_diff_num']+1,fields='created_at,price,quotes')  
            if context.monitor_data[tick['symbol']]['monitor_sell3_status']:  
                if tick['price']>=context.monitor_data[tick['symbol']]['upper_limit'] and data['quotes'].iloc[-1][0]['bid_v']>=context.strategy_params['trade_parameter']['cond3_sell_start_volume']:  
                    context.monitor_data[tick['symbol']]['monitor_sell3_status'] = True  
            if context.monitor_data[tick['symbol']]['monitor_sell3_status']:  
                reduce_rate = (data['quotes'].iloc[-2][0]['bid_v']-data['quotes'].iloc[-1][0]['bid_v'])/data['quotes'].iloc[-2][0]['bid_v']  
                sell_cond3_1 = reduce_rate>context.strategy_params['trade_parameter']['cond3_sell_volume_rate'] # tick内成交量大于封单量的1/3  
                sell_cond3_2 = tick['created_at'].time()>=datetime.datetime.strptime(context.strategy_params['trade_parameter']['cond3_sell_time'], '%H:%M:%S').time() # 下午  
                sell_cond3_3 = tick['price']<context.monitor_data[tick['symbol']]['upper_limit']# 开板  
  
                if sell_cond3_2 and (sell_cond3_1 or sell_cond3_3):  
                    sell_order(context,symbol=tick['symbol'],sell_price=context.monitor_data[tick['symbol']]['lower_limit'])  
                    return  
    except:  
        pass  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.strategy_params['trade_parameter']['cond2_sell_time1']:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.strategy_params['trade_parameter']['cond2_sell_time2']:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
  
def sell_order(context,symbol,sell_price):  
    # 有持仓  
    position = context.account().position(symbol=symbol,side = PositionSide_Long)  
    if position:  
        available_now = position['volume']-position['volume_today'] if context.mode==MODE_BACKTEST else position['available_now']  
        if available_now>0:  
            order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=sell_price, position_effect=PositionEffect_Close)  
              
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        context.Logger.print_LogInfo('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
  
  
def on_error(context, code, info):  
    context.Logger.print_LogInfo(code)  
    context.Logger.print_LogInfo(info)  
    if code=='1100' or code=='1101' or code=='1200' or code=='1201':  
        pass  
    else:  
        stop()  
  
  
def on_backtest_finished(context, indicator):  
    context.Logger.print_LogInfo('*'*50)  
    context.Logger.print_LogInfo('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='fc00a55f-d5d5-11ee-9d33-00163e39007c',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='41c6860c10ca26a3d49232d7df232c4370211ac3',  
        backtest_start_time='2024-06-12 08:00:00',  
        backtest_end_time='2024-06-12 15:30:30',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

## -*- coding: utf-8 -*-  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import os  
import sys  
import json  
import toml    
### import talib  
import logging  
import datetime  
import numpy as np  
import pandas as pd  
import statsmodels.api as sm  
from StrategyAPIgm import stk_get_daily_mktvalue_pt_new  
  
"""  
Created on Wed Oct 11 09:53:15 2023  
  
@author:Daizhongbao  
"""  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    if inclusive:  
        data[data>mean_+std_*scale]=mean_+std_*scale  
        data[data<mean_-std_*scale]=mean_-std_*scale  
    else:  
        data[data>mean_+std_*scale]=np.nan  
        data[data<mean_-std_*scale]=np.nan  
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
      
  
def neutralize_MarketValue(context,data,date,counts=1):  
    """  
    市值中性化  
    :param data:待处理数据  
    :param date:目标日期  
    :param counts：历史回溯天数  
    """    if isinstance(data,pd.Series):  
        data = data.to_frame()  
    security = data.index.to_list()  
    market_value = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', end_date=date, counts=counts, df=True)  
    max_date = market_value['trade_date'].max()  
    market_value = market_value[market_value['trade_date']==max_date][['symbol','tot_mv']].set_index('symbol')  
    x = sm.add_constant(market_value)  
    common_index = list(set(x.index) & set(data.index))  
    x = x.loc[common_index,:]  
    data = data.loc[common_index,:]  
    residual = sm.OLS(data, x).fit().resid# 此处使用最小二乘回归计算  
    return residual  
  
  
def cal_MACD(context,data):  
    """  
    计算单标的MACD  
    ：return diff:快线  
    ：return dea:慢线  
    ：return macd:红绿柱  
        """  
    # 计算MACD  
    diff,dea,macd = talib.MACD(data, fastperiod=12,slowperiod=26,signalperiod=9)  
    macd = (diff-dea)*2  
    return macd,diff,dea  
  
  
def cal_MACDs(context,security,date,data):  
    """  
    计算多标的MACD  
    ：return macd_df:对应diff  
    ：return signal_df:对应dea  
    ：return hist_df:对应macd  
        """  
    securityList = security.split(',')  
    # 计算MACD  
    macd_df,signal_df,hist_df = pd.DataFrame(index=[date],columns=securityList),pd.DataFrame(index=[date],columns=securityList),pd.DataFrame(index=[date],columns=securityList)  
    for code in securityList:  
        macd,signal,hist = talib.MACD(data[code], fastperiod=12,slowperiod=26,signalperiod=9)  
        hist = (macd-signal)*2  
        macd_df.loc[date,code]=macd[-1]  
        signal_df.loc[date,code]=signal[-1]  
        hist_df.loc[date,code]=hist[-1]  
    return macd_df,signal_df,hist_df  
  
  
import logging  
class MyLog():  
    def __init__(self, level=logging.INFO, file_name = r'mylog.log', file_url = r'./log'):  
        if not os.path.exists(file_url):  
            os.mkdir(file_url)  
        logging.basicConfig(filename=os.path.abspath(r'log/{}_{}'.format(datetime.datetime.now().strftime('%Y-%m-%d'),file_name)), level=level,  
                            format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')  
  
    def print_LogInfo(self,word,price_status=True):      
        if price_status:  
            print(word)  
        logging.info(word)    
          
    def print_LogErroe(self,word,price_status=True):      
        if price_status:  
            print(word)  
        logging.error(word)   
  
  
class MyLocalData():  
    def __init__(self,file_url = r'./Local_Data'):  
        self.file_url = file_url  
        if not os.path.exists(self.file_url):  
            os.mkdir(file_url)  
              
    #  读取本地json文件  
    def read_json_file(self, file_suffix):  
        local_file = self.file_url+file_suffix  
        data = None  
        if os.path.exists(local_file):  
            with open(local_file) as f:  
                data = json.loads(f.read())  
        return data  
  
  
    #  保存json文件  
    def save_json_file(self, data, file_suffix):  
        local_file = self.file_url+file_suffix  
        with open(local_file, 'w') as f:  
            json.dump(data, f)  
  
  
class QQemail():  
    def __init__(self,email_user, email_pass, message_from=None, message_to='掘金者', message_subject='交易信号提示'):  
          
        import smtplib  
        from email.mime.text import MIMEText  
        from email.header import Header  
          
        self.email_host="smtp.qq.com"                                                                                  # QQ邮箱的host地址，不需要修改  
        self.email_user=email_user                                                                                     # 邮箱地址  
        self.email_pass=email_pass                                                                                     # 口令/授权码  
        self.receivers = [self.email_user]                                                                             # 接收邮箱  
        self.message_from = 'myquant <{}>'.format(self.email_user)  if message_from is None else message_from          # 发件人名称  
        self.message_to = message_to                                                                                   # 收件人名称  
        self.message_subject = message_subject                                                                         # 邮件的主题  
  
  
    def send_email(self,email_message):  
        """  
        发送邮件提示信息  
        :param email_message:邮件内容  
        :param email_from:发件人名称  
        :param email_to:收件人名称  
        :param email_subject:邮件主题  
        :param email_host:SMTP 服务器主机,qq邮箱为："smtp.qq.com"  
        :param email_user: 邮箱发送人地址  
        :param email_pass: 邮箱SMTP授权码  
        :param email_receivers:邮箱接收人地址  
        """        if self.email:  
            message = MIMEText(email_message, 'plain', 'utf-8')  
            message['From'] = Header(self.message_from)  
            message['To'] =  Header(self.message_to, 'utf-8')  
            message['Subject'] = Header(self.message_subject, 'utf-8')  
            try:  
                smtpObj = smtplib.SMTP_SSL(host=self.email_host)  
                smtpObj.connect(self.email_host, 465)    # 465为SMTP加密端口号  
                smtpObj.login(self.email_user,self.email_pass)  
                smtpObj.sendmail(self.email_user, self.receivers, message.as_string())  
                print('邮件发送成功：{}'.format(email_message))  
            except:  
                print('{}:邮件发送失败，请检查！'.format(self.now))  
        else:  
            print(email_message)  
  
  
class toml_file():  
    def __init__(self,file_path=r'.\config.toml'):       
     
        self.file_path = file_path  
              
    def read_toml(self):  
        # 读取本地参数文件配置  
        with open(self.file_path, "r", encoding='utf8') as f:  
            file_context = toml.load(f)  
        return file_context

## -*- coding: utf-8 -*-  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import os  
import sys  
import json  
import logging  
import datetime  
import numpy as np  
import pandas as pd  
  
"""  
Created on Wed Oct 11 09:53:15 2023  
  
@author:Daizhongbao  
"""  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def change_stock_code(code):  
    """将股票代码转换为掘金代码"""  
    code = str(code)  
    if code=='':return ''  
    code = (6-len(code))*'0'+code if len(code)<6 else code  
    if code[:2] in ['SZ','sz','SH','sh']:  
        if code[-6]=='6':  
            code = 'SHSE.'+code[-6:]  
        else:  
            code = 'SZSE.'+code[-6:]  
    elif code[0] in ['0','3','6']:  
        if code[0]=='6':  
            code = 'SHSE.'+code[:6]  
        else:  
            code = 'SZSE.'+code[:6]  
    return code  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str  
  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=context.trading_dates['date'].iloc[-1][:4])  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=context.trading_dates['date'].iloc[0][:4], end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def get_N_trading_date(context,date,counts=1,model='previous',exchange='SHSE'):  
    """  
    获取end_date前N个交易日,end_date为datetime格式，不包括date日期  
    :param date：目标日期  
    :param counts：历史回溯天数，默认为1，即前一天，该值需为非零正整数  
    :param model: 模式，默认为 previous，即前N个交易日； next 模式为后N个交易日  
    """    if counts<=0 or not isinstance(counts, int):  
        print('参数类型错误：get_N_trading_date()的counts参数需为非零正整数.')  
        import sys  
        sys.exit(-1)  
    date = pd.Timestamp(date)  
    date_str = date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=date.year-1, end_year=datetime.datetime.now().year)  
    # 判断当前日期是否为交易日  
    pre_times = 1  
    if date_str not in list(context.trading_dates['trade_date']) and str(date.year) in set([x[:4] for x in context.trading_dates['trade_date']]):# 非交易日  
        date = context.trading_dates[context.trading_dates['date']==date_str]['pre_trade_date'].iloc[0]  
        pre_times = 0  
    date = pd.Timestamp(date)  
    date_str = date.strftime('%Y-%m-%d')  
    # 历史N个交易日  
    for i in range(2):  
        trading_dates_dropna = context.trading_dates.replace('',np.nan).dropna(how='any')  
        if model == 'previous':  
            previous_date_df = trading_dates_dropna[trading_dates_dropna['trade_date']<=date_str]  
            if len(previous_date_df)>=counts+1:  
                return previous_date_df['trade_date'].iloc[-counts-pre_times]  
            else:  
                start_date = pd.Timestamp(context.trading_dates['date'].iloc[0])-datetime.timedelta(days = int(365*np.ceil((counts-len(previous_date_df))/250)))  
                start_year = min(start_date.year-1,date.year)  
                end_year = int(context.trading_dates['date'].iloc[-1][:4])  
                context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_year, end_year=end_year)  
        elif model == 'next':  
            previous_date_df = trading_dates_dropna[trading_dates_dropna['trade_date']>=date_str]  
            if len(previous_date_df)>=counts+1:  
                return previous_date_df['trade_date'].iloc[counts]  
            else:  
                end_date = pd.Timestamp(context.trading_dates['date'].iloc[-1])+datetime.timedelta(days = max(int(365*np.ceil((counts-len(previous_date_df))/250)),1))  
                start_year = int(context.trading_dates['date'].iloc[0][:4])  
                end_year = max(end_date.year,date.year)  
                context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_year, end_year=end_year)  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_N_trading_date(context,date=end_date,counts=counts,model='previous',exchange='SHSE')  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def stk_get_daily_basic_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_basic_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_N_trading_date(context,date=end_date,counts=counts,model='previous',exchange='SHSE')  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
          
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_basic_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def get_period_price_wjlec(stock, rule='W', start_date=None, end_date=None, frequency='1d', fields=None, skip_suspended = True, expect_df=False, adjust_type='pre'):  
    '''  
    极简方法获取真实的日线、周线、月线或其他周期数据，本函数考虑了假期情况，不会列出不交易的日期。  
    :param stock : 证券代码  
    :param rule : K线类别，默认是周线'W'，月线是'M',季度线是'Q',年线是'A'。期间代码也可以使用周K（'W-FRI'）、月K（'BM'）、年K（'A-DEC'）  
    :param end_date : 默认None, 对应使用attribute_history; 否则使用get_price  
    :param 其他参数：同get_price  
    :return dataframe: index:时间序列   
    范例：  
        df=get_period_price_wjlec('601668.XSHG', start_date='2017-1-1', end_date='2017-5-31')#获取周线  
        df=get_period_price_wjlec('601668.XSHG', rule='W', count=120, end_date='2017-6-1')#获取周线  
        df=get_period_price_wjlec('601668.XSHG', rule='M', start_date='2017-1-1', end_date='2017-5-31')#获取月线  
        df=get_period_price_wjlec('601668.XSHG', rule='M', count=1000, end_date='2017-5-31')#获取月线  
    '''    #设定转换周期period_type  转换为周是'W',月'M',季度线'Q',五分钟'5min',12天'12D'  
    period_type = rule  
      
    stock_data = get_price(stock, start_date=start_date, end_date=end_date,   
                 frequency=frequency, fields=['open','high','low','close', 'volume', 'total_turnover'],   
              adjust_type=adjust_type, skip_suspended = skip_suspended, market='cn', expect_df=expect_df)  
      
    print(stock_data)  
    #记录每个周期中最后一个交易日  
    stock_data['date'] = stock_data.index  
    period_type=rule  
    #进行转换，周线的每个变量都等于那一周中最后一个交易日的变量值  
    period_stock_data = stock_data.resample(period_type, how='last')  
    #周线的open等于那一周中第一个交易日的open  
    period_stock_data['open'] = stock_data['open'].resample(period_type,how='first')  
    #周线的high等于那一周中的high的最大值  
    period_stock_data['high'] = stock_data['high'].resample(period_type,how='max')  
    #周线的low等于那一周中的low的最大值  
    period_stock_data['low'] = stock_data['low'].resample(period_type,how='min')  
    #周线的volume和money等于那一周中volume和money各自的和  
    period_stock_data['volume'] = stock_data['volume'].resample(period_type,how='sum')  
    period_stock_data['total_turnover'] = stock_data['total_turnover'].resample(period_type,how='sum')  
    # 剔除整个周期都休盘或停牌的数据  
    period_stock_data = period_stock_data.dropna()  
    period_stock_data.set_index('date',inplace=True)  
      
    return period_stock_data  
  
  
class MyLocalPositions():  
    def __init__(self,file_url = r'./Local_Positions.csv'):  
        self.file_url = file_url  
        if os.path.exists(self.file_url):  
            self.position = pd.read_csv(self.file_url, header=0, index_col=0, parse_dates=True, engine='python')  
        else:  
            self.position = pd.DataFrame(columns=['Symbol', 'Side', 'Quantity', 'Available', 'Price', 'Time', 'Cl_ord_id'])#生成持仓  
        # 添加一个新的持仓  
    def add_position(self, symbol, side, quantity, available, price=None, time=None, cl_ord_id=None):  
        new_row = {'Symbol':symbol, 'Side':side, 'Quantity':quantity, 'Available':available, 'Price':price, 'Time':time, 'Cl_ord_id':cl_ord_id}  
        self.position = pd.concat([self.position,pd.DataFrame(new_row,index=[0])], ignore_index=True)  
        self.position.to_csv(self.file_url, index=True)  # 持仓保存到本地  
  
    # 更新一个持仓  
    def update_position(self, symbol, side, quantity=None, available=None, price=None, time=None, cl_ord_id=None):  
        index = (self.position['Symbol'] == symbol)&(self.position['Side'] == side)  
        if quantity is not None:  
            self.position.loc[index, 'Quantity'] = quantity  
        if available is not None:  
            self.position.loc[index, 'Available'] = available  
        if price is not None:  
            self.position.loc[index, 'Price'] = price  
        if time is not None:  
            self.position.loc[index, 'Time'] = time  
        if cl_ord_id is not None:  
            self.position.loc[index, 'Cl_ord_id'] = cl_ord_id  
        self.position.to_csv(self.file_url, index=True)  # 持仓保存到本地  
  
    # 删除一个持仓  
    def delete_position(self, symbol, side):  
        index = self.position[(self.position['Symbol'] == symbol)&(self.position['Side'] == side)].index  
        self.position = self.position.drop(index)  
        self.position.to_csv(self.file_url, index=True)#持仓保存到本地  
  
    # 保存持仓  
    def save_position(self):  
        self.position.to_csv(self.file_url, index=True)#持仓保存到本地  
  
    # 显示所有持仓  
    def display_positions(self):  
        print('当前本地持仓：')  
        print(self.position)  
  
    # 检查本地持仓和账户持仓是否匹配：本地数据再账户中是否存在(标的和方向)，不存在则删除该数据  
    def check_position(self):  
        positions = get_position()  
        times = 0  
        for i in range(len(self.position)-1,-1,-1):  
            index = self.position.index[i]  
            symbol = self.position.loc[index,'Symbol']  
            side = self.position.loc[index,'Side']  
            if len(list(filter(lambda x:x['symbol']==symbol and x['side']==side, positions)))==0:  
                print('检测到本地持仓余账户不匹配，删除{}，持仓方向：{}'.format(symbol,side))  
                self.position = self.position.drop(index)  
                times += 1  
        if times>0:  
            self.position.to_csv(self.file_url, index=True)#持仓保存到本地  
            self.display_positions()  
  
  
class MyStatistics():  
    def __init__(self,his_asset_url,per_stat_url,tot_stat_url):  
        # 汇总统计表  
        self.tot_stat_url = tot_stat_url   
        self.tot_stat_flie = pd.read_excel(self.tot_stat_url,header=0)  
  
        # 区间统计表  
        self.per_stat_url = per_stat_url   
        self.per_stat_file = pd.read_excel(self.per_stat_url,header=0)  
  
        # 历史资产表  
        self.his_asset_url = his_asset_url          
        self.his_asset_file = pd.read_excel(self.his_asset_url,header=0,index_col=0)  
          
    # 更新汇总统计  
    def updata_stat_tot_stat(self, df):  
        # 设置Excel输出格式  
        columns = ['策略名称','开始日期','结束日期','总收益率','年化收益率','最大回撤','夏普比率','收益回撤比','胜率','平仓次数','说明']  
        for column in columns:  
            if column in ['总收益率','年化收益率','最大回撤','胜率']:  
                for i in range(len(df)):  
                    index = df.index[i]  
                    df.loc[index,column] = format(df.loc[index,column], '.2%').replace('nan%','')  
            if column in ['夏普比率','收益回撤比']:  
                for i in range(len(df)):  
                    index = df.index[i]  
                    df.loc[index,column] = format(df.loc[index,column], '.2f').replace('nan%','')  
        #保存到本地  
        self.tot_stat_flie = pd.concat([df,self.tot_stat_flie]).sort_values('结束日期',ascending=False).drop_duplicates(subset=['策略名称'],keep='first')   
        self.tot_stat_flie.sort_values('年化收益率',ascending=False,inplace=True)  
        self.tot_stat_flie.index = range(len(self.tot_stat_flie))  
        # 保存  
        self.tot_stat_flie[columns].to_excel(self.tot_stat_url, index=False)  
  
    # 更新区间统计  
    def updata_stat_per_stat(self, df):  
        # 设置Excel输出格式  
        columns = ['日期','策略名称','最近 1 天收益率','最近 1 个月收益率','最近 3 个月收益率','最近 1 年收益率','总收益率']  
        for column in columns:  
            if column in ['最近 1 天收益率','最近 1 个月收益率','最近 3 个月收益率','最近 1 年收益率','总收益率']:  
                for i in range(len(df)):  
                    index = df.index[i]  
                    df.loc[index,column] = format(df.loc[index,column], '.2%').replace('nan%','')  
        #保存到本地  
        self.per_stat_file = pd.concat([df,self.per_stat_file]).sort_values('日期',ascending=False).drop_duplicates(subset=['策略名称'],keep='first')   
        self.per_stat_file.sort_values('最近 1 个月收益率',ascending=False,inplace=True)  
        self.per_stat_file.index = range(len(self.per_stat_file))  
        # 保存  
        self.per_stat_file[columns].to_excel(self.per_stat_url, index=False)  
  
    # 更新历史净值  
    def updata_stat_his_asset(self, df):  
        #保存到本地  
        columns = df.columns[0]  
        if columns in self.his_asset_file.columns:  
            self.his_asset_file.drop(columns,axis=1,inplace=True)  
        self.his_asset_file = pd.concat([self.his_asset_file,df],axis=1)  
        # 设置Excel输出格式  
        for column in self.his_asset_file.columns:  
            for i in range(len(self.his_asset_file)):  
                index = self.his_asset_file.index[i]  
                self.his_asset_file.loc[index,column] = format(self.his_asset_file.loc[index,column], '.4f').replace('nan%','')  
        self.his_asset_file.sort_values(self.his_asset_file.index[0],axis=1,ascending=False).sort_index(ascending=False).to_excel(self.his_asset_url, index=True)


# 杯柄形态策略
## coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *

import numpy as np
import pandas as pd
from datetime import datetime,timedelta

"""
杯柄形态策略
1.B点均>=前后两个点；
2.A点>=B点，且A点均>=前后两个点;
3.A点到杯底之间所有收盘价低于A；杯底到B点之间所有收盘价低于B
4.A点到杯底之间回调处于[-10%,-35%]之间,杯底到B点之间上涨处于[10%,35%]之间
5.A点前的最小涨幅至少在 30%
6.杯身呈U型,采用插值法进行抛物线拟合，筛选满足误差比例的形态；
7.突破B时买入；
8.25%止盈，8%最大回撤止盈止损。

其他：5分钟频率，最大持有10只股票
"""
### 策略中必须有init方法
def init(context):
    # context.trade_fre = 'D'            # 交易频率，日频：D,周线是'W'，月线是'M'
    context.trade_periods = 126          # 杯柄的最大周期长度，默认日频对应是6个月，即126个交易日；其他频率需要修规对应周期长度
    context.span = 3                     # A点和B点的最小跨度
    context.min_Retracement = -0.10      # A点到最低点的最小回调幅度
    context.max_Retracement = -0.35      # A点到最低点的最大回调幅度
    context.min_tracement = 0.10         # 最低点到B点的最小回调幅度
    context.max_tracement = 0.35         # 最低点到B点的最大回调幅度
    context.min_rise_rate = 0.30         # A点前的最小涨幅
    context.max_mpe = 0.01                               # U型底的插值最大误差比例
    context.max_holding = 10                              # 最大持股数量
    context.stop_profit = 0.25                            # 卖出条件1：止盈比例
    context.max_drawdown = 0.08                           # 卖出条件2：最大回撤比例
    context.holding_max_price = {}                        # 开仓后的最高价
    # 定时任务
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:20:00')
    

def algo(context):
    # 上一个交易日
    date = context.now.strftime('%Y-%m-%d')
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=context.trade_periods)
    last_date = date_list[-1]
    # 周一
    if context.now.weekday()<=datetime.strptime(last_date, '%Y-%m-%d').weekday():
        # 获取A股代码（剔除停牌股、ST股、次新股（365天））
        all_stock,all_stock_str = get_normal_stocks(context, context.now, new_days=30)
        
        # 2、获取历史数据
        start_date = date_list[0]
        all_data = history_new(security=all_stock,frequency='1d',start_time=start_date,end_time=last_date,fields='symbol,eob,close,high,low,amount,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=False, benchmark='SHSE.000300')
        all_data['mean_price'] = all_data['amount']/all_data['volume']
        price_type = 'mean_price'                          # 价格类型，可以选择常用的high/low等去分析形态的ABC点，U形形态，此处采用分时均价
        context.b_price = {}                              # 记录满足杯柄形态的股票以及买点价格(B点价格)
        for code in all_stock:
            code_price = all_data[all_data['symbol']==code]
            n = len(code_price)
            code_price.index = range(n) 
            if n<=6:continue
            
            # 寻找B点
            b = 0   
            for i in range(2,n-1):
                # B点>=最近一个点，且B点前一个点<=B点
                if code_price[price_type].iloc[-i]>code_price[price_type].iloc[-1] and code_price[price_type].iloc[-i-1]<=code_price[price_type].iloc[-i]:
                    b = i
                    b_value = code_price[price_type].iloc[-b]
                    break
            
            # 当存在B点时，进一步寻找A点
            a = b
            if b!=0 and b+context.span<n:
                for i in range(b+context.span,n-1):
                    # A点>=B点，且A点均>=前后两个点
                    if code_price[price_type].iloc[-i]>code_price[price_type].iloc[-b] and code_price[price_type].iloc[-i-1]<=code_price[price_type].iloc[-i] and code_price[price_type].iloc[-i+1]<=code_price[price_type].iloc[-i]:
                        a = i
                        a_value = code_price[price_type].iloc[-a]
                        # 找出A点和B点之间的最低点
                        min_value_index = code_price.iloc[-a:-b].index[np.argmin(code_price[price_type].iloc[-a:-b])]
                        min_value = code_price[price_type].iloc[min_value_index]
                        min_point = n-min_value_index

                        # A点到杯底之间所有收盘价低于A；杯底到B点之间所有收盘价低于B
                        if max(code_price['close'].iloc[-a+1:-min_point+1])>a_value or max(code_price['close'].iloc[-min_point:-b])>b_value:
                            continue

                        # A点到杯底之间回调处于[min_Retracement,max_Retracement]之间
                        retracement = min_value/a_value-1
                        if retracement>context.min_Retracement or retracement<context.max_Retracement:
                            continue
                        # 杯底到B点之间上涨处于[min_tracement,max_tracement]之间
                        tracement = b_value/min_value-1
                        if tracement<context.min_tracement or tracement>context.max_tracement:
                            continue

                        # A点前的最小涨幅至少在 min_rise_rate
                        min_value_before_A = min(code_price[price_type].iloc[:-a])
                        rice_rate = a_value/min_value_before_A-1
                        if rice_rate<context.min_rise_rate:
                            continue
                        
                        # 杯身呈U型，采用https://www.joinquant.com/view/community/detail/b07a7acf68fe7c3b504358b04b02d19b的形态符合抛物线形态
                        mpe=Lagrange_residual(a,min_point,b,code_price[price_type])
                        if mpe>context.max_mpe:
                            continue

                        #如果找到A点，说明这个股票在上一个阶段存在杯型图
                        else:
                            context.b_price[code] = b_value
                            print(code,code_price['eob'].iloc[-a],code_price['eob'].iloc[-b])
                            break
        
        # 持仓股
        Account_positions = context.account().positions()
        holding_stocks = [posi['symbol'] for posi in Account_positions]

        # 订阅数据
        target_stocks = list(context.b_price.keys())
        print('{}: 目标股票池：{}'.format(context.now,target_stocks))
        subs_stocks = list(set(target_stocks)|set(holding_stocks))
        subscribe(symbols=subs_stocks, frequency='300s', count=1, unsubscribe_previous=True)
    

def on_bar(context,bars):
    bar = bars[0]
    symbol = bar['symbol']
    close_price = bar['close']
    # # 所有持仓
    Account_positions = context.account().positions()
    # 指定持仓
    Account_position = list(filter(lambda x:x['symbol']==symbol,Account_positions))

    # 有持仓，监控卖点
    if Account_position:
        # 更新持股的最高价
        context.holding_max_price[symbol] = max(close_price,context.holding_max_price[symbol])
        # 有可用持仓
        available_now = Account_position[0]['available']-Account_position[0]['volume_today'] if context.mode==MODE_BACKTEST else Account_position[0]['available_now']
        if available_now>0:
            # 卖点1：25%止盈
            holding_raturn = close_price/Account_position[0]['vwap_open']-1
            if holding_raturn>context.stop_profit:
                new_trade(context,symbol,available_now,close_price,OrderSide_Sell,PositionEffect_Close)
                print('{}:{}达到卖出条件1'.format(context.now,symbol))
                return

            # 卖点2：回撤8%卖出
            if close_price<context.holding_max_price[symbol]*(1-context.max_drawdown):
                #卖出平仓一手
                new_trade(context,symbol,available_now,close_price,OrderSide_Sell,PositionEffect_Close)
                print('{}:{}达到卖出条件2'.format(context.now,symbol))

    # 没有持仓，监控买点
    elif symbol in context.b_price.keys():
        if close_price>context.b_price[symbol]:
            context.b_price.pop(symbol)
            if len(Account_positions)<context.max_holding:
                Account_cash = context.account().cash
                available_amount = min(Account_cash['nav']*0.98/context.max_holding,Account_cash['available'])   
                volume = cal_stock_buy_volume(context,symbol,available_amount,close_price)
                new_trade(context,symbol,volume,close_price,OrderSide_Buy,PositionEffect_Open)
                context.holding_max_price[symbol] = close_price
                print('{}:{}突破买入'.format(context.now,symbol))

            
def cal_stock_buy_volume(context,code,available_amount,price):
    """计算股票下单数量,回测时加上手续费和滑点"""       
    if code.startswith('SHSE.68'):
        trade_volume = int(np.floor(available_amount/price))
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)
    else:
        trade_volume = int(np.round(available_amount/price/100)*100)
    return trade_volume


def new_trade(context,symbol,volume,price,side,effect):
    # 回测时用当前价格限价交易，仿真时市价交易
    if context.mode==MODE_BACKTEST:
        order_volume(symbol=symbol, volume=volume, side=side, order_type=OrderType_Limit, position_effect=effect, price=price)
    else:
        order_volume(symbol=symbol, volume=volume, side=side, order_type=OrderType_Market, position_effect=effect, price=price)


def Lagrange_residual(a,min_point,b,highprice):
    """
    拉格朗日插值并返回平均百分比误差
    """
    mpe = 0
    for i in range(b,a+1):
        L0 = (i-min_point)*(i-b)/((a-min_point)*(a-b))
        L1 = (i-a)*(i-b)/((min_point-a)*(min_point-b))
        L2 = (i-min_point)*(i-a)/((b-min_point)*(b-a))
        interpolation = L0*highprice.iloc[-a]+L1*highprice.iloc[-min_point]+L2*highprice.iloc[-b]
        mpe += (highprice.iloc[i]-interpolation)/highprice.iloc[i]
    return mpe/(a-b+1)


def get_normal_stocks(context, date, new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):
    """
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））
    :param date：目标日期
    :param new_days:新股上市天数，默认为365天
    :param skip_suspended:是否剔除停牌股，默认为True
    :param skip_st:是否剔除ST股，默认为True
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效
    """
    date = pd.Timestamp(date).replace(tzinfo=None)
    # A股，剔除停牌和ST股票
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)
    if len(stocks_info)>0:
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))
        # 剔除次新股和退市股
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]
        all_stocks = list(stocks_info['symbol'])
        # 剔除开盘涨停股
        if skip_limit and context.mode==MODE_BACKTEST:
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)
            stocks_info = stocks_info.merge(low_price,on=['symbol'])
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()
    else:
        all_stocks = []
    all_stocks_str = ','.join(all_stocks)
    if return_info:
        return all_stocks,all_stocks_str,stocks_info
    else:
        return all_stocks,all_stocks_str


def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):
    """
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame
    """
    Data = pd.DataFrame()
    if frequency=='1d':
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))
    elif frequency=='tick':
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    # 计算合理间隔
    if isinstance(security,str):
        security = security.split(',')
    space = 30000//len(security)
    # 获取数据
    if len(trading_date)<=space:
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        for n in range(int(np.ceil(len(trading_date)/space))):
            start = n*space
            end = start+space
            if end>=len(trading_date):
                if frequency=='1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                elif frequency=='tick':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            else:
                if frequency=='1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            if len(data)==33000:
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')
            Data = pd.concat([Data,data])
    if df and len(Data)>0:
        if frequency=='tick': 
            Data.sort_values(['symbol','created_at'],inplace=True)
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)
        else:
            Data.sort_values(['symbol','eob'],inplace=True)
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)
        if type:
            if len(Data)>0:
                if frequency=='tick':
                    Data = Data.set_index(['created_at','symbol'])
                else:
                    Data = Data.set_index(['eob','symbol'])
                Data = Data.unstack()
                Data.columns = Data.columns.droplevel(level=0)
    return Data


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    # 交易方向
    if effect == 1:
        if side == 1:
            side_effect = '开多仓'
        else:
            side_effect = '开空仓'
    else:
        if side == 1:
            side_effect = '平空仓'
        else:
            side_effect = '平多仓'
    # 委托类型
    order_type_word = '限价' if order_type==1 else '市价'
    if status == 3:# 全成
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))
    elif status == 8:# 拒单
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))
       
       
def on_backtest_finished(context, indicator):
    print('*'*50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
        strategy_id策略ID, 由系统生成
        filename文件名, 请与本文件名保持一致
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID, 可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
        '''
    run(strategy_id='791355a0-9ff3-11ef-ac58-f46b8c5ab2ce',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        backtest_start_time='2024-11-18 08:00:00',
        backtest_end_time='2024-11-18 16:00:00',
        backtest_adjust=ADJUST_NONE,
        backtest_initial_cash=100000,
        backtest_commission_ratio=0.0007,
        backtest_slippage_ratio=0.00123,
        backtest_match_mode=1)

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
杯柄形态策略  
1.B点均>=前后两个点；  
2.A点>=B点，且A点均>=前后两个点;  
3.A点到杯底之间所有收盘价低于A；杯底到B点之间所有收盘价低于B  
4.A点到杯底之间回调处于[-10%,-35%]之间,杯底到B点之间上涨处于[10%,35%]之间  
5.A点前的最小涨幅至少在 30%6.杯身呈U型,采用插值法进行抛物线拟合，筛选满足误差比例的形态；  
7.量比>=**;  
8.突破B时买入；  
9.25%止盈，8%最大回撤止盈止损。  
  
其他：5分钟频率，最大持有10只股票  
"""  
### 策略中必须有init方法  
def init(context):  
    context.trade_periods = 126          # 杯柄的最大周期长度，默认日频对应是6个月，即126个交易日；其他频率需要修规对应周期长度  
    context.span = 3                     # A点和B点的最小跨度  
    context.min_Retracement = -0.10      # A点到最低点的最小回调幅度  
    context.max_Retracement = -0.35      # A点到最低点的最大回调幅度  
    context.min_tracement = 0.10         # 最低点到B点的最小回调幅度  
    context.max_tracement = 0.35         # 最低点到B点的最大回调幅度  
    context.min_rise_rate = 0.30         # A点前的最小涨幅  
    context.max_mpe = 0.01                               # U型底的插值最大误差比例  
    context.max_holding = 10                              # 最大持股数量  
    context.stop_profit = 0.25                            # 卖出条件1：止盈比例  
    context.max_drawdown = 0.08                           # 卖出条件2：最大回撤比例  
    context.holding_max_price = {}                        # 开仓后的最高价  
    # 定时任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:20:00')  
      
  
def algo(context):  
    # 上一个交易日  
    date = context.now.strftime('%Y-%m-%d')  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=context.trade_periods)  
    last_date = date_list[-1]  
    # 周一  
    if context.now.weekday()<=datetime.strptime(last_date, '%Y-%m-%d').weekday():  
        # 获取A股代码（剔除停牌股、ST股、次新股（365天））  
        all_stock,all_stock_str = get_normal_stocks(context, context.now, new_days=30)  
          
        # 2、获取历史数据  
        start_date = date_list[0]  
        all_data = history_new(security=all_stock,frequency='1d',start_time=start_date,end_time=last_date,fields='symbol,eob,close,high,low,amount,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=False, benchmark='SHSE.000300')  
        all_data['mean_price'] = all_data['amount']/all_data['volume']  
        price_type = 'mean_price'                          # 价格类型，可以选择常用的high/low等去分析形态的ABC点，U形形态，此处采用分时均价  
        context.b_price = {}                              # 记录满足杯柄形态的股票以及买点价格(B点价格)  
        for code in all_stock:  
            code_price = all_data[all_data['symbol']==code]  
            n = len(code_price)  
            code_price.index = range(n)   
            if n<=6:continue  
            # 寻找B点  
            b = 0     
for i in range(2,n-1):  
                # B点>=最近一个点，且B点前一个点<=B点  
                if code_price[price_type].iloc[-i]>code_price[price_type].iloc[-1] and code_price[price_type].iloc[-i-1]<=code_price[price_type].iloc[-i]:  
                    b = i  
                    b_value = code_price[price_type].iloc[-b]  
                    break  
            # 当存在B点时，进一步寻找A点  
            a = b  
            if b!=0 and b+context.span<n:  
                for i in range(b+context.span,n-1):  
                    # A点>=B点，且A点均>=前后两个点  
                    if code_price[price_type].iloc[-i]>code_price[price_type].iloc[-b] and code_price[price_type].iloc[-i-1]<=code_price[price_type].iloc[-i] and code_price[price_type].iloc[-i+1]<=code_price[price_type].iloc[-i]:  
                        a = i  
                        a_value = code_price[price_type].iloc[-a]  
                        # 找出A点和B点之间的最低点  
                        min_value_index = code_price.iloc[-a:-b].index[np.argmin(code_price[price_type].iloc[-a:-b])]  
                        min_value = code_price[price_type].iloc[min_value_index]  
                        min_point = n-min_value_index  
  
                        # A点到杯底之间所有收盘价低于A；杯底到B点之间所有收盘价低于B  
                        if max(code_price['close'].iloc[-a+1:-min_point+1])>a_value or max(code_price['close'].iloc[-min_point:-b])>b_value:  
                            continue  
  
                        # A点到杯底之间回调处于[min_Retracement,max_Retracement]之间  
                        retracement = min_value/a_value-1  
                        if retracement>context.min_Retracement or retracement<context.max_Retracement:  
                            continue  
                        # 杯底到B点之间上涨处于[min_tracement,max_tracement]之间  
                        tracement = b_value/min_value-1  
                        if tracement<context.min_tracement or tracement>context.max_tracement:  
                            continue  
  
                        # A点前的最小涨幅至少在 min_rise_rate                        min_value_before_A = min(code_price[price_type].iloc[:-a])  
                        rice_rate = a_value/min_value_before_A-1  
                        if rice_rate<context.min_rise_rate:  
                            continue  
                        # 杯身呈U型，采用https://www.joinquant.com/view/community/detail/b07a7acf68fe7c3b504358b04b02d19b的形态符合抛物线形态  
                        mpe=Lagrange_residual(a,min_point,b,code_price[price_type])  
                        if mpe>context.max_mpe:  
                            continue  
  
                        #如果找到A点，说明这个股票在上一个阶段存在杯型图  
                        else:  
                            context.b_price[code] = b_value  
                            # print(code,code_price['eob'].iloc[-a],code_price['eob'].iloc[-b])  
                            break  
        # 持仓股  
        Account_positions = context.account().positions()  
        holding_stocks = [posi['symbol'] for posi in Account_positions]  
  
        # 订阅数据  
        target_stocks = list(context.b_price.keys())  
        print('{}: 目标股票池：{}'.format(context.now,target_stocks))  
        subs_stocks = list(set(target_stocks)|set(holding_stocks))  
        context.frequency = '300s'  
        context.count = 48*6  
        subscribe(symbols=subs_stocks, frequency=context.frequency, count=48*6, fields='symbol,volume,eob', unsubscribe_previous=True)  
      
  
def on_bar(context,bars):  
  
    bar = bars[0]  
    symbol = bar['symbol']  
    close_price = bar['close']  
    # # 所有持仓  
    Account_positions = context.account().positions()  
    # 指定持仓  
    Account_position = list(filter(lambda x:x['symbol']==symbol,Account_positions))  
  
    # 有持仓，监控卖点  
    if Account_position:  
        # 更新持股的最高价  
        context.holding_max_price[symbol] = max(close_price,context.holding_max_price[symbol])  
        # 有可用持仓  
        available_now = Account_position[0]['available']-Account_position[0]['volume_today'] if context.mode==MODE_BACKTEST else Account_position[0]['available_now']  
        if available_now>0:  
            # 卖点1：25%止盈  
            holding_raturn = close_price/Account_position[0]['vwap_open']-1  
            if holding_raturn>context.stop_profit:  
                new_trade(context,symbol,available_now,close_price,OrderSide_Sell,PositionEffect_Close)  
                print('{}:{}达到卖出条件1'.format(context.now,symbol))  
                return  
  
            # 卖点2：回撤8%卖出  
            if close_price<context.holding_max_price[symbol]*(1-context.max_drawdown):  
                #卖出平仓一手  
                new_trade(context,symbol,available_now,close_price,OrderSide_Sell,PositionEffect_Close)  
                print('{}:{}达到卖出条件2'.format(context.now,symbol))  
  
    # 没有持仓，监控买点  
    elif symbol in context.b_price.keys():  
        if len(Account_positions)<context.max_holding:  
            if close_price>context.b_price[symbol]:  
                # 放量  
                data = context.data(symbol=symbol, frequency=context.frequency, count=context.count)  
                data['time'] = data['eob'].apply(lambda x: x.time())  
                data['date'] = data['eob'].apply(lambda x: x.date())  
                data_new = data[data['time']<=bar['eob'].time()]  
                data_new = data_new.groupby('date')['volume'].sum()  
                lb = data_new.iloc[-1]/np.mean(data_new.iloc[:5])  
                if lb>1.2:  
                    context.b_price.pop(symbol)  
                    Account_cash = context.account().cash  
                    available_amount = min(Account_cash['nav']*0.98/context.max_holding,Account_cash['available'])     
                    volume = cal_stock_buy_volume(context,symbol,available_amount,close_price)  
                    new_trade(context,symbol,volume,close_price,OrderSide_Buy,PositionEffect_Open)  
                    context.holding_max_price[symbol] = close_price  
                    print('{}:{}突破买入'.format(context.now,symbol))  
  
              
def cal_stock_buy_volume(context,code,available_amount,price):  
    """计算股票下单数量,回测时加上手续费和滑点"""         
if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def new_trade(context,symbol,volume,price,side,effect):  
    # 回测时用当前价格限价交易，仿真时市价交易  
    if context.mode==MODE_BACKTEST:  
        order_volume(symbol=symbol, volume=volume, side=side, order_type=OrderType_Limit, position_effect=effect, price=price)  
    else:  
        order_volume(symbol=symbol, volume=volume, side=side, order_type=OrderType_Market, position_effect=effect, price=price)  
  
  
def Lagrange_residual(a,min_point,b,highprice):  
    """  
    拉格朗日插值并返回平均百分比误差  
    """    mpe = 0  
    for i in range(b,a+1):  
        L0 = (i-min_point)*(i-b)/((a-min_point)*(a-b))  
        L1 = (i-a)*(i-b)/((min_point-a)*(min_point-b))  
        L2 = (i-min_point)*(i-a)/((b-min_point)*(b-a))  
        interpolation = L0*highprice.iloc[-a]+L1*highprice.iloc[-min_point]+L2*highprice.iloc[-b]  
        mpe += (highprice.iloc[i]-interpolation)/highprice.iloc[i]  
    return mpe/(a-b+1)  
  
  
def get_normal_stocks(context, date, new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:# 全成  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:# 拒单  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
         
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='6ad24d76-a57b-11ef-87f1-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-11-10 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)


## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
杯柄形态策略  
1.B点均>=前后两个点；  
2.A点>=B点，且A点均>=前后两个点;  
3.A点到杯底之间所有收盘价低于A；杯底到B点之间所有收盘价低于B  
4.A点到杯底之间回调处于[-10%,-35%]之间,杯底到B点之间上涨处于[10%,35%]之间  
5.A点前的最小涨幅至少在 30%6.杯身呈U型,采用插值法进行抛物线拟合，筛选满足误差比例的形态；  
7.突破A时买入；  
8.25%止盈，8%最大回撤止盈止损。  
  
其他：5分钟频率，最大持有10只股票  
"""  
### 策略中必须有init方法  
def init(context):  
    # context.trade_fre = 'D'            # 交易频率，日频：D,周线是'W'，月线是'M'  
    context.trade_periods = 126          # 杯柄的最大周期长度，默认日频对应是6个月，即126个交易日；其他频率需要修规对应周期长度  
    context.span = 3                     # A点和B点的最小跨度  
    context.min_Retracement = -0.10      # A点到最低点的最小回调幅度  
    context.max_Retracement = -0.35      # A点到最低点的最大回调幅度  
    context.min_tracement = 0.10         # 最低点到B点的最小回调幅度  
    context.max_tracement = 0.35         # 最低点到B点的最大回调幅度  
    context.min_rise_rate = 0.30         # A点前的最小涨幅  
    context.max_mpe = 0.01                               # U型底的插值最大误差比例  
    context.max_holding = 10                              # 最大持股数量  
    context.stop_profit = 0.25                            # 卖出条件1：止盈比例  
    context.max_drawdown = 0.08                           # 卖出条件2：最大回撤比例  
    context.holding_max_price = {}                        # 开仓后的最高价  
    # 定时任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:20:00')  
      
  
def algo(context):  
    # 上一个交易日  
    date = context.now.strftime('%Y-%m-%d')  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=context.trade_periods)  
    last_date = date_list[-1]  
    # 周一  
    if context.now.weekday()<=datetime.strptime(last_date, '%Y-%m-%d').weekday():  
        # 获取A股代码（剔除停牌股、ST股、次新股（365天））  
        all_stock,all_stock_str = get_normal_stocks(context, context.now, new_days=30)  
          
        # 2、获取历史数据  
        start_date = date_list[0]  
        all_data = history_new(security=all_stock,frequency='1d',start_time=start_date,end_time=last_date,fields='symbol,eob,close,high,low,amount,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=False, benchmark='SHSE.000300')  
        all_data['mean_price'] = all_data['amount']/all_data['volume']  
        price_type = 'mean_price'                          # 价格类型，可以选择常用的high/low等去分析形态的ABC点，U形形态，此处采用分时均价  
        context.b_price = {}                              # 记录满足杯柄形态的股票以及买点价格(B点价格)  
        for code in all_stock:  
            code_price = all_data[all_data['symbol']==code]  
            n = len(code_price)  
            code_price.index = range(n)   
            if n<=6:continue  
            # 寻找B点  
            b = 0     
for i in range(2,n-1):  
                # B点>=最近一个点，且B点前一个点<=B点  
                if code_price[price_type].iloc[-i]>code_price[price_type].iloc[-1] and code_price[price_type].iloc[-i-1]<=code_price[price_type].iloc[-i]:  
                    b = i  
                    b_value = code_price[price_type].iloc[-b]  
                    break  
            # 当存在B点时，进一步寻找A点  
            a = b  
            if b!=0 and b+context.span<n:  
                for i in range(b+context.span,n-1):  
                    # A点>=B点，且A点均>=前后两个点  
                    if code_price[price_type].iloc[-i]>code_price[price_type].iloc[-b] and code_price[price_type].iloc[-i-1]<=code_price[price_type].iloc[-i] and code_price[price_type].iloc[-i+1]<=code_price[price_type].iloc[-i]:  
                        a = i  
                        a_value = code_price[price_type].iloc[-a]  
                        # 找出A点和B点之间的最低点  
                        min_value_index = code_price.iloc[-a:-b].index[np.argmin(code_price[price_type].iloc[-a:-b])]  
                        min_value = code_price[price_type].iloc[min_value_index]  
                        min_point = n-min_value_index  
  
                        # A点到杯底之间所有收盘价低于A；杯底到B点之间所有收盘价低于B  
                        if max(code_price['close'].iloc[-a+1:-min_point+1])>a_value or max(code_price['close'].iloc[-min_point:-b])>b_value:  
                            continue  
  
                        # A点到杯底之间回调处于[min_Retracement,max_Retracement]之间  
                        retracement = min_value/a_value-1  
                        if retracement>context.min_Retracement or retracement<context.max_Retracement:  
                            continue  
                        # 杯底到B点之间上涨处于[min_tracement,max_tracement]之间  
                        tracement = b_value/min_value-1  
                        if tracement<context.min_tracement or tracement>context.max_tracement:  
                            continue  
  
                        # A点前的最小涨幅至少在 min_rise_rate                        min_value_before_A = min(code_price[price_type].iloc[:-a])  
                        rice_rate = a_value/min_value_before_A-1  
                        if rice_rate<context.min_rise_rate:  
                            continue  
                        # 杯身呈U型，采用https://www.joinquant.com/view/community/detail/b07a7acf68fe7c3b504358b04b02d19b的形态符合抛物线形态  
                        mpe=Lagrange_residual(a,min_point,b,code_price[price_type])  
                        if mpe>context.max_mpe:  
                            continue  
  
                        #如果找到A点，说明这个股票在上一个阶段存在杯型图  
                        else:  
                            context.b_price[code] = a_value  
                            print(code,code_price['eob'].iloc[-a],code_price['eob'].iloc[-b])  
                            break  
        # 持仓股  
        Account_positions = context.account().positions()  
        holding_stocks = [posi['symbol'] for posi in Account_positions]  
  
        # 订阅数据  
        target_stocks = list(context.b_price.keys())  
        print('{}: 目标股票池：{}'.format(context.now,target_stocks))  
        subs_stocks = list(set(target_stocks)|set(holding_stocks))  
        subscribe(symbols=subs_stocks, frequency='300s', count=1, unsubscribe_previous=True)  
      
  
def on_bar(context,bars):  
    bar = bars[0]  
    symbol = bar['symbol']  
    close_price = bar['close']  
    # # 所有持仓  
    Account_positions = context.account().positions()  
    # 指定持仓  
    Account_position = list(filter(lambda x:x['symbol']==symbol,Account_positions))  
  
    # 有持仓，监控卖点  
    if Account_position:  
        # 更新持股的最高价  
        context.holding_max_price[symbol] = max(close_price,context.holding_max_price[symbol])  
        # 有可用持仓  
        available_now = Account_position[0]['available']-Account_position[0]['volume_today'] if context.mode==MODE_BACKTEST else Account_position[0]['available_now']  
        if available_now>0:  
            # 卖点1：25%止盈  
            holding_raturn = close_price/Account_position[0]['vwap_open']-1  
            if holding_raturn>context.stop_profit:  
                new_trade(context,symbol,available_now,close_price,OrderSide_Sell,PositionEffect_Close)  
                print('{}:{}达到卖出条件1'.format(context.now,symbol))  
                return  
  
            # 卖点2：回撤8%卖出  
            if close_price<context.holding_max_price[symbol]*(1-context.max_drawdown):  
                #卖出平仓一手  
                new_trade(context,symbol,available_now,close_price,OrderSide_Sell,PositionEffect_Close)  
                print('{}:{}达到卖出条件2'.format(context.now,symbol))  
  
    # 没有持仓，监控买点  
    elif symbol in context.b_price.keys():  
        if close_price>context.b_price[symbol]:  
            context.b_price.pop(symbol)  
            if len(Account_positions)<context.max_holding:  
                Account_cash = context.account().cash  
                available_amount = min(Account_cash['nav']*0.98/context.max_holding,Account_cash['available'])     
                volume = cal_stock_buy_volume(context,symbol,available_amount,close_price)  
                new_trade(context,symbol,volume,close_price,OrderSide_Buy,PositionEffect_Open)  
                context.holding_max_price[symbol] = close_price  
                print('{}:{}突破买入'.format(context.now,symbol))  
  
              
def cal_stock_buy_volume(context,code,available_amount,price):  
    """计算股票下单数量,回测时加上手续费和滑点"""         
if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def new_trade(context,symbol,volume,price,side,effect):  
    # 回测时用当前价格限价交易，仿真时市价交易  
    if context.mode==MODE_BACKTEST:  
        order_volume(symbol=symbol, volume=volume, side=side, order_type=OrderType_Limit, position_effect=effect, price=price)  
    else:  
        order_volume(symbol=symbol, volume=volume, side=side, order_type=OrderType_Market, position_effect=effect, price=price)  
  
  
def Lagrange_residual(a,min_point,b,highprice):  
    """  
    拉格朗日插值并返回平均百分比误差  
    """    mpe = 0  
    for i in range(b,a+1):  
        L0 = (i-min_point)*(i-b)/((a-min_point)*(a-b))  
        L1 = (i-a)*(i-b)/((min_point-a)*(min_point-b))  
        L2 = (i-min_point)*(i-a)/((b-min_point)*(b-a))  
        interpolation = L0*highprice.iloc[-a]+L1*highprice.iloc[-min_point]+L2*highprice.iloc[-b]  
        mpe += (highprice.iloc[i]-interpolation)/highprice.iloc[i]  
    return mpe/(a-b+1)  
  
  
def get_normal_stocks(context, date, new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:# 全成  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:# 拒单  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
         
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='791355a0-9ff3-11ef-ac58-f46b8c5ab2ce',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-11-10 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)


# 单因子分析
from __future__ import print_function, absolute_import, unicode_literals

from gm.api import *

import pytz

import datetime

import alphalens

import numpy as np

import pandas as pd

import statsmodels.api as sm

import statsmodels.tsa.stattools as sttools

from statsmodels.tsa.stattools import adfuller

  

import warnings

warnings.filterwarnings("ignore")

def get_trading_dates_new(exchange, start_date, end_date):
    """
    获取两个日期间的交易日
    """
    start_date = pd.Timestamp(start_date)
    start_date_str = start_date.strftime('%Y-%m-%d')
    end_date = pd.Timestamp(end_date)
    end_date_str = end_date.strftime('%Y-%m-%d')
    # 判断trading_dates是否存在
    if 'trading_dates' not in globals():
        global trading_dates
        trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)
    # 判断start_date是否存在于trading_dates中
    if start_date_str not in list(trading_dates['date']):
        trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(trading_dates['date'].iloc[-1][:4]))
    # 判断end_date是否存在于trading_dates中
    if end_date_str not in list(trading_dates['date']):
        trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(trading_dates['date'].iloc[0][:4]), end_year=end_date.year)
    # 计算start_date所在得index位置
    start_date_index = trading_dates[trading_dates['date']==start_date_str].index[0]
    # 计算end_date所在得index位置
    end_date_index = trading_dates[trading_dates['date']==end_date_str].index[0]
    # 计算区间得交易日
    date_list = trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()
    date_list = [date for date in date_list if date!='']
    return date_list


def is_Week_or_Month_first_day(date,periods_type):
    """
    判断日期是否为 周/月 的第1个交易日
    :param date：目标日期
    :param periods_type：类型，'week'为周，'month'为月
    """
    date = pd.Timestamp(date)
    # 判断该日期是否为交易日
    is_trading_date = get_trading_dates_new(exchange='SZSE', start_date=date, end_date=date)
    if len(is_trading_date)==0:
        return False   
    
    # 上一个交易日
    last_date = pd.Timestamp(get_previous_n_trading_dates(exchange='SHSE', date=date, n=1)[0])

    status = None
    if periods_type=='week':
        status = date.week!=last_date.week
    elif periods_type=='month':
        status = date.month!=last_date.month
    return status


def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):
    """
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame
    """
    Data = pd.DataFrame()
    if frequency=='1d':
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))
    elif frequency=='tick':
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    # 计算合理间隔
    if isinstance(security,str):
        security = security.split(',')
    space = 30000//len(security)
    # 获取数据
    if len(trading_date)<=space:
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        for n in range(int(np.ceil(len(trading_date)/space))):
            start = n*space
            end = start+space
            if end>=len(trading_date):
                if frequency=='1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                elif frequency=='tick':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            else:
                if frequency=='1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            if len(data)==33000:
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')
            Data = pd.concat([Data,data])
    if df and len(Data)>0:
        if frequency=='tick': 
            Data.sort_values(['symbol','created_at'],inplace=True)
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)
        else:
            Data.sort_values(['symbol','eob'],inplace=True)
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)
        if type:
            if len(Data)>0:
                if frequency=='tick':
                    Data = Data.set_index(['created_at','symbol'])
                else:
                    Data = Data.set_index(['eob','symbol'])
                Data = Data.unstack()
                Data.columns = Data.columns.droplevel(level=0)
    return Data


def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):
    """
    去极值(data以code*date的形式)
    :param data：待处理数据[Series]
    :param scale：标准差倍数，默认为3
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN
    :param inf2nan：True为将inf转化为nan，False不转化
    """
    data = data.astype('float')
    if inf2nan:
        data = data.replace([np.inf, -np.inf], np.nan)
    std_ = data.std()
    mean_ = data.mean()
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])
    if inclusive:
        data = np.minimum(data, np.asarray(uplimit)) 
        data = np.maximum(data, np.asarray(downlimit)) 
    else:
        data = np.minimum(data, np.asarray(nan_df)) 
        data = np.maximum(data, np.asarray(nan_df)) 
    return data


def standardlize(data, inf2nan=True):
    """
    标准化(data以code*date的形式)
    :param data:待处理数据
    :param inf2nan：是否将inf转化为nan
    """
    if inf2nan:
        data = data.replace([np.inf, -np.inf], np.nan)
    return (data - data.mean()) / data.std()


def valid_sample_size(data,min_size_rate=2/3):
    """
    判断有效样本数量是否满足最低限制(data以code*date的形式)
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3
    """
    min_size = int(round(data.shape[1]*min_size_rate))
    nan_data = np.isnan(data).sum(axis=1)
    security = nan_data[nan_data<min_size].index
    data = data.loc[security,:]
    return data


def neutralize(data):
    """
    市值+行业中性化(data以code*date的形式)
    :param data：待处理数据
    :param date：目标日期
    """
    data = data.dropna(how='any')
    security = list(data.index)
    residual_df = pd.DataFrame()
    month_date = []
    for date in data.columns:
        month = date[:7]
        if month not in month_date:
            month_date.append(month)
            # 查询标的市值，默认历史回溯天数count=1
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)
            market_value = market_value.drop(['trade_date'], axis=1)
            market_value['tot_mv'] = np.log(market_value['tot_mv'])
            market_value = market_value.set_index('symbol')
            # 查询标的行业代码，以哑变量格式存储
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列
            symbol_industry = symbol_industry.groupby('symbol').sum()
            # 采用最小二乘法进行多元线性回归，计算残差
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')
        X = sm.add_constant(df_reg.iloc[:, 1:])
        Y = df_reg.iloc[:, 0]
        MLR_model = sm.OLS(Y, X)
        residual = MLR_model.fit().resid
        residual = residual.to_frame(name=date).rename_axis('symbol')
        residual_df = pd.concat([residual_df,residual],axis=1)
        
    return residual_df


def alphalens_analyse(factor,close,periods=(5,10,21),group_num=10):
    """
    基于alphalens的因子分析
    :param factor:因子数据，index为带有时区的日期，columns为股票
    :param close:价格，index为带有时区的日期，columns为股票
    :param periods:调仓周期，5天为1周，10天为两周，21天为一个月
    :param group_num:分组数，默认为10组
    
    注：
    注意下述函数的price变量大小需大于groupby_labels变量的大小，否则输出为空
    导入的因子数据需为单因子，且具有日期、股票两层索引；
    groupby自设置的一个分组，ricequant模板中是以市值作为分组
    by_group的值为True则进行按行业分组
    quantiles因子层的分组，真正意义上的分组，分组数在程序开头由group_num变量设定
    periods 调仓周期，函数默认为（1,5,10），即1、5、10个交易日调一次仓，对应1天、1周、半个月
    filter_zscore 设置收益率异常值的阈值为多少倍标准差(暂时不是太清楚)
    groupby_labels 与by_group的值相对应，为其附上标签
    """
    # 调整数据结构
    series_facs_datas = factor
    # 统一columns的格式
    timezone = pytz.timezone('Asia/Shanghai') # 美国太平洋时区 ('US/Pacific')
    series_facs_datas.index = series_facs_datas.index.map(lambda x:timezone.localize(pd.Timestamp(pd.Timestamp(x).strftime('%Y-%m-%d'))))
    close.index = close.index.map(lambda x:timezone.localize(pd.Timestamp(pd.Timestamp(x).strftime('%Y-%m-%d'))))
    price = close.loc[series_facs_datas.index,series_facs_datas.columns]
    series_facs_datas = series_facs_datas.stack()
    # 基于alphalens处理数据
    facs_data_analysis  = alphalens.utils.get_clean_factor_and_forward_returns(series_facs_datas,price,groupby=None,quantiles=group_num,bins=None,periods=periods,filter_zscore=20,groupby_labels=None)

    # 分析
    alphalens.tears.create_summary_tear_sheet(facs_data_analysis)
    # 收益率分析
    alphalens.tears.create_returns_tear_sheet(facs_data_analysis,by_group=False)
     # 稳定性分析 因子的稳定性决定调仓频率，分别输出各组的换手率及自回归系数图
     alphalens.tears.create_turnover_tear_sheet(facs_data_analysis)
    #IC值不同时间维度的比较分析，时间跨度由by_time控制：M-月，q-季度，2M-2个月
    alphalens.performance.mean_information_coefficient(facs_data_analysis,group_adjust=False,by_group=False,by_time='2M')

    
def stk_get_daily_mktvalue_pt_new(symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):
    """
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数
    """
    if counts!=None:
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]
    date_list = get_trading_dates_new(exchange='SZSE', start_date=start_date, end_date=end_date)
    
    # 循环获取数据
    df_total = pd.DataFrame()
    for date in date_list:
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)
        if df:
            df_total = pd.concat([df_total,df_new])
        else:
            df_total = df_total+df_new
    return df_total


def cal_StyleFactor_Size(security, start_date, end_date):
    """
    计算风格因子 Size
    :param security 待筛选股票池（list)（这里是secucode）
    :param date 目标日期（int）
    return:Size(DataFrame)
    """
    dfdata = stk_get_daily_mktvalue_pt_new(symbols=security, fields='tot_mv', start_date=start_date, end_date=end_date, df=True)    
    dfdata = dfdata.set_index(['trade_date','symbol'])
    dfdata = dfdata.unstack()
    dfdata.columns = dfdata.columns.droplevel(level=0)
    Size = np.log(dfdata)
    return Size.replace([-np.inf,np.inf],np.nan).fillna(0)

def cal_macd(security, start_date, end_date):
    """
    计算MACD
    """
    import talib
    talib.set_compatibility(1)
    # 获取开始日期前120个交易日的日期
    pre_date = get_previous_n_trading_dates(exchange='SHSE', date=start_date, n=120)[0]
    # 获取数据
    close_price = history_new(security=security,frequency='1d',start_time=pre_date,end_time=end_date,fields='eob,symbol,close')
    # 计算 MACD 注：用talib.MACD中的macd = diff-dea，需进一步改写
    macd_df = pd.DataFrame()
    for symbol in close_price.columns:
        diff, dea, macd4 = talib.MACD(close_price[symbol])
        macd = 2*(diff - dea)
        macd_df = pd.concat([macd_df,pd.DataFrame(macd.values,columns=[symbol],index=close_price.index)],axis=1)
    return macd_df.iloc[120:,:]


# 汉斯123法则（股指期货-新版SDK）
**1、****策略说明：**

**原本的策略逻辑如下：**

每日开盘前09:00:00获取沪深300指数期货可交易的期货合约；

假设开盘后time时间的走势形成的高低价作为突破的上轨和下轨，这里time设置为一小时，即10点开始跟踪趋势，高点突破则作为开仓信号。

订阅频率为5分钟

不允许重复下单

开盘前平仓

止盈20止损20

每天早上9点，平掉所有单子。

**分析：**

代码实现过程中存在较多问题，例如存在订阅后当天未交易，第二天会在10:00的行情中触发交易信号。早上9:00平仓命令使用的是order_close_all()，该命令是市价委托，导致都是以日频收盘价平仓的，与开盘价有较大差距。另外平仓条件是采用的固定点位，过于刻板，且程序写法生硬。同时频繁订阅和取消订阅，对内存的开销较大，应减少订阅和取消订阅操作。再者策略交易对象有很多都是虚拟合约（合约指数和连续合约），实盘中没有对应合约，需要进行调整。

**优化：**

l  突破做多，前一价格<=上轨，当前价格>上轨时做多；

突破做空，前一价格>=上轨，当前价格<上轨时做空。

l  9:00时，以开盘价限价平仓。

l  平仓条件，ATR固定止盈止损和ATR 移动止盈止损两种方式。

l  上下轨计算方式采用高低点+N倍波动；

l  主做当月、下月、下季和隔季合约。

**ATR****固定止盈止损**

每日开盘前09:00:00获取股指期货的当月、下月、下季和隔季合约；以开盘后一段时间内的高低点+N倍波动作为上下轨，向上突破时做多，向下突破时做空，ATR固定止盈止损，次日开盘时清仓。

**ATR****移动止盈止损**

每日开盘前09:00:00获取股指期货的当月、下月、下季和隔季合约；以开盘后一段时间内的高低点+N倍波动作为上下轨，向上突破时做多，向下突破时做空，ATR移动止盈止损，次日开盘时清仓。


## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import string  
from gm.enum import OrderSide_Buy, OrderSide_Sell, OrderType_Limit, OrderType_Market, PositionEffect_Close  
import pandas as pd  
from pandas.core.frame import DataFrame  
  
'''  
日内策略  
每日开盘前09:00:00获取股指期货的当月、下月、下季和隔季个月；  
假设开盘后time时间的走势形成的高低价作为突破的上轨和下轨，这里time设置为一小时，即10点开始跟踪趋势，高点突破则作为开仓信号。  
订阅频率为5分钟  
不允许重复下单  
开盘前平仓  
止盈20止损20  
每天早上9点，平掉所有单子。  
  
'''  
  
### 策略中必须有init方法  
def init(context):  
    context.symbol = 'CFFEX.IF'                     # 设置标的  
    context.trade_num = 1                           # 交易数量  
    context.stopProfitPrice = 5                     # 设置止盈点数  
    context.stopLossPrice = -5                      # 设置止损点数  
  
    #突破开盘区间的高低阈值，定义为0.6%  
    context.hold=0.01  
    #记录标的的高低价情况  
    context.high=pd.DataFrame()  
    context.low=pd.DataFrame()  
    #定义上轨  
    context.uphold=pd.DataFrame()  
    #定义下轨  
    context.downhold=pd.DataFrame()  
  
    # 数据一次性获取  
    context.symbol = [context.symbol+code for code in ['00','01','02','03']]  
    if context.mode==MODE_BACKTEST:  
        context.contract_dict = {}  
        for symbol in context.symbol:  
            contract_list = fut_get_continuous_contracts(csymbol=symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
            if len(contract_list)>0:  
                context.contract_dict[symbol] = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
  
    #每天的09:00:00执行策略algo_1  开盘前获取沪深300指数期货交易标的信息  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:00:00')  
    #每天的10:00:00执行策略algo_2  开始执行策略逻辑，开盘后N根k线的高低点突破，作为开仓信号  
    schedule(schedule_func=algo_2, date_rule='1d', time_rule='10:00:00')  
  
  
def algo_1(context):  
    context.date = context.now.strftime('%Y-%m-%d')  
    print('------',context.now,'平当前所有可平持仓')  
    positions = context.account().positions()  
    for posi in positions:  
        symbol = posi['symbol']  
        side = posi['side']  
        available = posi['volume']  
        trade_side = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
        price_new = history(symbol=symbol, frequency='1d', start_time=context.date,  end_time=context.date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
        order_volume(symbol=symbol, side=trade_side, volume=available, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
  
    context.contracts = []  
    if context.mode==MODE_BACKTEST:  
        for symbol in context.symbol:  
            if context.date in context.contract_dict[symbol].keys():  
                context.contracts.append(context.contract_dict[symbol][context.date])  
            else:  
                context.contracts.append(fut_get_continuous_contracts(csymbol=symbol, start_date=context.date, end_date=context.date)[0]['symbol'])  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.date, end_date=context.date)[0]['symbol']  
  
  
def algo_2(context):  
    #获取开盘后一个小时内的5分钟bar数据  
    history_data = history(symbol=context.contracts, frequency='300s', start_time=context.date+' 09:00:00',  end_time=context.date+' 10:00:00', fields='symbol,open, close, low, high, eob', adjust=ADJUST_PREV, df= True)  
    #计算最高价的最大值  
    context.high = history_data.groupby('symbol')['high'].max()  
    #计算最低价的最低值  
    context.low = history_data.groupby('symbol')['low'].min()  
    #定义上轨  
    context.uphold = context.high*(1+context.hold)   
    #定义下轨  
    context.downhold = context.low*(1-context.hold)   
    #计算好上下轨之后订阅五分钟数据，在订阅里跟踪趋势  
    subscribe(symbols=context.contracts, frequency='300s', count=5, unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    bar = bars[0]  
    symbol = bar['symbol']  
    close_price = bar['close']  
    # 查询持仓  
    long_position = context.account().position(symbol=symbol,side = PositionSide_Long)  
    short_position = context.account().position(symbol=symbol,side = PositionSide_Short)  
    #如果没有持仓且价格突破上轨，则开多  
    if not long_position:  
        if bar['close']>context.uphold[symbol]:  
            print('{}:价格突破上轨,买入开仓: {}'.format(context.now,symbol))  
            #买入开仓一手  
            order_volume(symbol=symbol, volume=context.trade_num, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=close_price)  
    else:  
        #设置止损条件  
        if bar['close']<(context.low[symbol]+context.stopLossPrice):  
            #卖出平仓一手  
            order_volume(symbol=symbol, volume=context.trade_num, side=OrderSide_Sell, order_type=OrderType_Limit, position_effect=PositionEffect_Close, price=close_price)  
            print('{}:达到止损条件 ，对多头卖出平仓: {}'.format(context.now,symbol))  
        #设置止盈条件  
        if bar['close']>(context.high[symbol]+context.stopProfitPrice):  
            #卖出平仓一手  
            order_volume(symbol=symbol, volume=context.trade_num, side=OrderSide_Sell, order_type=OrderType_Limit, position_effect=PositionEffect_Close, price=close_price)  
            print('{}:达到止盈条件 ，对多头卖出平仓: {}'.format(context.now,symbol))  
  
    #如果没有持仓且价格突破下轨，则开空  
    if not short_position:  
        if bar['close']<context.downhold[symbol]:  
            print('{}:价格突破下轨,卖出开仓: {}'.format(context.now,symbol))  
            #卖出开仓一手  
            order_volume(symbol=symbol, volume=context.trade_num, side=OrderSide_Sell, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=close_price)  
    else:  
        #设置止盈条件  
        if bar['close']<(context.low[symbol]-context.stopProfitPrice):  
            #买入平仓一手  
            order_volume(symbol=symbol, volume=context.trade_num, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Close, price=close_price)  
            print('{}:达到止盈条件 ，对空头买入平仓: {}'.format(context.now,symbol))  
        #设置止损条件  
        if bar['close']>(context.high[symbol]-context.stopLossPrice):  
            #买入平仓一手  
            order_volume(symbol=symbol, volume=context.trade_num, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Close, price=close_price)  
            print('{}:达到止损条件 ，对空头买入平仓: {}'.format(context.now,symbol))  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
         
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
      
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='dabab1b5-9a53-11ef-a912-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2022-01-01 08:00:00',  
        backtest_end_time='2024-10-30 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=2000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 股指期货
**策略说明：**

2019年的海通证券《短周期交易策略研究之二，基于日内收益分布特征的股指期货交易策略》
## 复合策略
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import pandas as pd  
import datetime  
  
'''  
本策略采用定时任务结构（每天10：00：00、14:59:00定时执行策略algo_sell、algo_buy），建立隔夜收益增强模型  
当收盘前14:45-14:59区间基差下降时，或者收盘前半小时委买总量大于委卖总量时，或者当收盘价（14:59的实时价格）低于结算价（14:00-14:59的成交量加权价格）时做多，持有至次日上午10点平仓;  
当收盘前14:45-14:59区间基差上升时，或者收盘前半小时委买总量小于委卖总量时，或者当收盘价（14:59的实时价格）高于结算价（14:00-14:59的成交量加权价格）时做空，持有至次日上午10点平仓。  
'''  
  
def init(context):   
    # 设置交易品种  
    context.main_contract = 'CFFEX.IC00'  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_list}  
    # 如果是交易时间段，等到开盘时间确保进入algo()  
    schedule(schedule_func=algo_sell, date_rule='1d', time_rule='10:00:00')  
    schedule(schedule_func=algo_buy, date_rule='1d', time_rule='14:59:00')   
  
  
def algo_sell(context):  
    # 查询持仓  
    pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
    pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
    # 若持有多仓  
    if pos_long:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_1 = history_n(symbol=pos_long['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为卖出平仓价下单平多仓  
        sell_price_long = quotes_1[0]['bid_p']  
        order_target_percent(symbol=pos_long['symbol'], percent=0, position_side=PositionSide_Long,  
                             order_type=OrderType_Limit, price=sell_price_long)  
        print('以买一价为卖出平仓价平多仓', context.main_contract)  
  
    # 若持有空仓  
    if pos_short:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_2 = history_n(symbol=pos_short['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为买入平仓价下单平空仓  
        sell_price_short = quotes_2[0]['bid_p']  
        order_target_percent(symbol=pos_short['symbol'], percent=0, position_side=PositionSide_Short,  
                             order_type=OrderType_Limit, price=sell_price_short)  
        print('以买一价为买入平仓价平空仓', context.main_contract)  
  
  
def algo_buy(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约      
date = now_str  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list:  
        context.main_contract = context.main_contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取当月交易标的的基本交割日期  
    symbol_info = get_symbols(sec_type1=1040, symbols=context.main_contract, trade_date=now_str, df=False)  
    delisted_date = symbol_info[0]['delisted_date']  
      
    # 若当日不是交易标的的交割日期则进行判断买卖，否则不进行买卖  
    if delisted_date.strftime('%Y-%m-%d') != context.now.strftime('%Y-%m-%d'):  
        # 每天14：59：00 定时获取 tick 数据，实时模式可以用current函数获取  
        # 获取当月合约的价格  
        print(context.now,context.main_contract)  
        price_main = history_n(symbol=context.main_contract, frequency='tick', end_time=context.now,  
                            count=1, fields='price, quotes', adjust=ADJUST_PREV,  
                            adjust_end_time=context.now, df=True)  
  
        # 买卖委托不均衡：获取前30分钟的买一委托总量和卖一委托总量  
        quotes_3 = history(symbol=context.main_contract, frequency='tick', fields='quotes',  
                        start_time=(context.now-datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        # 买一委托总量  
        total_bid_v = quotes_3.quotes.apply(lambda x: x[0]['bid_v']).sum()  
        # 卖一委托总量  
        total_ask_v = quotes_3.quotes.apply(lambda x: x[0]['ask_v']).sum()  
  
        # 基差  
        underlying_symbol = symbol_info[0]['underlying_symbol']  
        price_data = history(symbol=[context.main_contract,underlying_symbol], frequency='60s', fields='symbol,eob,close',  
                        start_time=(context.now-datetime.timedelta(minutes=14)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        underlying_price = price_data[price_data['symbol']==underlying_symbol].set_index('eob')  
        main_price = price_data[price_data['symbol']==context.main_contract].set_index('eob')  
        jc = (underlying_price['close']-main_price['close']).dropna()  
        jc_diff = jc.iloc[-1]-jc.iloc[0]  
  
        # 结算价  
        price_volume = history(symbol=context.main_contract, frequency='tick', fields='price,last_volume',  
                        start_time=(context.now-datetime.timedelta(minutes=59)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        settle_price = (price_volume['price']*price_volume['last_volume']).sum()/price_volume['last_volume'].sum()   
  
        # 查询持仓  
        pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
        pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
        # 交易逻辑：若无持仓  
        if not pos_long and not pos_short:  
            # 当收盘前14:45-14:59区间基差下降时，或者收盘前半小时委买总量大于委卖总量时，或者当收盘价（14:59的实时价格）低于结算价（14:00-14:59的成交量加权价格）时做多，持有至次日上午10点平仓;  
            if jc_diff <0 or total_bid_v > total_ask_v or price_volume['price'].iloc[-1] < settle_price:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Buy, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为买入开仓价做多', context.main_contract)  
  
            # 当收盘前14:45-14:59区间基差上升时，或者收盘前半小时委买总量小于委卖总量时，或者当收盘价（14:59的实时价格）高于结算价（14:00-14:59的成交量加权价格）时做空，持有至次日上午10点平仓  
            elif jc_diff >0 or total_bid_v < total_ask_v or price_volume['price'].iloc[-1] > settle_price:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Sell, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为卖出开仓价做空', context.main_contract)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='43ddbf73-92ab-11ef-ae02-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-10-26 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

## 基差
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import pandas as pd  
import datetime  
  
'''  
本策略采用定时任务结构（每天10：00：00、14:59:00定时执行策略algo_sell、algo_buy），建立隔夜收益增强模型  
当收盘前14:45-14:59区间基差下降时做多，持有至次日上午10点平仓;  
当收盘前14:45-14:59区间基差上升时做空，持有至次日上午10点平仓。  
'''  
    
def init(context):   
    # 设置交易品种  
    context.main_contract = 'CFFEX.IC00'  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_list}  
    # 如果是交易时间段，等到开盘时间确保进入algo()  
    schedule(schedule_func=algo_sell, date_rule='1d', time_rule='10:00:00')  
    schedule(schedule_func=algo_buy, date_rule='1d', time_rule='14:59:00')   
  
  
def algo_sell(context):  
    # 查询持仓  
    pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
    pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
    # 若持有多仓  
    if pos_long:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_1 = history_n(symbol=pos_long['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为卖出平仓价下单平多仓  
        sell_price_long = quotes_1[0]['bid_p']  
        order_target_percent(symbol=pos_long['symbol'], percent=0, position_side=PositionSide_Long,  
                             order_type=OrderType_Limit, price=sell_price_long)  
        print('以买一价为卖出平仓价平多仓', context.main_contract)  
  
    # 若持有空仓  
    if pos_short:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_2 = history_n(symbol=pos_short['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为买入平仓价下单平空仓  
        sell_price_short = quotes_2[0]['bid_p']  
        order_target_percent(symbol=pos_short['symbol'], percent=0, position_side=PositionSide_Short,  
                             order_type=OrderType_Limit, price=sell_price_short)  
        print('以买一价为买入平仓价平空仓', context.main_contract)  
  
  
def algo_buy(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约      
date = now_str  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list:  
        context.main_contract = context.main_contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取当月交易标的的基本交割日期  
    symbol_info = get_symbols(sec_type1=1040, symbols=context.main_contract, trade_date=now_str, df=False)  
    delisted_date = symbol_info[0]['delisted_date']  
      
    # 若当日不是交易标的的交割日期则进行判断买卖，否则不进行买卖  
    if delisted_date.strftime('%Y-%m-%d') != context.now.strftime('%Y-%m-%d'):  
        # 每天14：59：00 定时获取 tick 数据，实时模式可以用current函数获取  
        # 获取当月合约的价格  
        print(context.now,context.main_contract)  
        price_main = history_n(symbol=context.main_contract, frequency='tick', end_time=context.now,  
                            count=1, fields='price, quotes', adjust=ADJUST_PREV,  
                            adjust_end_time=context.now, df=True)  
  
        # 获取前30分钟的买一委托总量和卖一委托总量  
        quotes_3 = history(symbol=context.main_contract, frequency='tick', fields='quotes',  
                        start_time=(context.now-datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        # 买一委托总量  
        total_bid_v = quotes_3.quotes.apply(lambda x: x[0]['bid_v']).sum()  
        # 卖一委托总量  
        total_ask_v = quotes_3.quotes.apply(lambda x: x[0]['ask_v']).sum()  
  
        # 结算价  
        underlying_symbol = symbol_info[0]['underlying_symbol']  
        price_data = history(symbol=[context.main_contract,underlying_symbol], frequency='60s', fields='symbol,eob,close',  
                        start_time=(context.now-datetime.timedelta(minutes=14)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        underlying_price = price_data[price_data['symbol']==underlying_symbol].set_index('eob')  
        main_price = price_data[price_data['symbol']==context.main_contract].set_index('eob')  
        jc = (underlying_price['close']-main_price['close']).dropna()  
        jc_diff = jc.iloc[-1]-jc.iloc[0]  
        # 查询持仓  
        pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
        pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
        # 交易逻辑：若无持仓  
        if not pos_long and not pos_short:  
            # 当收盘前14:45-14:59区间基差下降时，根据卖一价作为买入开仓价做多，持有至次日上午10点平仓  
            if jc_diff <0:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Buy, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为买入开仓价做多', context.main_contract)  
  
            # 当收盘前14:45-14:59区间基差上升时，根据卖一价作为卖出开仓价做空，持有至次日上午10点平仓  
            if jc_diff >0:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Sell, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为卖出开仓价做空', context.main_contract)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='43ddbf73-92ab-11ef-ae02-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-10-26 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

## 买卖委托不均衡
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import pandas as pd  
import datetime  
  
'''  
本策略采用定时任务结构（每天10：00：00、14:59:00定时执行策略algo_sell、algo_buy），建立隔夜收益增强模型  
收盘前半小时委买总量大于委卖总量时做多，持有至次日上午10点平仓;  
收盘前半小时委买总量小于委卖总量时做空，持有至次日上午10点平仓。  
'''  
  
def init(context):   
    # 设置交易品种  
    context.main_contract = 'CFFEX.IC00'  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_list}  
    # 如果是交易时间段，等到开盘时间确保进入algo()  
    schedule(schedule_func=algo_sell, date_rule='1d', time_rule='10:00:00')  
    schedule(schedule_func=algo_buy, date_rule='1d', time_rule='14:59:00')   
  
  
def algo_sell(context):  
    # 查询持仓  
    pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
    pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
    # 若持有多仓  
    if pos_long:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_1 = history_n(symbol=pos_long['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为卖出平仓价下单平多仓  
        sell_price_long = quotes_1[0]['bid_p']  
        order_target_percent(symbol=pos_long['symbol'], percent=0, position_side=PositionSide_Long,  
                             order_type=OrderType_Limit, price=sell_price_long)  
        print('以买一价为卖出平仓价平多仓', context.main_contract)  
  
    # 若持有空仓  
    if pos_short:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_2 = history_n(symbol=pos_short['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为买入平仓价下单平空仓  
        sell_price_short = quotes_2[0]['bid_p']  
        order_target_percent(symbol=pos_short['symbol'], percent=0, position_side=PositionSide_Short,  
                             order_type=OrderType_Limit, price=sell_price_short)  
        print('以买一价为买入平仓价平空仓', context.main_contract)  
  
  
def algo_buy(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约      
date = now_str  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list:  
        context.main_contract = context.main_contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取当月交易标的的基本交割日期  
    symbol_info = get_symbols(sec_type1=1040, symbols=context.main_contract, trade_date=now_str, df=False)  
    delisted_date = symbol_info[0]['delisted_date']  
      
    # 若当日不是交易标的的交割日期则进行判断买卖，否则不进行买卖  
    if delisted_date.strftime('%Y-%m-%d') != context.now.strftime('%Y-%m-%d'):  
        # 每天14：59：00 定时获取 tick 数据，实时模式可以用current函数获取  
        # 获取当月合约的价格  
        print(context.now,context.main_contract)  
        price_main = history_n(symbol=context.main_contract, frequency='tick', end_time=context.now,  
                            count=1, fields='price, quotes', adjust=ADJUST_PREV,  
                            adjust_end_time=context.now, df=True)  
  
        # 获取前30分钟的买一委托总量和卖一委托总量  
        quotes_3 = history(symbol=context.main_contract, frequency='tick', fields='quotes',  
                        start_time=(context.now-datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        # 买一委托总量  
        total_bid_v = quotes_3.quotes.apply(lambda x: x[0]['bid_v']).sum()  
        # 卖一委托总量  
        total_ask_v = quotes_3.quotes.apply(lambda x: x[0]['ask_v']).sum()  
  
        # 查询持仓  
        pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
        pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
        # 交易逻辑：若无持仓  
        if not pos_long and not pos_short:  
            # 收盘前半小时委买总量大于委卖总量时，根据卖一价作为买入开仓价做多，持有至次日上午10点平仓  
            if total_bid_v > total_ask_v:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Buy, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为买入开仓价做多', context.main_contract)  
  
            # 收盘前半小时委买总量小于委卖总量时，根据卖一价作为卖出开仓价做空，持有至次日上午10点平仓  
            if total_bid_v < total_ask_v:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Sell, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为卖出开仓价做空', context.main_contract)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='43ddbf73-92ab-11ef-ae02-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-10-26 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)


## 收盘折溢价
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import pandas as pd  
import datetime  
  
'''  
本策略采用定时任务结构（每天10：00：00、14:59:00定时执行策略algo_sell、algo_buy），建立隔夜收益增强模型  
当价差小于80，且收盘前半小时委买总量大于委卖总量时做多，持有至次日上午10点平仓;  
当价差大于80，且收盘前半小时委买总量小于委卖总量时做空，持有至次日上午10点平仓。  
'''  
  
def init(context):   
    # 设置交易品种  
    context.main_contract = 'CFFEX.IC00'  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_list}  
    # 如果是交易时间段，等到开盘时间确保进入algo()  
    schedule(schedule_func=algo_sell, date_rule='1d', time_rule='10:00:00')  
    schedule(schedule_func=algo_buy, date_rule='1d', time_rule='14:59:00')   
  
  
def algo_sell(context):  
    # 查询持仓  
    pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
    pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
    # 若持有多仓  
    if pos_long:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_1 = history_n(symbol=pos_long['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为卖出平仓价下单平多仓  
        sell_price_long = quotes_1[0]['bid_p']  
        order_target_percent(symbol=pos_long['symbol'], percent=0, position_side=PositionSide_Long,  
                             order_type=OrderType_Limit, price=sell_price_long)  
        print('以买一价为卖出平仓价平多仓', context.main_contract)  
  
    # 若持有空仓  
    if pos_short:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_2 = history_n(symbol=pos_short['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为买入平仓价下单平空仓  
        sell_price_short = quotes_2[0]['bid_p']  
        order_target_percent(symbol=pos_short['symbol'], percent=0, position_side=PositionSide_Short,  
                             order_type=OrderType_Limit, price=sell_price_short)  
        print('以买一价为买入平仓价平空仓', context.main_contract)  
  
  
def algo_buy(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约      
date = now_str  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list:  
        context.main_contract = context.main_contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取当月交易标的的基本交割日期  
    delisted_date = get_symbols(sec_type1=1040, symbols=context.main_contract, trade_date=now_str, df=False)[0]['delisted_date']  
      
    # 若当日不是交易标的的交割日期则进行判断买卖，否则不进行买卖  
    if delisted_date.strftime('%Y-%m-%d') != context.now.strftime('%Y-%m-%d'):  
        # 每天14：59：00 定时获取 tick 数据，实时模式可以用current函数获取  
        # 获取当月合约的价格  
        print(context.now,context.main_contract)  
        price_main = history_n(symbol=context.main_contract, frequency='tick', end_time=context.now,  
                            count=1, fields='price, quotes', adjust=ADJUST_PREV,  
                            adjust_end_time=context.now, df=True)  
  
        # 获取前30分钟的买一委托总量和卖一委托总量  
        quotes_3 = history(symbol=context.main_contract, frequency='tick', fields='quotes',  
                        start_time=(context.now-datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        # 买一委托总量  
        total_bid_v = quotes_3.quotes.apply(lambda x: x[0]['bid_v']).sum()  
        # 卖一委托总量  
        total_ask_v = quotes_3.quotes.apply(lambda x: x[0]['ask_v']).sum()  
  
        # 结算价  
        price_volume = history(symbol=context.main_contract, frequency='tick', fields='price,last_volume',  
                        start_time=(context.now-datetime.timedelta(minutes=59)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        settle_price = (price_volume['price']*price_volume['last_volume']).sum()/price_volume['last_volume'].sum()   
  
        # 查询持仓  
        pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
        pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
        # 交易逻辑：若无持仓  
        if not pos_long and not pos_short:  
            # 收盘前半小时委买总量大于委卖总量时，根据卖一价作为买入开仓价做多，持有至次日上午10点平仓  
            if total_bid_v > total_ask_v:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Buy, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为买入开仓价做多', context.main_contract)  
  
            # 收盘前半小时委买总量小于委卖总量时，根据卖一价作为卖出开仓价做空，持有至次日上午10点平仓  
            if total_bid_v < total_ask_v:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Sell, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为卖出开仓价做空', context.main_contract)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='43ddbf73-92ab-11ef-ae02-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-10-26 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

## 尾盘涨幅
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import pandas as pd  
import datetime  
  
'''  
本策略采用定时任务结构（每天10：00：00、14:59:00定时执行策略algo_sell、algo_buy），建立隔夜收益增强模型  
当收盘前14:45-14:59区间下跌时做多，持有至次日上午10点平仓;  
当收盘前14:45-14:59区间上涨时做空，持有至次日上午10点平仓。  
'''  
  
def init(context):   
    # 设置交易品种  
    context.main_contract = 'CFFEX.IC00'  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {dic['trade_date']:dic['symbol'] for dic in main_contract_list}  
    # 如果是交易时间段，等到开盘时间确保进入algo()  
    schedule(schedule_func=algo_sell, date_rule='1d', time_rule='10:00:00')  
    schedule(schedule_func=algo_buy, date_rule='1d', time_rule='14:59:00')   
  
  
def algo_sell(context):  
    # 查询持仓  
    pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
    pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
    # 若持有多仓  
    if pos_long:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_1 = history_n(symbol=pos_long['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为卖出平仓价下单平多仓  
        sell_price_long = quotes_1[0]['bid_p']  
        order_target_percent(symbol=pos_long['symbol'], percent=0, position_side=PositionSide_Long,  
                             order_type=OrderType_Limit, price=sell_price_long)  
        print('以买一价为卖出平仓价平多仓', context.main_contract)  
  
    # 若持有空仓  
    if pos_short:  
        # 每天10：00：00 定时获取 tick 数据，实时模式可以用current函数获取  
        quotes_2 = history_n(symbol=pos_short['symbol'], frequency='tick', count=1, end_time=context.now,  
                             fields='quotes', adjust=ADJUST_PREV, adjust_end_time=context.now, df=True).quotes[0]  
  
        # 根据买一价作为买入平仓价下单平空仓  
        sell_price_short = quotes_2[0]['bid_p']  
        order_target_percent(symbol=pos_short['symbol'], percent=0, position_side=PositionSide_Short,  
                             order_type=OrderType_Limit, price=sell_price_short)  
        print('以买一价为买入平仓价平空仓', context.main_contract)  
  
  
def algo_buy(context):  
    # 当天日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约      
date = now_str  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list:  
        context.main_contract = context.main_contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.main_contract, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
  
    # 获取当月交易标的的基本交割日期  
    symbol_info = get_symbols(sec_type1=1040, symbols=context.main_contract, trade_date=now_str, df=False)  
    delisted_date = symbol_info[0]['delisted_date']  
      
    # 若当日不是交易标的的交割日期则进行判断买卖，否则不进行买卖  
    if delisted_date.strftime('%Y-%m-%d') != context.now.strftime('%Y-%m-%d'):  
        # 每天14：59：00 定时获取 tick 数据，实时模式可以用current函数获取  
        # 获取当月合约的价格  
        print(context.now,context.main_contract)  
        price_main = history_n(symbol=context.main_contract, frequency='tick', end_time=context.now,  
                            count=1, fields='price, quotes', adjust=ADJUST_PREV,  
                            adjust_end_time=context.now, df=True)  
  
        # 获取前30分钟的买一委托总量和卖一委托总量  
        quotes_3 = history(symbol=context.main_contract, frequency='tick', fields='quotes',  
                        start_time=(context.now-datetime.timedelta(minutes=30)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        # 买一委托总量  
        total_bid_v = quotes_3.quotes.apply(lambda x: x[0]['bid_v']).sum()  
        # 卖一委托总量  
        total_ask_v = quotes_3.quotes.apply(lambda x: x[0]['ask_v']).sum()  
  
        # 结算价  
        price_data = history(symbol=[context.main_contract], frequency='60s', fields='symbol,eob,close',  
                        start_time=(context.now-datetime.timedelta(minutes=14)).strftime('%Y-%m-%d %H:%M:%S'),  
                        end_time=context.now, adjust=ADJUST_PREV, adjust_end_time=context.now, df=True)  
        return_diff = price_data['close'].iloc[-1]-price_data['close'].iloc[0]  
        # 查询持仓  
        pos_long = context.account().position(symbol=context.main_contract, side=PositionSide_Long)  
        pos_short = context.account().position(symbol=context.main_contract, side=PositionSide_Short)  
  
        # 交易逻辑：若无持仓  
        if not pos_long and not pos_short:  
            # 当收盘前14:45-14:59区间下跌时，根据卖一价作为买入开仓价做多，持有至次日上午10点平仓  
            if return_diff <0:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Buy, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为买入开仓价做多', context.main_contract)  
  
            # 当收盘前14:45-14:59区间上涨时，根据卖一价作为卖出开仓价做空，持有至次日上午10点平仓  
            if return_diff >0:  
                order_percent(symbol=context.main_contract, percent=0.8, side=OrderSide_Sell, order_type=OrderType_Limit,  
                            position_effect=PositionEffect_Open, price=price_main.quotes[0][0]['ask_p'])  
                print('以卖一价为卖出开仓价做空', context.main_contract)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='43ddbf73-92ab-11ef-ae02-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-10-26 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# ORB突破策略
**1、****策略说明**

ORB突破策略最早由美国基金经理托比于1988年提出。策略核心思想是以历史数据的振幅作为当日突破界限，当市场突破该界限后，便认为是真正的突破，跟随该趋势。

**ORB****策略的相关指标定义如下：**

ORB=MIN(ABS(昨高-昨收)，ABS(昨低-昨收))

上轨=今日开盘价+MEAN(ORB,N)*M

下轨=今日开盘价-MEAN(ORB,N)*M

当价格突破上轨，买入开仓；

当价格跌穿下轨，卖出开仓。
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import multiprocessing  
import matplotlib.pyplot as plt  
import numpy as np  
import pandas as pd  
pd.set_option('display.max_rows',None)      #显示所有列 pd.set_option('display.max_columns',None)   #显示所有行 # 用来正常显示中文标签        plt.rcParams['font.sans-serif']=['SimHei']   
#用来正常显示负号        plt.rcParams['axes.unicode_minus']=False   
"""  
日内交易策略，移动止盈止损，收盘平仓；  
  
ORB失败突破基于过去N个交易日ORB指标；  
ORB=MIN(ABS(昨高-昨收),ABS(昨低-昨收))  
上轨＝今日开盘价+N天ORB*M；  
下轨＝今日开盘价－N天ORB*M；  
当价格突破上轨，买入开仓；  
当价格跌穿下轨，卖出开仓。  
"""  
  
def init(context):  
    # 设置标的  
    context.exchange = 'CFFEX'  
    # context.symbol = 'CFFEX.IF'  
    # 盘前运行  
    context.periods_N = 3# ORB 周期  
    context.param_up_M = 2#上轨ORB倍数  
    context.param_down_M = 1.5#下轨ORB倍数  
    context.up_stop_rate = 0.02# 止盈幅度  
    context.down_stop_rate = -0.03# 止盈幅度  
    # 评价指标  
    context.long_win_times = 0# 多头盈利次数  
    context.long_loss_times = 0# 多头亏损次数  
    context.long_win_money = 0# 多头盈利金额  
    context.long_loss_money = 0 # 多头亏损金额  
    context.short_win_times = 0#空头盈利次数  
    context.short_loss_times = 0# 空头亏损次数  
    context.short_win_money = 0# 空头盈利金额  
    context.short_loss_money = 0 # 空头亏损金额  
    context.orb_df = pd.DataFrame(columns=['ORB'])  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(contract_list)>0:  
            context.contract_list = {dic['trade_date']:dic['symbol'] for dic in contract_list}  
    # 设置定时任务：夜盘21点开始，日盘9点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')  
  
  
def algo(context):  
    # 更换主力合约  
    now_str = context.now.strftime('%Y-%m-%d')  
    last_date = get_previous_n_trading_dates(exchange=context.exchange, date=now_str, n=1)[0]  
    # 主力合约  
    if context.now.hour>15:  
        date = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    if context.mode==MODE_BACKTEST and date in context.contract_list:  
        context.main_contract = context.contract_list[date]  
    else:  
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出  
    Account_positions = get_position()  
    if Account_positions:  
        for posi in Account_positions:  
            if context.main_contract!=posi['symbol']:  
                # 更换合约  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))  
                new_price = history(symbol=posi['symbol'], frequency='1d', start_time=now_str,  end_time=now_str, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
                order_target_volume(symbol=posi['symbol'],   
                                    volume=0,   
                                    position_side=posi['side'],   
                                    order_type=OrderType_Limit,  
                                    price=new_price)  
    # 订阅一分钟线  
    subscribe(symbols = context.main_contract,frequency = '60s',count = 1)  
      
    # 计算ORB数值  
    df = history_n(context.main_contract, frequency='1d', count=context.periods_N, end_time=last_date, fields='symbol,eob,close,high,low', adjust=ADJUST_PREV, df=True)  
    df['amp_up'] = abs(df['high']-df['close'])  
    df['amp_down'] = abs(df['low']-df['close'])  
    df['ORB'] = df[['amp_up','amp_down']].min(axis=1)  
    ORB = df['ORB'].mean()     
    # 获取当日开盘价  
    # 如果是回测模式  
    if context.mode == 2:  
        # 开盘价直接在data最后一个数据里取到,前一交易日的最高和最低价为history_data里面的倒数第二条中取到  
        open_price = df = history_n(context.main_contract, frequency='1d', count=1, end_time=context.now, fields='symbol,open', adjust=ADJUST_PREV, df=True)['open'][0]  
    # 如果是实时模式  
    else:  
        # 开盘价通过current取到  
        open_price = current(context.main_contract)[0]['open']     
    # 计算上下轨价格  
    context.upper_price = open_price+ORB*context.param_up_M  
    context.lower_price = open_price-ORB*context.param_down_M  
    context.orb_df.loc[context.now,'ORB']=ORB  
      
  
def on_bar(context,bars):  
    if len(context.orb_df)>=10 and context.orb_df.iloc[-10:,:].mean()[0]>15:  
        # 取出订阅的这一分钟的bar  
        bar = bars[0]  
        symbol = bars[0]['symbol']  
        end_market_time = context.now.hour == 15  
        # 现有持仓情况  
        position_long = context.account().position(symbol=symbol, side=PositionSide_Long)  
        position_short = context.account().position(symbol=symbol, side=PositionSide_Short)  
        # 交易逻辑部分  
        if position_long:    
            vwap = position_long['vwap']# 持仓均价  
            if bar.close/vwap-1>context.up_stop_rate or bar.close/vwap-1<context.down_stop_rate or end_market_time:  
                info = order_volume(symbol=symbol, volume=1, side=OrderSide_Sell,  
                            order_type=OrderType_Market, position_effect=PositionEffect_Close)  
                update_data(context,position_long,info)  
                print('1,以市价单平多仓')  
  
        elif position_short:  
            vwap = position_short['vwap']# 持仓均价  
            if bar.close/vwap-1>abs(context.down_stop_rate) or bar.close/vwap-1<-abs(context.up_stop_rate) or end_market_time:  
                info = order_volume(symbol=symbol, volume=1, side=OrderSide_Buy,  
                            order_type=OrderType_Market, position_effect=PositionEffect_Close)  
                update_data(context,position_short,info)  
                print('2,以市价单平空仓')  
  
        else:  # 没有持仓  
            if bar.close > context.upper_price:  # 当前的最新价大于上轨价格  
                # 开多  
                info = order_volume(symbol=symbol, volume=1, side=OrderSide_Buy,  
                            order_type=OrderType_Market, position_effect=PositionEffect_Open)  
                print('3,{}：以市价单开多仓'.format(context.now))  
            elif bar.close < context.lower_price:  # 当前最新价小于下轨价格  
                # 开空  
                info = order_volume(symbol=symbol, volume=1, side=OrderSide_Sell,  
                            order_type=OrderType_Market, position_effect=PositionEffect_Open)  
                print('4,{}：以市价单开空仓'.format(context.now))  
  
def update_data(context,position,order_info):  
    pass  

def on_backtest_finished(context, indicator):  
    # 回测业绩指标数据  
    data = [  
        indicator['pnl_ratio'], indicator['pnl_ratio_annual'],indicator['sharp_ratio'],  
        indicator['max_drawdown'], context.symbol  
    ]  
    # 将超参加入context.result  
    context.result.append(data)  
  
  
def run_strategy(param):  
    from gm.model.storage import context  
    # 用context传入回测次数参数  
    context.symbol = param  
    # context.result用以存储超参  
    context.result = []  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='bb627376-7906-11ec-8399-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='{{token}}',  
        backtest_start_time='2022-01-01 08:00:00',  
        backtest_end_time='2024-10-12 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001)  
    return context.result  
      
  
if __name__ == '__main__':  
    # 循环输入参数  
    param = ['CFFEX.IF','CFFEX.IC','CFFEX.IH']  
    a_list = []  
    pool = multiprocessing.Pool(processes=len(param), maxtasksperchild=1)  # create processes  
    for i in range(len(param)):  
        a_list.append(pool.apply_async(func=run_strategy, args=(param[i],)))  
    pool.close()  
    pool.join()  
    info = []  
    for pro in a_list:  
        print('pro', pro.get()[0])  
        info.append(pro.get()[0])  
    print(info)  
    info = pd.DataFrame(np.array(info), columns=['pnl_ratio', 'pnl_ratio_annual', 'sharp_ratio', 'max_drawdown', 'symbol'])  
    info.to_csv('ORB突破策略_最小ORB.csv', index=False)  
  
    # info = pd.DataFrame(np.array(info), columns=['pnl_ratio', 'pnl_ratio_annual', 'sharp_ratio', 'max_drawdown', '多单胜率','多单盈亏比','多单盈亏额','空单胜率','空单盈亏比','空单盈亏额','交易次数','最小ORB'])  
    info.to_csv('ORB突破策略_最小ORB.csv', index=False)


# 形态匹配策略
**策略说明**

1、策略以近期涨幅最大的股票形态为目标，通过每日涨跌幅、每日振幅和每日成交量变化率三个特征进行全市场匹配股票，以相关系数大于阈值作为匹配标准。

2、优化策略修改1W的定时任务为1D（实盘不支持1W的参数）、增加非周期首日的卖出判断，防止跌停或停牌导致的未卖出情况、增加相关系数spearson的参数。

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import pandas as pd  
import numpy as np  
import copy  
  
"""  
形态匹配策略  
每周第一个交易日选取最近一周表现最好的一只股票，再以这只股票上上周的K线形态去匹配当前的所有股票，  
剔除低相关性的股票，在剩余股票中平均买入相关性最高的N只股票，每周轮换。  
"""  
  
def init(context):  
    # 形态数量，即带匹配的股票数  
    context.form_num = 1  
    # 每个形态匹配的数量  
    context.matching_num = 10  
    # 涨幅区间时间,天  
    context.periods_time = 5  
    # 相关性阈值  
    context.similarity_threshold = 0.8  
    # 每周定时任务  
    schedule(schedule_func=algo, date_rule='1w', time_rule='14:57:00')  
  
  
def algo(context):  
    # 获取全A股票（剔除停牌，ST，次新股等）  
    all_stocks,all_stocks_str = get_normal_stocks(context,context.now,new_days=365)  
    context.all_security = all_stocks_str  
    # 日期列表  
    today = context.now.strftime('%Y-%m-%d')  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.periods_time+1)  
    # 涨幅区间的开始时间  
    return_start_date = date_list[0]  
    # 涨幅区间的结束时间，即上一个交易日  
    return_end_date =date_list[-1]  
    # 选取历史M天涨幅最高的N只股票  
    close = history_new(security=all_stocks_str,frequency='1d',start_time=return_start_date,end_time=return_end_date,fields='eob,symbol,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=today,df=True)  
    return_rate = (close.iloc[-1,:]/close.iloc[0,:]-1).sort_values(ascending = False)[:context.form_num]  
    best_symbols = ','.join(list(return_rate.index))  
    print('*****************************************')  
    print('{}，最优股票：{}'.format(context.now,best_symbols))  
    # 形态匹配  
    # 形态的开始时间  
    # form_start_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.periods_time+context.periods_time+1)[0]  
    # to_buy = form_matching(context,security=best_symbols,start_date=form_start_date,end_date=return_start_date,simi_threshold=context.similarity_threshold,matching_num=context.matching_num)  
    to_buy = form_matching(context,security=best_symbols,end_date=return_end_date,n=context.periods_time,simi_threshold=context.similarity_threshold,matching_num=context.matching_num)  
    print('{}，待买入股票：{}\n'.format(context.now,to_buy))  
  
    # 获取持仓  
    positions = context.account().positions()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    # 平不在标的池的股票  
    for position in positions:  
        symbol = position['symbol']  
        if symbol not in to_buy:  
            close_price = history(symbol=symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, adjust_end_time=today, df= True)  
            lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=today, skip_st=False)  
            if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=close_price['close'].iloc[0]:  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=close_price['close'].iloc[0])  
            else:  
                print('{}:{}数据跌停或停牌导致未能卖出！'.format(context.now,symbol))  
              
    # 买在标的池中的股票  
    # 所有持仓  
    positions = get_position()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    for symbol in to_buy:  
        close_price = history(symbol=symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df= True)  
        upper_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
        if len(upper_limit)>0 and len(close_price)>0 and upper_limit[0]['upper_limit']!=close_price['close'].iloc[0]:  
            new_price = close_price['close'].iloc[0]  
            nav = context.account().cash['nav']  
            trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/(context.form_num*context.matching_num),price=new_price)  
            order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
  
   
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def form_matching(context,security,end_date,n,simi_threshold,matching_num=1):  
    """  
    形态匹配    最优股票的形态区间：(end_date-2n,end_date-n],输出股票的形态区间：(end_date-n,end_date]  
    :param security:待匹配的股票，str  
    :param end_date:结束时间  
    :param n:形态的天数  
    :param simi_threshold：形似度的阈值  
    :param maatching_num:每只股票的匹配数量，默认为1  
    """    date_list = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=2*n)  
    last_best_start_date = date_list[0]  
    best_start_date = date_list[1]  
    best_end_date = date_list[n]  
    ## 获取待匹配股票的历史形态数据(每日涨跌幅、每日振幅、每日成交量变化比率)  
    # 每日涨跌幅  
    close = history_new(security=security,frequency='1d',start_time=last_best_start_date,end_time=best_end_date,fields='eob,symbol,close',skip_suspended=True,fill_missing='Last',adjust=ADJUST_POST,df=True)  
    d_return = (close/close.shift(1)-1).iloc[1:,:]  
    # 每日振幅  
    high = history_new(security=security,frequency='1d',start_time=best_start_date,end_time=best_end_date,fields='eob,symbol,high',skip_suspended=True,fill_missing='NaN',adjust=ADJUST_POST,df=True)  
    low = history_new(security=security,frequency='1d',start_time=best_start_date,end_time=best_end_date,fields='eob,symbol,low',skip_suspended=True,fill_missing='NaN',adjust=ADJUST_POST,df=True)  
    d_amplitude = high/low-1  
    # 每日成交量变化比率  
    volume = history_new(security=security,frequency='1d',start_time=last_best_start_date,end_time=best_end_date,fields='eob,symbol,volume',skip_suspended=True,fill_missing='NaN',adjust=ADJUST_POST,df=True)  
    d_volume_rate = (volume/volume.shift(1)-1).iloc[1:,:]  
  
    ## 获取当前所有股票（剔除停牌，ST，次新股等）的最新形态数据  
    last_return_start_date = date_list[-n]  
    return_start_date = date_list[-n+1]  
    return_end_date = end_date  
    # 每日涨跌幅  
    new_close = history_new(security=context.all_security,frequency='1d',start_time=last_return_start_date,end_time=return_end_date,fields='eob,symbol,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_d_return = (new_close/new_close.shift(1)-1).iloc[1:,:]  
    # 每日振幅  
    new_high = history_new(security=context.all_security,frequency='1d',start_time=return_start_date,end_time=return_end_date,fields='eob,symbol,high',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_low = history_new(security=context.all_security,frequency='1d',start_time=return_start_date,end_time=return_end_date,fields='eob,symbol,low',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_d_amplitude = new_high/new_low-1  
    # 每日成交量变化比率  
    new_volume = history_new(security=context.all_security,frequency='1d',start_time=last_return_start_date,end_time=return_end_date,fields='eob,symbol,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_d_volume_rate = (new_volume/new_volume.shift(1)-1).iloc[1:,:]  
    # 计算相关性  
    high_simi_security = []  
    for code in security.split(','):  
        # 每日收益相关性  
        df0 = copy.deepcopy(new_d_return)  
        if len(d_return.loc[:,code])<len(df0):continue  
        df0.loc[:,'base_form'] = d_return.loc[:,code].values  
        simi_return = df0.corr(method ='pearson').loc[:,'base_form']  
        simi_return = simi_return[:len(simi_return)-1]  
        # # 每日振幅相关性  
        df1 = copy.deepcopy(new_d_amplitude)  
        if len(d_amplitude.loc[:,code])<len(df1):continue  
        df1.loc[:,'base_form'] = d_amplitude.loc[:,code].values  
        simi_amplitude = df1.corr(method ='pearson').loc[:,'base_form']  
        simi_amplitude = simi_amplitude[:len(simi_amplitude)-1]  
        # # 每日成交量变化比率相关性  
        df2 = copy.deepcopy(new_d_volume_rate)  
        if len(d_volume_rate.loc[:,code])<len(df2):continue  
        df2.loc[:,'base_form'] = d_volume_rate.loc[:,code].values  
        simi_volume_rate = df2.corr(method ='pearson').loc[:,'base_form']  
        simi_volume_rate = simi_volume_rate[:len(simi_volume_rate)-1]  
        # 计算平均相似度  
        simi = (simi_return+simi_amplitude+simi_volume_rate)/3  
        # 过滤相似度阈值  
        simi = simi[simi>=context.similarity_threshold]  
        if len(simi)>0:  
            # 保留股票  
            if matching_num==1:  
                high_simi_security.append(simi.idxmax())  
            else:  
                high_simi_security = high_simi_security+list((simi.sort_values(ascending=False)[:matching_num]).index)  
    return high_simi_security  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_limit:是否剔除开盘涨停股票，默认为False,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:  
        pass  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
  
def on_backtest_finished(context, indicator):  
    print(indicator)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='6eece943-915e-11ec-9103-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 08:00:00',  
        backtest_end_time='2024-08-11 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import pandas as pd  
import numpy as np  
import copy  
  
"""  
形态匹配策略  
每周第一个交易日选取最近一周表现最好的一只股票，再以这只股票上上周的K线形态去匹配当前的所有股票，  
剔除低相关性的股票，在剩余股票中平均买入相关性最高的N只股票，每周轮换。  
"""  
  
def init(context):# 3*3     

    context.form_num = 2  
    # 每个形态匹配的数量  
    context.matching_num = 5  
    # 涨幅区间时间,天  
    context.periods_time = 10  
    # 相关性阈值  
    context.similarity_threshold = 0.8  
    # 其他  
    context.to_buy = []  
    # 每周定时任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:57:00')  
  
  
def algo(context):  
    trade_status = is_Week_or_Month_first_day(context,context.now.strftime('%Y-%m-%d'),periods_type='week')  
    if trade_status:  
        # 获取全A股票（剔除停牌，ST，次新股等）  
        all_stocks,all_stocks_str = get_normal_stocks(context,context.now,new_days=365)  
        context.all_security = all_stocks_str  
        # 日期列表  
        today = context.now.strftime('%Y-%m-%d')  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.periods_time+1)  
        # 涨幅区间的开始时间  
        return_start_date = date_list[0]  
        # 涨幅区间的结束时间，即上一个交易日  
        return_end_date =date_list[-1]  
        # 选取历史M天涨幅最高的N只股票  
        close = history_new(security=all_stocks_str,frequency='1d',start_time=return_start_date,end_time=return_end_date,fields='eob,symbol,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=today,df=True)  
        return_rate = (close.iloc[-1,:]/close.iloc[0,:]-1).sort_values(ascending = False)[:context.form_num]  
        best_symbols = ','.join(list(return_rate.index))  
        print('*****************************************')  
        print('{}，最优股票：{}'.format(context.now,best_symbols))  
        # 形态匹配  
        # 形态的开始时间  
        # form_start_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.periods_time+context.periods_time+1)[0]  
        # context.to_buy = form_matching(context,security=best_symbols,start_date=form_start_date,end_date=return_start_date,simi_threshold=context.similarity_threshold,matching_num=context.matching_num)  
        context.to_buy = form_matching(context,security=best_symbols,end_date=return_end_date,n=context.periods_time,simi_threshold=context.similarity_threshold,matching_num=context.matching_num,corr_type='pearson')  
        print('{}，待买入股票：{}\n'.format(context.now,context.to_buy))  
  
        # 获取持仓  
        positions = context.account().positions()  
        holding_stocks = [posi['symbol'] for posi in positions]  
        # 平不在标的池的股票  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in context.to_buy:  
                close_price = history(symbol=symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, adjust_end_time=today, df= True)  
                lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=today, skip_st=False)  
                if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=close_price['close'].iloc[0]:  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=close_price['close'].iloc[0])  
                else:  
                    print('{}:{}数据跌停或停牌导致未能卖出！'.format(context.now,symbol))  
                  
        # 买在标的池中的股票  
        # 所有持仓  
        positions = get_position()  
        holding_stocks = [posi['symbol'] for posi in positions]  
        for symbol in context.to_buy:  
            close_price = history(symbol=symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df= True)  
            upper_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
            if len(upper_limit)>0 and len(close_price)>0 and upper_limit[0]['upper_limit']!=close_price['close'].iloc[0]:  
                new_price = close_price['close'].iloc[0]  
                nav = context.account().cash['nav']  
                trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/(context.form_num*context.matching_num),price=new_price)  
                order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
    else:  
        # 再次进行卖出判断，防止部分跌停或停牌股未卖出  
        positions = context.account().positions()  
        holding_stocks = [posi['symbol'] for posi in positions]  
        # 平不在标的池的股票  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in context.to_buy:  
                close_price = history(symbol=symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, adjust_end_time=today, df= True)  
                lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=today, skip_st=False)  
                if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=close_price['close'].iloc[0]:  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=close_price['close'].iloc[0])  
                else:  
                    print('{}:{}数据跌停或停牌导致未能卖出！'.format(context.now,symbol))  
                      
   
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def is_Week_or_Month_first_day(context,date_str,periods_type):  
    """  
    判断日期是否为 周/月 的第1个交易日  
    :param date_str：目标日期,str  
    :param periods_type：类型，'week'为周，'month'为月  
    """    # 获取前一个交易日  
    pre_date_str = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)[0]  
    # 时间格式转化，将str格式的日期转换成Timestamp  
    pre_date = pd.Timestamp(pre_date_str)  
    date = pd.Timestamp(date_str)  
    # 判断是否是当月第一个交易日  
    if periods_type=='week':  
        status = date.week!=pre_date.week  
    elif periods_type=='month':  
        status = date.month!=pre_date.month  
    return status  
  
  
def form_matching(context,security,end_date,n,simi_threshold,matching_num=1,corr_type='pearson'):  
    """  
    形态匹配    最优股票的形态区间：(end_date-2n,end_date-n],输出股票的形态区间：(end_date-n,end_date]  
    :param security:待匹配的股票，str  
    :param end_date:结束时间  
    :param n:形态的天数  
    :param simi_threshold：形似度的阈值  
    :param maatching_num:每只股票的匹配数量，默认为1  
    :param corr_type:相关性类型，pearson相关系数，spearman相关系数  
    """    date_list = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=2*n)  
    last_best_start_date = date_list[0]  
    best_start_date = date_list[1]  
    best_end_date = date_list[n]  
    ## 获取待匹配股票的历史形态数据(每日涨跌幅、每日振幅、每日成交量变化比率)  
    # 每日涨跌幅  
    close = history_new(security=security,frequency='1d',start_time=last_best_start_date,end_time=best_end_date,fields='eob,symbol,close',skip_suspended=True,fill_missing='Last',adjust=ADJUST_POST,df=True)  
    d_return = (close/close.shift(1)-1).iloc[1:,:]  
    # 每日振幅  
    high = history_new(security=security,frequency='1d',start_time=best_start_date,end_time=best_end_date,fields='eob,symbol,high',skip_suspended=True,fill_missing='NaN',adjust=ADJUST_POST,df=True)  
    low = history_new(security=security,frequency='1d',start_time=best_start_date,end_time=best_end_date,fields='eob,symbol,low',skip_suspended=True,fill_missing='NaN',adjust=ADJUST_POST,df=True)  
    d_amplitude = high/low-1  
    # 每日成交量变化比率  
    volume = history_new(security=security,frequency='1d',start_time=last_best_start_date,end_time=best_end_date,fields='eob,symbol,volume',skip_suspended=True,fill_missing='NaN',adjust=ADJUST_POST,df=True)  
    d_volume_rate = (volume/volume.shift(1)-1).iloc[1:,:]  
  
    ## 获取当前所有股票（剔除停牌，ST，次新股等）的最新形态数据  
    last_return_start_date = date_list[-n]  
    return_start_date = date_list[-n+1]  
    return_end_date = end_date  
    print('最优形态区间：{}至{}\n匹配形态区间：{}至{}'.format(best_start_date,best_end_date,return_start_date,return_end_date))  
    # 每日涨跌幅  
    new_close = history_new(security=context.all_security,frequency='1d',start_time=last_return_start_date,end_time=return_end_date,fields='eob,symbol,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_d_return = (new_close/new_close.shift(1)-1).iloc[1:,:]  
    # 每日振幅  
  
    new_high = history_new(security=context.all_security,frequency='1d',start_time=return_start_date,end_time=return_end_date,fields='eob,symbol,high',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_low = history_new(security=context.all_security,frequency='1d',start_time=return_start_date,end_time=return_end_date,fields='eob,symbol,low',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_d_amplitude = new_high/new_low-1  
    # 每日成交量变化比率  
    new_volume = history_new(security=context.all_security,frequency='1d',start_time=last_return_start_date,end_time=return_end_date,fields='eob,symbol,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_POST,df=True)  
    new_d_volume_rate = (new_volume/new_volume.shift(1)-1).iloc[1:,:]  
    # 计算相关性  
    high_simi_security = []  
    for code in security.split(','):  
        # 每日收益相关性  
        df0 = copy.deepcopy(new_d_return)  
        if len(d_return.loc[:,code])<len(df0):continue  
        df0.loc[:,'base_form'] = d_return.loc[:,code].values  
        simi_return = df0.corr(method =corr_type).loc[:,'base_form']  
        simi_return = simi_return[:len(simi_return)-1]  
        # # 每日振幅相关性  
        df1 = copy.deepcopy(new_d_amplitude)  
        if len(d_amplitude.loc[:,code])<len(df1):continue  
        df1.loc[:,'base_form'] = d_amplitude.loc[:,code].values  
        simi_amplitude = df1.corr(method =corr_type).loc[:,'base_form']  
        simi_amplitude = simi_amplitude[:len(simi_amplitude)-1]  
        # # 每日成交量变化比率相关性  
        df2 = copy.deepcopy(new_d_volume_rate)  
        if len(d_volume_rate.loc[:,code])<len(df2):continue  
        df2.loc[:,'base_form'] = d_volume_rate.loc[:,code].values  
        simi_volume_rate = df2.corr(method =corr_type).loc[:,'base_form']  
        simi_volume_rate = simi_volume_rate[:len(simi_volume_rate)-1]  
        # 计算平均相似度  
        simi = (simi_return+simi_amplitude+simi_volume_rate)/3  
        # 过滤相似度阈值  
        simi = simi[simi>=context.similarity_threshold]  
        if len(simi)>0:  
            # 保留股票  
            if matching_num==1:  
                high_simi_security.append(simi.idxmax())  
            else:  
                high_simi_security = high_simi_security+list((simi.sort_values(ascending=False)[:matching_num]).index)  
    return high_simi_security  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_limit:是否剔除开盘涨停股票，默认为False,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:  
        pass  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         

def on_backtest_finished(context, indicator):  
    print(indicator)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='6eece943-915e-11ec-9103-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 08:00:00',  
        backtest_end_time='2024-08-11 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# 量化复盘
**策略说明：**

策略包括五大模块：大盘、行业/板块、个股、因子和大小盘风格
## -*- coding: utf-8 -*-  
"""  
Created on Mon Oct 21 15:56:10 2024  
  
@author: 掘金量化 戴钟保  
"""  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import os  
import csv  
import datetime  
import numpy as np  
import pandas as pd  
import matplotlib.pyplot as plt  
import statsmodels.api as sm  
import statsmodels.tsa.stattools as sttools  
from statsmodels.tsa.stattools import adfuller  
import warnings  
warnings.filterwarnings("ignore")  
        
plt.rcParams['font.sans-serif']=['SimHei']                            # 用来正常显示中文标签  plt.rcParams['axes.unicode_minus']=False                              # 用来正常显示负号    
        
pd.set_option('display.max_rows',None)                                # 显示所有列 pd.set_option('display.max_columns',None)                             # 显示所有行  
pd.set_option('colheader_justify', 'center')                          # 显示居中还是左边 pd.set_option('display.float_format', lambda x: '%.4f' % x)           # 保留两位小数  
  
  
class QuantReview():  
    def __init__(self,date= None):  
        # 获取当前日期(不是交易日的话，返回最近一个交易日)  
        self.current_date = datetime.datetime.now() if date is None else datetime.datetime.strptime(date+' 22:00:00',"%Y-%m-%d %H:%M:%S")  
        if len(get_trading_dates(exchange='SZSE', start_date=self.current_date, end_date=self.current_date))==0 or self.current_date.hour<18:  
            self.current_date = pd.Timestamp(get_previous_trading_date(exchange='SZSE', date=self.current_date))  
        self.current_date_str = self.current_date.strftime('%Y-%m-%d')  
        # 获取上一个交易日  
        self.last_date = get_previous_trading_date(exchange='SZSE', date=self.current_date)  
        # 获取所有股票  
        self.all_stocks,self.all_stocks_str = self.get_normal_stocks(self.current_date,new_days=0)  
        # 计算当日收益  
        self.stocks_rate = self.get_return()  
        # 数据缓存地址  
        self.local_data_url = r'.\Local_Data'  
        # 检查目录是否存在  
        if not os.path.exists(self.local_data_url):  
            os.makedirs(self.local_data_url)  
        # 其他信息数据  
        # 收盘价（除权）  
        self.close  = history(symbol=self.all_stocks_str, frequency='1d', start_time=self.current_date,  end_time=self.current_date, fields='symbol, close', adjust=ADJUST_NONE, df= True)  
        self.base_data = get_history_instruments(symbols=self.all_stocks, start_date=self.current_date, end_date=self.current_date, df=True)  
        self.base_data = self.base_data.merge(self.close,on='symbol',how='left')  
        self.close.set_index('symbol',inplace=True)  
        # 单季度归母净利润  
        self.quarter_net_profit = None  
        # 单季度ROE及其报告日期  
        self.quarter_ROE = None  
        self.quarter_ROE_end_date = None  
        # 单季度ROA及其报告日期  
        self.quarter_ROA = None  
        self.quarter_ROA_end_date = None  
        # 一个月反转  
        self.inversion_1month = None  
        def plot_distribution(self):  
        """绘制涨幅分布图"""  
        # 计算各区间的股票数量  
        rate_neg10 = len(self.base_data[(self.base_data['close']==self.base_data['lower_limit'])&(self.base_data['sec_level']==1)])# 跌停(不包含ST)  
        rate_10 = len(self.base_data[(self.base_data['close']==self.base_data['upper_limit'])&(self.base_data['sec_level']==1)])# 涨停(不包含ST)  
        is_suspended = len(self.base_data[(self.base_data['is_suspended']==1)])# 停牌  
        rate_neg8t10 = len(self.stocks_rate[self.stocks_rate<=-0.08])# <=-8%  
        rate_neg6t8 = len(self.stocks_rate[(self.stocks_rate<=-0.06)&(self.stocks_rate>-0.08)])# -8%至-6%  
        rate_neg4t6 = len(self.stocks_rate[(self.stocks_rate<=-0.04)&(self.stocks_rate>-0.06)])# -6%至-4%  
        rate_neg2t4 = len(self.stocks_rate[(self.stocks_rate<=-0.02)&(self.stocks_rate>-0.04)])# -4%至-2%  
        rate_neg0t2 = len(self.stocks_rate[(self.stocks_rate<-0.00)&(self.stocks_rate>-0.02)])# -2%至-0%  
        rate_0 = len(self.stocks_rate[(self.stocks_rate==0.00)])# 0%  
        rate_0t2 = len(self.stocks_rate[(self.stocks_rate<=0.02)&(self.stocks_rate>0.00)])# 0%至2%  
        rate_2t4 = len(self.stocks_rate[(self.stocks_rate<=0.04)&(self.stocks_rate>0.02)])# 2%至4%  
        rate_4t6 = len(self.stocks_rate[(self.stocks_rate<=0.06)&(self.stocks_rate>0.04)])# 4%至6%  
        rate_6t8 = len(self.stocks_rate[(self.stocks_rate<=0.08)&(self.stocks_rate>0.06)])# 6%至8%  
        rate_8t10 = len(self.stocks_rate[self.stocks_rate>0.08])# >8%  
        # 汇总统计  
        down_num = rate_neg0t2+rate_neg2t4+rate_neg4t6+rate_neg6t8+rate_neg8t10+rate_neg10# 下跌  
        none_num = rate_0# 平盘  
        up_num = rate_0t2+rate_2t4+rate_4t6+rate_6t8+rate_8t10+rate_10# 上涨  
        # 输出  
        print('当前日期：{}'.format(self.current_date_str),end = '    ')  
        print('赚钱效应：{:0.2f}%'.format(100*up_num/(up_num+down_num)))  
          
        print('下跌：{:4}家'.format(down_num),end = '    ')  
        print('平盘：{:4}家'.format(none_num),end = '    ')  
        print('上涨：{:4}家'.format(up_num))  
          
        print('跌停：{:4}家'.format(rate_neg10),end = '    ')  
        print('停牌：{:4}家'.format(is_suspended),end = '    ')  
        print('涨停：{:4}家'.format(rate_10))  
          
        print('注：1、股票不包含北交所股票；\n    2、涨幅为0的股票包含在(-2%,0%]中；\n    3、部分涨跌停股票可能存在未彻底封板情况。')  
          
        # 绘图  
        plt.figure(figsize=(10,5))  
        data_zf = [rate_neg10,rate_neg8t10,rate_neg6t8,rate_neg4t6,rate_neg2t4,rate_neg0t2,rate_0t2,rate_2t4,rate_4t6,rate_6t8,rate_8t10,rate_10]  
        idx = np.arange(len(data_zf))  
        colors = ['g']*6+['r']*6  
        plt.bar(idx,data_zf,color=colors,zorder=10)  
        for a,b in zip(idx, data_zf):              
            plt.text(a, b+max(data_zf)/100, '%.0f' % b, ha='center', va= 'bottom',fontsize=10)  
        label = ['跌停  ','<=-8%','(-8%,-6%] ','(-6%,-4%] ','(-4%,-2%] ','(-2%,0%) ','(0%,2%]','(2%,4%] ','(4%,6%] ','(6%,8%] ','>8%','涨停  ']  
        plt.xticks(idx,label, rotation=45)  
        plt.title('全市场涨跌分布({})'.format(self.current_date_str))  
        plt.grid(axis='y',zorder=0)  
        plt.show()  
          
          
    def get_return(self):  
        close  = history(symbol=self.all_stocks_str, frequency='1d', start_time=self.last_date,  end_time=self.current_date, fields='symbol, close, eob', adjust=ADJUST_PREV, df= True).set_index(['eob','symbol'])  
        close = close.unstack()  
        close.columns = close.columns.droplevel(level=0)  
        rate = (close/close.shift(1)-1).iloc[-1,:]  
        return rate  
          
      
    def get_normal_stocks(self, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
        """  
        获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
        :param date：目标日期  
        :param new_days:新股上市天数，默认为365天  
        :param skip_suspended:是否剔除停牌股，默认为True  
        :param skip_st:是否剔除ST股，默认为True  
        :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
        """        date = pd.Timestamp(date).replace(tzinfo=None)  
        # A股，剔除停牌和ST股票  
        stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
        if len(stocks_info)>0:  
            stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
            stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
            # 剔除次新股和退市股  
            stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>date)]  
            all_stocks = list(stocks_info['symbol'])  
            # 剔除当日涨停股（收盘价==涨停价）  
            if skip_upper_limit:  
                low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
                stocks_info = stocks_info.merge(low_price,on=['symbol'])  
                all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
        else:  
            all_stocks = []  
        all_stocks_str = ','.join(all_stocks)  
        return all_stocks,all_stocks_str  
  
    def get_N_trading_date(self,date,counts=1,model='previous',exchange='SHSE'):  
        """  
        获取end_date前N个交易日,end_date为datetime格式，不包括date日期  
        :param date：目标日期  
        :param counts：历史回溯天数，默认为1，即前一天，该值需为非零正整数  
        :param model: 模式，默认为 previous，即前N个交易日； next 模式为后N个交易日  
        """        if counts<=0 or not isinstance(counts, int):  
            print('参数类型错误：get_N_trading_date()的counts参数需为非零正整数.')  
            import sys  
            sys.exit(-1)  
        date = pd.Timestamp(date)  
        date_str = date.strftime('%Y-%m-%d')  
        # 判断context.trading_dates是否存在  
        if 'trading_dates' not in {k: v for k, v in self.__dict__.items() if not k.startswith('__')}.keys():  
            self.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=date.year-1, end_year=datetime.datetime.now().year)  
        # 判断当前日期是否为交易日  
        pre_times = 1  
        if date_str not in list(self.trading_dates['trade_date']) and str(date.year) in set([x[:4] for x in self.trading_dates['trade_date']]):# 非交易日  
            date = self.trading_dates[self.trading_dates['date']==date_str]['pre_trade_date'].iloc[0]  
            pre_times = 0  
        date = pd.Timestamp(date)  
        date_str = date.strftime('%Y-%m-%d')  
        # 历史N个交易日  
        for i in range(2):  
            trading_dates_dropna = self.trading_dates.replace('',np.nan).dropna(how='any')  
            if model == 'previous':  
                previous_date_df = trading_dates_dropna[trading_dates_dropna['trade_date']<=date_str]  
                if len(previous_date_df)>=counts+1:  
                    return previous_date_df['trade_date'].iloc[-counts-pre_times]  
                else:  
                    start_date = pd.Timestamp(self.trading_dates['date'].iloc[0])-datetime.timedelta(days = int(365*np.ceil((counts-len(previous_date_df))/250)))  
                    start_year = min(start_date.year-1,date.year)  
                    end_year = int(self.trading_dates['date'].iloc[-1][:4])  
                    self.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_year, end_year=end_year)  
            elif model == 'next':  
                previous_date_df = trading_dates_dropna[trading_dates_dropna['trade_date']>=date_str]  
                if len(previous_date_df)>=counts+1:  
                    return previous_date_df['trade_date'].iloc[counts]  
                else:  
                    end_date = pd.Timestamp(self.trading_dates['date'].iloc[-1])+datetime.timedelta(days = max(int(365*np.ceil((counts-len(previous_date_df))/250)),1))  
                    start_year = int(self.trading_dates['date'].iloc[0][:4])  
                    end_year = max(end_date.year,date.year)  
                    self.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_year, end_year=end_year)  
  
                      
    def history_new(self,security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
        """  
        分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
        :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
        :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
        """        Data = pd.DataFrame()  
        if frequency=='1d':  
            trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
        elif frequency=='tick':  
            trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
        else:  
            trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
        # 计算合理间隔  
        if isinstance(security,str):  
            security = security.split(',')  
        space = 30000//len(security)  
        # 获取数据  
        if len(trading_date)<=space:  
            Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
        else:  
            for n in range(int(np.ceil(len(trading_date)/space))):  
                start = n*space  
                end = start+space  
                if end>=len(trading_date):  
                    if frequency=='1d':  
                        data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                    elif frequency=='tick':  
                        data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                    else:  
                        data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    if frequency=='1d':  
                        data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                    else:  
                        data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                if len(data)==33000:  
                    print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
                Data = pd.concat([Data,data])  
        if df and len(Data)>0:  
            if frequency=='tick':   
                Data.sort_values(['symbol','created_at'],inplace=True)  
                Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
            else:  
                Data.sort_values(['symbol','eob'],inplace=True)  
                Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
            if type:  
                if len(Data)>0:  
                    if frequency=='tick':  
                        Data['created_at'] = Data['created_at'].dt.tz_localize(None)  
                        Data = Data.set_index(['created_at','symbol'])  
                    else:  
                        Data['eob'] = Data['eob'].dt.tz_localize(None)  
                        Data = Data.set_index(['eob','symbol'])  
                    Data = Data.unstack()  
                    Data.columns = Data.columns.droplevel(level=0)  
        return Data  
      
    def get_trading_dates_new(self, exchange, start_date, end_date):  
        """  
        获取两个日期间的交易日  
        """        start_date = pd.Timestamp(start_date)  
        start_date_str = start_date.strftime('%Y-%m-%d')  
        end_date = pd.Timestamp(end_date)  
        end_date_str = end_date.strftime('%Y-%m-%d')  
        # 判断context.trading_dates是否存在  
        if 'trading_dates' not in {k: v for k, v in self.__dict__.items() if not k.startswith('__')}.keys():  
            self.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
        # 判断start_date是否存在于context.trading_dates中  
        if start_date_str not in list(self.trading_dates['date']):  
            self.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=self.trading_dates['date'].iloc[-1][:4])  
        # 判断end_date是否存在于context.trading_dates中  
        if end_date_str not in list(self.trading_dates['date']):  
            self.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=self.trading_dates['date'].iloc[0][:4], end_year=end_date.year)  
        # 计算start_date所在得index位置  
        start_date_index = self.trading_dates[self.trading_dates['date']==start_date_str].index[0]  
        # 计算end_date所在得index位置  
        end_date_index = self.trading_dates[self.trading_dates['date']==end_date_str].index[0]  
        # 计算区间得交易日  
        trading_dates = self.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
        trading_dates = [date for date in trading_dates if date!='']  
        return trading_dates  
  
    def stk_get_daily_basic_pt_new(self, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
        """  
        多日期调用stk_get_daily_basic_pt函数，当有count时就不采用start_date,count为正整数  
        """        if counts!=None:  
            start_date = self.get_N_trading_date(date=end_date,counts=counts,model='previous',exchange='SHSE')  
        date_list = self.get_trading_dates_new(exchange='SZSE', start_date=start_date, end_date=end_date)  
  
        # 循环获取数据  
        df_total = pd.DataFrame()  
        for date in date_list:  
            df_new = stk_get_daily_basic_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
            if df:  
                df_total = pd.concat([df_total,df_new])  
            else:  
                df_total = df_total+df_new  
        return df_total  
      
    def stk_get_daily_valuation_pt_new(self, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
        """  
        多日期调用stk_get_daily_valuation_pt函数，当有count时就不采用start_date,count为正整数  
        """        if counts!=None:  
            start_date = self.get_N_trading_date(date=end_date,counts=counts,model='previous',exchange='SHSE')  
        date_list = self.get_trading_dates_new(exchange='SZSE', start_date=start_date, end_date=end_date)  
  
        # 循环获取数据  
        df_total = pd.DataFrame()  
        for date in date_list:  
            df_new = stk_get_daily_valuation_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
            if df:  
                df_total = pd.concat([df_total,df_new])  
            else:  
                df_total = df_total+df_new  
        return df_total  
      
    def count_zt(self):  
        # 获取最低价  
        low_price  = history(symbol=self.all_stocks_str, frequency='1d', start_time=self.current_date,  end_time=self.current_date, fields='symbol, eob,low,high', adjust=ADJUST_PREV, df= True)  
        base_data = self.base_data.merge(low_price,on='symbol',how='left')  
        # 非一字板涨停数  
        rate_10_df = base_data[(base_data['close']==base_data['upper_limit'])&(self.base_data['sec_level']==1)]  
        rate_10 = len(rate_10_df)  
        rate_10_Noall_time = len(base_data[(base_data['close']==base_data['upper_limit'])&(base_data['low']!=base_data['upper_limit'])&(self.base_data['sec_level']==1)])  
        # 涨停被砸率：涨停被砸/（涨停被砸+涨停）  
        failed_num = len(base_data[(base_data['high']==base_data['upper_limit'])&(self.base_data['sec_level']==1)&(base_data['close']!=base_data['upper_limit'])])# 被砸股票  
        failed_rate_10 = failed_num/(failed_num+rate_10)  
        # 涨停封板率：涨停/（涨停被砸+涨停）  
        succeed_rate_10 = rate_10/(failed_num+rate_10)  
        # 输出  
        print('{}涨停：{:4}家'.format('          ',rate_10),end = '    ')  
        print('非一字涨停：{:4}家'.format(rate_10_Noall_time),end = '    ')  
        print('封板率：{:0.2%}'.format(succeed_rate_10))  
        # 涨停股情况  
        rate_10_name = ','.join(list(rate_10_df['symbol']))  
        df = base_data[base_data['symbol'].isin(list(rate_10_df['symbol']))]  
        # 中文名  
        chinese_name = get_instruments(symbols=rate_10_name, df=True)  
        df = df.merge(chinese_name,on='symbol',how='left')  
        # 成交额  
        amount = history(symbol=rate_10_name, frequency='1d', start_time=self.current_date, end_time=self.current_date, fields='symbol,amount', df=True)  
        df = df.merge(amount,on='symbol',how='left')  
        # 历史收盘价  
        start_time = self.get_N_trading_date(self.current_date,counts=51)  
        history_close = self.history_new(security=rate_10_name,frequency='1d',start_time=start_time,end_time=self.current_date,fields='symbol,close,eob',skip_suspended=True,fill_missing=None,adjust=ADJUST_NONE,df=True)  
        # 历史涨停价  
        history_upper = get_history_instruments(symbols=rate_10_name, fields='symbol,upper_limit,trade_date', start_date=start_time, end_date=self.current_date, df=True)  
        history_upper = history_upper.set_index(['trade_date','symbol'])  
        history_upper = history_upper.unstack()  
        history_upper.columns = history_upper.columns.droplevel(level=0)  
        # 计算连板数  
        df['连板数'] = 0  
        for code in df['symbol']:  
            int_lb = 0  
            for x in range(-1,-len(history_upper),-1):  
                if history_upper[code][x]==history_close[code][x]:  
                    int_lb += 1  
                else:  
                    df.loc[df[df['symbol']==code].index,'连板数'] = int_lb  
                    break  
        # 首板时间  
        df['首板时间'] = 0  
        start_time = datetime.datetime.strptime(self.current_date.strftime('%Y-%m-%d')+' 00:00:00', '%Y-%m-%d %H:%M:%S')  
        end_time = datetime.datetime.strptime(self.current_date.strftime('%Y-%m-%d')+' 23:00:00', '%Y-%m-%d %H:%M:%S')  
        close_minute = self.history_new(security=rate_10_name,frequency='60s',start_time=start_time,end_time=end_time,fields='symbol,close,eob',skip_suspended=True,fill_missing=None,adjust=ADJUST_NONE,df=True)  
        for code in df['symbol']:  
            time_ = close_minute[code].idxmax().strftime('%H:%M')  
            df.loc[df[df['symbol']==code].index,'首板时间'] = time_  
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=rate_10_name, fields='a_mv_ex_ltd', trade_date=self.current_date_str, df=True)  
        df = df.merge(mv,on='symbol',how='left')  
        # 所属行业  
        df['行业'] = None  
        for i in range(len(df)):  
            index = df.index[i]  
            code = df.loc[index,'symbol']  
            ind_name = stk_get_symbol_industry(code, source="sw2021", level=2, date=self.current_date_str)['industry_name'].tolist()  
            ind_name = ','.join(ind_name)  
            df.loc[index,'行业'] = ind_name  
          
        # 输出  
        df.rename(columns={'symbol':'股票代码','sec_name_y':'股票名称','amount':'成交额/亿','a_mv_ex_ltd':'流通市值/亿'},inplace=True)  
        df['成交额/亿'].astype(float)  
        df = df.sort_values(by=['连板数','成交额/亿'],ascending=False)  
        df.index = range(1,len(df)+1)  
        df['成交额/亿'] = df['成交额/亿']*1e-8  
        df['流通市值/亿'] = df['流通市值/亿']*1e-8  
        return df[['股票代码','股票名称','首板时间','连板数','成交额/亿','流通市值/亿','行业']]  
      
    def get_index_the_date_return(self,codes=['SHSE.000001','SZSE.399001','SZSE.399006','SHSE.000016','SHSE.000300','SHSE.000688']):  
        codes_str = security=','.join(codes)  
        history_close = self.history_new(security=codes_str,frequency='1d',start_time=self.last_date,end_time=self.current_date,fields='symbol,close,eob',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,df=True)  
        rate_df = pd.DataFrame(history_close.iloc[-1,:]/history_close.iloc[0,:]-1).reset_index()  
        rate_df.columns = ['symbol','rate']  
        chinese_name = get_instruments(symbols=codes_str, df=True)  
        rate_df = rate_df.merge(chinese_name,on='symbol',how='left').set_index('symbol')  
        rate_df = rate_df.loc[codes,:]  
        # 绘图  
        plt.figure(figsize=(10,5))  
        rate = rate_df['rate']  
        colors = []  
        idx = np.arange(len(rate))  
        for a,b in zip(idx, rate):       
            if b>=0:  
                plt.text(a, b+max(rate)/100, '{:.2%}'.format(b), ha='center', va= 'bottom',fontsize=10)  
                colors = colors+['r']  
            else:  
                plt.text(a, max(rate)/100, '{:.2%}'.format(b), ha='center', va= 'bottom',fontsize=10)  
                colors = colors+['g']  
        plt.bar(idx,rate,zorder=10,color=colors)  
        label = rate_df['sec_name']  
        plt.xticks(idx,label, rotation=45)  
        plt.title('主要指数表现({})'.format(self.current_date_str),pad=15)  
        plt.grid(axis='y',zorder=0)  
        plt.show()  
  
      
    def get_big_small_style(self,start_date='2018-01-01'):  
        codes_str = 'SHSE.000300,SZSE.399905,SZSE.399303'  
        history_close = self.history_new(security=codes_str,frequency='1d',start_time=start_date,end_time=self.current_date,fields='symbol,close,eob',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,df=True)  
        rate_df = (history_close/history_close.shift(1)).cumprod()  
        rate_df.rename(columns={'SHSE.000300':'沪深300','SZSE.399905':'中证500','SZSE.399303':'国证2000'}, inplace = True)   
        rate_df['国证2000/中证500'] = rate_df['国证2000']/rate_df['中证500']  
        rate_df['沪深300/中证500'] = rate_df['沪深300']/rate_df['中证500']  
        # 绘图  
        plt.figure(figsize=(10,5))  
        rate = rate_df[['国证2000/中证500','沪深300/中证500']]  
        plt.plot(rate)  
        plt.legend(['国证2000/中证500','沪深300/中证500'],loc='best')     
        plt.title('国证2000与沪深300相对于中证500指数超额',pad=15)  
        plt.grid(axis='y',zorder=0)  
        plt.show()     
      
    def file_check_and_load_decorator(file_prefix_list):  
        def decorator(func):  
            def wrapper(self, index_code, symbols, date, *args, **kwargs):  
                # 生成文件名  
                i = 0  
                for file_prefix in file_prefix_list:  
                    i += 1  
                    file_name = r"{}\{}_{}_{}.csv".format(self.local_data_url, file_prefix, index_code, date)  
                    if os.path.exists(file_name):  
                        locals()['data'+str(i)] = pd.read_csv(file_name,header=0,index_col=[0])  
                        if len(locals()['data'+str(i)])>=0:  
                            locals()['data'+str(i)] = locals()['data'+str(i)][file_prefix]  
    #                    print(f"从文件 {file_name} 中读取数据")  
                    else:  
                        print(f"文件 {file_name} 不存在，运行函数生成数据")  
                        data = func(self, index_code, symbols, date, *args, **kwargs)  
                        if isinstance(data,tuple):  
                            locals()['data'+str(i)] = data[i-1]  
                        else:  
                            locals()['data'+str(i)] = data  
                        locals()['data'+str(i)].to_csv(file_name, encoding='utf_8_sig')  
                        print(f"数据已保存到文件 {file_name}")  
                if len(file_prefix_list)==1:  
                    return locals()['data1']  
                elif len(file_prefix_list)==2:  
                    return locals()['data1'],locals()['data2']  
            return wrapper  
        return decorator  
  
    @file_check_and_load_decorator(['PB'])  
    def factor_bp(self,index_code,symbols,date):  
        """估值-BP:净资产/总市值"""         
df = stk_get_daily_valuation_pt(symbols=symbols, fields='pb_mrq', trade_date=date, df=True).set_index('symbol')  
        df.rename(columns={'pb_mrq':'PB'},inplace=True)  
        return 1/df['PB']  
      
    @file_check_and_load_decorator(['EP'])  
    def factor_EP(self,index_code,symbols,date):  
        """估值-单季EP:单季度归母净利润/总市值"""  
        df = self.stk_get_finance_prime_pt_single_quarter(symbols=symbols,fields='net_prof_pcom',rpt_type=None, data_type=101, date=date, df=True)  
        df['EP'] = df['net_prof_pcom']/self.total_market['tot_mv']  
        return df['EP']  
      
    @file_check_and_load_decorator(['SP'])  
    def factor_SP(self,index_code,symbols,date):  
        """估值-单季SP:单季度营业收入/总市值"""  
        df = self.stk_get_finance_prime_pt_single_quarter(symbols=symbols,fields='inc_oper',rpt_type=None, data_type=101, date=date, df=True)  
        df['SP'] = df['inc_oper']/self.total_market['tot_mv']  
        return df['SP']  
      
    @file_check_and_load_decorator(['EPTTM'])  
    def factor_EPTTM(self,index_code,symbols,date):  
        """估值-EPTTM:归母净利润 TTM/总市值"""    
df = stk_get_daily_valuation_pt(symbols=symbols, fields='pe_ttm', trade_date=date, df=True).set_index('symbol')  
        df['EPTTM'] = 1/df['pe_ttm']  
        return df['EPTTM']  
      
    @file_check_and_load_decorator(['SPTTM'])  
    def factor_SPTTM(self,index_code,symbols,date):  
        """估值-SPTTM:营业收入 TTM/总市值"""  
        df = stk_get_daily_valuation_pt(symbols=symbols, fields='ps_ttm', trade_date=date, df=True).set_index('symbol')  
        df['SPTTM'] = 1/df['ps_ttm']  
        return df['SPTTM']  
      
    @file_check_and_load_decorator(['EPTTM分位点'])  
    def factor_EPTTM_quantile(self,index_code,symbols,date):  
        """估值-EPTTM 分位点:EPTTM 在过去一年中的分位点"""  
        df = self.stk_get_daily_valuation_pt_new(symbols=symbols, fields='pe_ttm', end_date=date, counts=240, df=True).set_index(['trade_date','symbol'])  
        df = df.unstack().fillna(0)  
        df.columns = df.columns.droplevel(level=0)  
        df = df**(-1)  
        quantile_count = df<df.iloc[-1,:]  
        quantile_count_df = (quantile_count.sum()/240).to_frame()  
        quantile_count_df.columns = ['EPTTM分位点']  
        return quantile_count_df['EPTTM分位点']  
      
    @file_check_and_load_decorator(['dy_ttm'])  
    def factor_dividend_yield(self,index_code,symbols,date):  
        """估值-股息率:最近四个季度预案分红金额/总市值(掘金：滚动12月TTM)"""  
        df = stk_get_daily_valuation_pt(symbols=symbols, fields='dy_ttm', trade_date=date, df=True).set_index('symbol')  
        return df['dy_ttm']  
      
    @file_check_and_load_decorator(['一个月反转'])  
    def factor_inversion_1month(self,index_code,symbols,date):  
        """反转-一个月反转:过去 20 个交易日涨跌幅"""  
        pre_date = self.get_N_trading_date(date,counts=20+1)  
        self.pre_close_1month = history(symbol=symbols, frequency='1d', start_time=pre_date,  end_time=pre_date, fields='symbol, close', adjust=ADJUST_NONE, df= True).set_index('symbol')  
        df = (self.close['close']/self.pre_close_1month['close']-1).to_frame()  
        df.columns = ['一个月反转']  
        return df['一个月反转']  
      
      
    @file_check_and_load_decorator(['三个月反转'])  
    def factor_inversion_3month(self,index_code,symbols,date):  
        """反转-三个月反转:过去 60 个交易日涨跌幅"""  
        pre_date = self.get_N_trading_date(date,counts=60+1)  
        pre_close = history(symbol=symbols, frequency='1d', start_time=pre_date,  end_time=pre_date, fields='symbol, close', adjust=ADJUST_NONE, df= True).set_index('symbol')  
        df = (self.close['close']/pre_close['close']-1).to_frame()  
        df.columns = ['三个月反转']  
        return df['三个月反转']  
      
    @file_check_and_load_decorator(['一年动量'])  
    def factor_momentum_1year(self,index_code,symbols,date):  
        """动量-一年动量:近一年除近一月后动量"""  
        pre_date = self.get_N_trading_date(date,counts=240+1)  
        pre_close_12month = history(symbol=symbols, frequency='1d', start_time=pre_date,  end_time=pre_date, fields='symbol, close', adjust=ADJUST_NONE, df= True).set_index('symbol')  
        df = (self.pre_close_1month['close']/pre_close_12month['close']-1).to_frame()  
        df.columns = ['一年动量']  
        return df['一年动量']  
      
    @file_check_and_load_decorator(['net_prof_pcom'])  
    def factor_net_profit_growth(self,index_code,symbols,date):  
        """成长-单季净利同比增速:单季度净利润同比增长率"""  
        df = self.stk_get_finance_prime_pt_single_quarter_rate(symbols=symbols,fields='net_prof_pcom',rpt_type=None, data_type=101, date=date, df=True)  
        g = df['net_prof_pcom']  
        return g  
      
    @file_check_and_load_decorator(['inc_oper'])  
    def factor_operating_income_growth(self,index_code,symbols,date):  
        """成长-单季营收同比增速:单季度营业收入同比增长率"""  
        df = self.stk_get_finance_prime_pt_single_quarter_rate(symbols=symbols,fields='inc_oper',rpt_type=None, data_type=101, date=date, df=True)  
        return df['inc_oper']  
      
    @file_check_and_load_decorator(['oper_prof'])  
    def factor_operating_profit_growth(self,index_code,symbols,date):  
        """成长-单季营利同比增速:单季度营业利润同比增长率"""  
        df = self.stk_get_finance_prime_pt_single_quarter_rate(symbols=symbols,fields='oper_prof',rpt_type=None, data_type=101, date=date, df=True)  
        return df['oper_prof']  
      
    @file_check_and_load_decorator(['***'])  
    def factor_SUE(self,index_code,symbols,date):  
        """成长-SUE:（单季度实际净利润-预期净利润）/预期净利润标准差    （缺少预期数据）"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_SUR(self,index_code,symbols,date):  
        """成长-SUR:（单季度实际营业收入-预期营业收入）/预期营业收入标准差    （缺少预期数据）"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_net_profit_exprctation(self,index_code,symbols,date):  
        """成长-单季超预期幅度:预期单季度净利润 / 财报单季度净利润    （缺少预期数据）"""  
        pass  
    @file_check_and_load_decorator(['DELTAROE','ROE'])  
    def factor_DELTAROE(self,index_code,symbols,date):  
        """盈利-DELTAROE:单季度净资产收益率-去年同期单季度净资产收益率"""  
        data_type = None  
        # 1、单季度净利润  
        last_npp_data = pd.DataFrame()  
        npp_data = stk_get_finance_prime_pt(symbols=symbols, fields='net_prof_pcom', rpt_type=None, data_type=data_type, date=date, df=True).set_index('symbol')  
        # 将日期字符串转换为日期类型  
        npp_data['rpt_date_datetime'] = pd.to_datetime(npp_data['rpt_date'])  
        # 提取月份  
        npp_data['month'] = npp_data['rpt_date_datetime'].dt.month  
        for rpt_date in set(npp_data['rpt_date']):  
            data_sample = npp_data[npp_data['rpt_date']==rpt_date]  
            month = data_sample['month'].iloc[0]  
            symbols_sample = list(data_sample.index)  
            if month==3:  
                new_rpt_type = 1  
            elif month==6:  
                new_rpt_type = 6  
            elif month==9:  
                new_rpt_type = 9  
            else:  
                new_rpt_type = 12  
            # 获取上一年同季度数据  
            last_data_sample = stk_get_finance_prime_pt(symbols=symbols_sample, fields='net_prof_pcom', rpt_type=new_rpt_type, data_type=data_type, date=rpt_date, df=True).set_index('symbol')  
            last_npp_data = pd.concat([last_npp_data,last_data_sample])  
              
        # 2、单季度净资产  
        last_tep_data = pd.DataFrame()  
        tep_data = stk_get_finance_prime_pt(symbols=symbols, fields='ttl_eqy_pcom', rpt_type=None, data_type=data_type, date=date, df=True).set_index('symbol')  
        # 将日期字符串转换为日期类型  
        tep_data['rpt_date_datetime'] = pd.to_datetime(tep_data['rpt_date'])  
        # 提取月份  
        tep_data['month'] = tep_data['rpt_date_datetime'].dt.month  
        for rpt_date in set(tep_data['rpt_date']):  
            data_sample = tep_data[tep_data['rpt_date']==rpt_date]  
            month = data_sample['month'].iloc[0]  
            symbols_sample = list(data_sample.index)  
            if month==3:  
                new_rpt_type = 1  
            elif month==6:  
                new_rpt_type = 6  
            elif month==9:  
                new_rpt_type = 9  
            else:  
                new_rpt_type = 12  
            # 获取上一年同季度数据  
            last_data_sample = stk_get_finance_prime_pt(symbols=symbols_sample, fields='ttl_eqy_pcom', rpt_type=new_rpt_type, data_type=data_type, date=rpt_date, df=True).set_index('symbol')  
            last_tep_data = pd.concat([last_tep_data,last_data_sample])  
          
        # 3、ROE  
        roe = npp_data['net_prof_pcom']/tep_data['ttl_eqy_pcom']  
        last_roe = last_npp_data['net_prof_pcom']/last_tep_data['ttl_eqy_pcom']  
        deltaroe = roe-last_roe  
          
        deltaroe = deltaroe.to_frame()  
        deltaroe.columns = ['DELTAROE']  
        roe = roe.to_frame()  
        roe.columns = ['ROE']  
        return deltaroe['DELTAROE'],roe['ROE']  
      
    @file_check_and_load_decorator(['DELTAROA','ROA'])  
    def factor_DELTAROA(self,index_code,symbols,date):  
        """盈利-DELTAROA:单季度总资产收益率-去年同期单季度总资产收益率"""  
        data_type = None  
        # 1、单季度净利润  
        last_npp_data = pd.DataFrame()  
        npp_data = stk_get_finance_prime_pt(symbols=symbols, fields='net_prof_pcom', rpt_type=None, data_type=data_type, date=date, df=True).set_index('symbol')  
        # 将日期字符串转换为日期类型  
        npp_data['rpt_date_datetime'] = pd.to_datetime(npp_data['rpt_date'])  
        # 提取月份  
        npp_data['month'] = npp_data['rpt_date_datetime'].dt.month  
        for rpt_date in set(npp_data['rpt_date']):  
            data_sample = npp_data[npp_data['rpt_date']==rpt_date]  
            month = data_sample['month'].iloc[0]  
            symbols_sample = list(data_sample.index)  
            if month==3:  
                new_rpt_type = 1  
            elif month==6:  
                new_rpt_type = 6  
            elif month==9:  
                new_rpt_type = 9  
            else:  
                new_rpt_type = 12  
            # 获取上一年同季度数据  
            last_data_sample = stk_get_finance_prime_pt(symbols=symbols_sample, fields='net_prof_pcom', rpt_type=new_rpt_type, data_type=data_type, date=rpt_date, df=True).set_index('symbol')  
            last_npp_data = pd.concat([last_npp_data,last_data_sample])  
              
        # 2、单季度总资产  
        last_ta_data = pd.DataFrame()  
        ta_data = stk_get_finance_prime_pt(symbols=symbols, fields='ttl_ast', rpt_type=None, data_type=data_type, date=date, df=True).set_index('symbol')  
        # 将日期字符串转换为日期类型  
        ta_data['rpt_date_datetime'] = pd.to_datetime(ta_data['rpt_date'])  
        # 提取月份  
        ta_data['month'] = ta_data['rpt_date_datetime'].dt.month  
        for rpt_date in set(ta_data['rpt_date']):  
            data_sample = ta_data[ta_data['rpt_date']==rpt_date]  
            month = data_sample['month'].iloc[0]  
            symbols_sample = list(data_sample.index)  
            if month==3:  
                new_rpt_type = 1  
            elif month==6:  
                new_rpt_type = 6  
            elif month==9:  
                new_rpt_type = 9  
            else:  
                new_rpt_type = 12  
            # 获取上一年同季度数据  
            last_data_sample = stk_get_finance_prime_pt(symbols=symbols_sample, fields='ttl_ast', rpt_type=new_rpt_type, data_type=data_type, date=rpt_date, df=True).set_index('symbol')  
            last_ta_data = pd.concat([last_ta_data,last_data_sample])  
          
        # 3、ROA  
        roa = npp_data['net_prof_pcom']/ta_data['ttl_ast']  
        last_roa = last_npp_data['net_prof_pcom']/last_ta_data['ttl_ast']  
        deltaroa = roa-last_roa  
          
        deltaroa = deltaroa.to_frame()  
        deltaroa.columns = ['DELTAROA']  
        roa = roa.to_frame()  
        roa.columns = ['ROA']  
        return deltaroa['DELTAROA'],roa['ROA']  
      
    @file_check_and_load_decorator(['非流动性冲击'])  
    def factor_not_liquidity_shock(self,index_code,symbols,date):  
        """流动性-非流动性冲击:过去 20 个交易日的日涨跌幅绝对值/成交额的均值"""  
        pre_date = self.get_N_trading_date(date,counts=20+1)   
        # 日涨跌幅绝对值  
        his_close = self.history_new(security=symbols,frequency='1d',start_time=pre_date,end_time=date,fields='symbol, close, eob',adjust=ADJUST_PREV,df=True)  
        his_rate = abs((his_close/his_close.shift(1)-1).iloc[1:,:])  
        # 日成交额  
        his_amount = self.history_new(security=symbols,frequency='1d',start_time=pre_date,end_time=date,fields='symbol, eob, amount',adjust=ADJUST_PREV,df=True)  
        his_amount = his_amount.iloc[1:,:]  
        # 涨跌幅绝对值/成交额 的均值  
        data = (his_close/his_amount).mean()  
        data = data.to_frame()  
        data.columns = ['非流动性冲击']  
        return data['非流动性冲击']  
      
    @file_check_and_load_decorator(['一个月换手率','三个月换手率'])  
    def factor_turnover_rate(self,index_code,symbols,date):  
        """流动性-换手率:过去 60（20） 个交易日换手率均值"""  
        df = self.stk_get_daily_basic_pt_new(symbols=symbols, fields='turnrate', end_date=date, counts=60, df=True).set_index(['trade_date','symbol'])  
        df = df.unstack()  
        df.columns = df.columns.droplevel(level=0)  
        # 1个月换手率  
        turnover_rate_1month = df.iloc[-20:,:].mean()  
        # 3个月换手率  
        turnover_rate_3month = df.mean()       
        turnover_rate_1month = turnover_rate_1month.to_frame()  
        turnover_rate_1month.columns = ['一个月换手率']  
        turnover_rate_3month = turnover_rate_3month.to_frame()  
        turnover_rate_3month.columns = ['三个月换手率']  
        return turnover_rate_1month['一个月换手率'],turnover_rate_3month['三个月换手率']    
      
    @file_check_and_load_decorator(['特异度'])  
    def factor_specificity(self,index_code,symbols,date):  
        """波动-特异度:1-过去 20 个交易日 Fama-French 三因子回归的拟合度"""  
  
        # 数据滑窗  
        date_num = 20  
  
        # 账面市值比的大/中/小分类  
        BM_HIGH = 3.0  
        BM_MIDDLE = 2.0  
        BM_LOW = 1.0  
  
        # 市值大/小分类  
        MV_BIG = 2.0  
        MV_SMALL = 1.0  
  
        # 获取P/B和市值数据  
        fin = stk_get_daily_mktvalue_pt(symbols=symbols, fields='tot_mv', trade_date=date, df=True).sort_values(by='tot_mv')  
        # 净资产  
        ttl_eqy = stk_get_fundamentals_balance_pt(symbols=symbols, date=date, fields='ttl_eqy', df=True)  
        ttl_eqy['max_rpt_date'] = ttl_eqy.groupby(['symbol'])['rpt_date'].transform(max) == ttl_eqy['rpt_date']  
        ttl_eqy = ttl_eqy[ttl_eqy['max_rpt_date'] == True]  
        # 计算PB  
        fin = fin.merge(ttl_eqy,on=['symbol'],how='left')  
        fin['PB'] = fin['tot_mv']/fin['ttl_eqy']      
  
        # 计算账面市值比,为P/B的倒数  
        fin.loc[:,'PB'] = (fin['PB'] ** -1)  
  
        # 计算市值的50%的分位点,用于后面的分类  
        size_gate = fin['tot_mv'].quantile(0.50)  
  
        # 计算账面市值比的30%和70%分位点,用于后面的分类  
        bm_gate = [fin['PB'].quantile(0.30), fin['PB'].quantile(0.70)]  
        fin.index = fin.symbol  
  
        # 设置存放股票收益率的变量  
        data_df = pd.DataFrame()  
        if isinstance(symbols,str):  
            symbols_list = symbols.split(',')  
        elif isinstance(symbols,list):  
            symbols_list = symbols  
        # 对未停牌的股票进行处理  
        for symbol in symbols_list:  
            # 计算收益率  
            if self.inversion_1month is None:  
                close = history_n(symbol=symbol, frequency='1d', count=date_num + 1, end_time=date, fields='close',  
                                  skip_suspended=True, fill_missing='Last', adjust=ADJUST_PREV, df=True)['close'].values  
                stock_return = close[-1] / close[0] - 1  
            else:  
                stock_return = self.inversion_1month[symbol]  
  
            pb = fin['PB'][symbol]  
            market_value = fin['tot_mv'][symbol]  
  
            # 获取[股票代码， 股票收益率, 账面市值比的分类, 市值的分类, 市值]  
            # 其中账面市值比的分类为：高（3）、中（2）、低（1）  
            # 市值的分类：大（2）、小（1）  
            if pb < bm_gate[0]:  
                if market_value < size_gate:  
                    label = [symbol, stock_return, BM_LOW, MV_SMALL, market_value]# 小市值/低BM                  
else:  
                    label = [symbol, stock_return, BM_LOW, MV_BIG, market_value]# 大市值/低BM  
            elif pb < bm_gate[1]:  
                if market_value < size_gate:  
                    label = [symbol, stock_return, BM_MIDDLE, MV_SMALL, market_value]# 小市值/中BM  
                else:  
                    label = [symbol, stock_return, BM_MIDDLE, MV_BIG, market_value]# 大市值/中BM  
            elif market_value < size_gate:  
                label = [symbol, stock_return, BM_HIGH, MV_SMALL, market_value]# 小市值/高BM  
            else:  
                label = [symbol, stock_return, BM_HIGH, MV_BIG, market_value]# 大市值/高BM  
            data_df = pd.concat([data_df,pd.DataFrame(label,index=['symbol', 'return', 'BM', 'TOTMKTCAP', 'mv']).T])  
        data_df.set_index('symbol',inplace=True)  
  
        # 调整数据类型  
        for column in data_df.columns:  
            data_df[column] = data_df[column].astype(np.float64)  
  
        # 计算小市值组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        smb_s = (self.market_value_weighted(data_df, MV_SMALL, BM_LOW) +  
                 self.market_value_weighted(data_df, MV_SMALL, BM_MIDDLE) +  
                 self.market_value_weighted(data_df, MV_SMALL, BM_HIGH)) / 3  
        # 计算大市值组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        smb_b = (self.market_value_weighted(data_df, MV_BIG, BM_LOW) +  
                 self.market_value_weighted(data_df, MV_BIG, BM_MIDDLE) +  
                 self.market_value_weighted(data_df, MV_BIG, BM_HIGH)) / 3  
        # 计算规模因子的收益率（小市值组收益率-大市值组收益率）  
        smb = smb_s - smb_b  
  
        # 计算高BM组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        hml_b = (self.market_value_weighted(data_df, MV_SMALL, BM_HIGH) +  
                 self.market_value_weighted(data_df, MV_BIG, BM_HIGH)) / 2  
        # 计算低BM组合的收益率（组内以市值加权计算收益率，组间以等权计算收益率）  
        hml_s = (self.market_value_weighted(data_df, MV_SMALL, BM_LOW) +  
                 self.market_value_weighted(data_df, MV_BIG, BM_LOW)) / 2  
        # 计算价值因子的收益率（高BM组收益率-低BM市值组收益率）  
        hml = hml_b - hml_s  
  
        # 获取市场收益率  
        close = history_n(symbol='SHSE.000001', frequency='1d', count=date_num + 1,  
                          end_time=date, fields='close', skip_suspended=True,  
                          fill_missing='Last', adjust=ADJUST_PREV, df=True)['close'].values  
        market_return = close[-1] / close[0] - 1  
        coff_pool = []  
  
        # 对每只股票进行回归获取其alpha值  
        for stock in data_df.index:  
            x_value = np.array([[market_return], [smb], [hml], [1.0]])  
            y_value = np.array([data_df['return'][stock]])  
            # OLS估计系数  
            coff = np.linalg.lstsq(x_value.T, y_value, rcond=None)[0][3]  
            coff_pool.append(coff)  
               
        data_df.loc[:,'alpha'] = coff_pool  
        data_df.rename(columns={'alpha':'特异度'},inplace=True)  
        return data_df['特异度']  
      
    @file_check_and_load_decorator(['一个月volatility','三个月volatility'])  
    def factor_volatility(self,index_code,symbols,date):  
        """波动-volatility:过去 60（20） 个交易日日内真实波幅均值  
        （真实波幅是ATR，但日内真实波幅就不知道什么东西了...  
        参考国信证券的其他金工研报《如何优选核心资产？》，将波动定义为过去N日的日收益率标准差"""  
        pre_date = self.get_N_trading_date(date,counts=60+1)   
        # 日涨跌幅  
        his_close = self.history_new(security=symbols,frequency='1d',start_time=pre_date,end_time=date,fields='symbol, close, eob',adjust=ADJUST_PREV,df=True)  
        his_rate = (his_close/his_close.shift(1)-1).iloc[1:,:]  
        # 20日波动  
        volatility_20d = his_rate.iloc[-20:,:].std()  
        # 60日波动  
        volatility_60d = his_rate.std()  
        volatility_20d = volatility_20d.to_frame()  
        volatility_20d.columns = ['一个月volatility']  
        volatility_60d = volatility_60d.to_frame()  
        volatility_60d.columns = ['三个月volatility']  
        return volatility_20d['一个月volatility'],volatility_60d['三个月volatility']  
              
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """公司治理-高管薪酬:前三高管报酬总额取对数"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """分析师-预期 EPTTM:一致预期滚动 EP"""        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """分析师-预期 BP:前三高管报酬总额取对数"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """分析师-预期 PEG:前三高管报酬总额取对数"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """分析师-预期净利润环比:前三高管报酬总额取对数"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """分析师-三个月盈利上下调:前三高管报酬总额取对数"""  
        pass  
    @file_check_and_load_decorator(['***'])  
    def factor_(self,index_code,symbols,date):  
        """分析师-三个月机构覆盖:前三高管报酬总额取对数"""  
        pass  
    def market_value_weighted(self,df, MV, BM):  
        """  
        计算市值加权下的收益率  
        :param MV：MV为市值的分类对应的组别  
        :param BM：BM账目市值比的分类对应的组别  
        """        select = df[(df['TOTMKTCAP'] == MV) & (df['BM'] == BM)] # 选出市值为MV，账目市值比为BM的所有股票数据  
        mv_weighted = select['mv']/np.sum(select['mv'])# 市值加权的权重  
        return_weighted = select['return']*mv_weighted# 市值加权下的收益率  
        return np.sum(return_weighted)  
      
    def baseData_total_market(self,symbols,date):  
        """总市值"""  
        total_market = stk_get_daily_mktvalue_pt(symbols=index_stocks, fields='tot_mv', trade_date=date, df=True).set_index('symbol')  
        return total_market[['tot_mv']]  
      
    def baseData_owners_net_profit(self,symbols,date):  
        """归母净利润"""  
        owners_net_profit = stk_get_finance_prime_pt(symbols=symbols, fields='net_prof_pcom', rpt_type=None, data_type=None, date=date, df=True).set_index('symbol')  
        return owners_net_profit  
      
    def baseData_net_profit(self,symbols,date):  
        """净利润"""  
        net_profit = stk_get_fundamentals_income_pt(symbols=symbols, rpt_type=None, data_type=None, date=date, fields='net_prof', df=True).set_index('symbol')  
        return net_profit  
      
    def line_predict_annual_data(self,data,field_name,date_name):  
        """线性预估年度值（如一季度100W净利润，则全年预估400W净利润）"""  
        if isinstance(data[date_name][0],datetime.datetime):  
            data['date'] = data[date_name].apply(lambda x:x.strftime('%m-%d'))  
        data['times'] = np.nan  
        for i in range(len(data)):  
            index = data.index[i]  
            date = data['date'][i]  
            if date=='03-31':  
                data.loc[index,'times'] = 4  
            elif date=='06-30':  
                data.loc[index,'times'] = 2  
            elif date=='09-30':  
                data.loc[index,'times'] = 4/3  
            elif date=='12-31':  
                data.loc[index,'times'] = 1  
        data['new_'+field_name] = data[field_name]*data['times']  
        return data    
      
    def stk_get_finance_prime_pt_single_quarter(self,symbols,fields,rpt_type, data_type, date, df):  
        """财务主要指标-单季度数据"""  
        data = stk_get_finance_prime_pt(symbols=symbols, fields=fields, rpt_type=rpt_type, data_type=data_type, date=date, df=df).set_index('symbol')  
          
        # 将日期字符串转换为日期类型  
        data['rpt_date_datetime'] = pd.to_datetime(data['rpt_date'])  
        # 提取月份  
        data['month'] = data['rpt_date_datetime'].dt.month  
        data_single_quarter = pd.DataFrame()  
        for rpt_date in set(data['rpt_date']):  
            data_sample = data[data['rpt_date']==rpt_date]  
            month = data_sample['month'].iloc[0]  
            if month==3:  
                data_single_quarter = pd.concat([data_single_quarter,data_sample])  
            else:  
                symbols_sample = list(data_sample.index)  
                data_pre_rpt = stk_get_finance_prime_pt(symbols=symbols_sample, fields=fields, rpt_type=rpt_type,   
                                                      data_type=data_type, date=rpt_date, df=df).set_index('symbol')  
                data_sample.loc[:,fields] = data_sample.loc[:,fields]-data_pre_rpt.loc[:,fields]  
                data_single_quarter = pd.concat([data_single_quarter,data_sample])  
        return data_single_quarter[[fields]]  
  
    def stk_get_finance_prime_pt_single_quarter_rate(self,symbols,fields,rpt_type, data_type, date, df):  
        """财务主要指标-单季度同比"""  
        data = stk_get_finance_prime_pt(symbols=symbols, fields=fields, rpt_type=rpt_type, data_type=data_type, date=date, df=df).set_index('symbol')  
          
        # 将日期字符串转换为日期类型  
        data['rpt_date_datetime'] = pd.to_datetime(data['rpt_date'])  
        # 提取月份  
        data['month'] = data['rpt_date_datetime'].dt.month  
        data_single_quarter = pd.DataFrame()  
        for rpt_date in set(data['rpt_date']):  
            data_sample = data[data['rpt_date']==rpt_date]  
            month = data_sample['month'].iloc[0]  
            symbols_sample = list(data_sample.index)  
            if month==3:  
                new_rpt_type = 1  
            elif month==6:  
                new_rpt_type = 6  
            elif month==9:  
                new_rpt_type = 9  
            else:  
                new_rpt_type = 12  
            # 获取上一年同季度数据  
            last_data_sample = stk_get_finance_prime_pt(symbols=symbols_sample, fields=fields, rpt_type=new_rpt_type, data_type=data_type, date=rpt_date, df=df).set_index('symbol')  
            data_sample.loc[:,fields] = data_sample.loc[:,fields]/last_data_sample.loc[:,fields]-1  
            data_single_quarter = pd.concat([data_single_quarter,data_sample])  
        return data_single_quarter[[fields]]  
      
    def cal_factor_return(self,data,start_date,end_date):  
        """计算因子收益率，data为股票因子值数据"""  
        stocks = ','.join(list(data.index))  
        close_price = self.history_new(security=stocks,frequency='1d',start_time=start_date,end_time=end_date,fields='symbol, eob, close',adjust=ADJUST_PREV,df=True)  
        per_rate_stock = (close_price/close_price.shift(1)-1).iloc[1:,:]  
        per_rate_factor = per_rate_stock.mean(axis=1)  
        return per_rate_factor  
      
    def get_last_day_of_month(self, year, month):  
        # 获取下个月的第一天  
        if month == 12:  
            next_month = datetime.date(year + 1, 1, 1)  
        else:  
            next_month = datetime.date(year, month + 1, 1)  
          
        # 下个月的第一天的前一天就是当月的最后一天  
        last_day = next_month - datetime.timedelta(days=1)  
        return last_day  
  
    def GuoXin_multiple_factor(self,index_code='SHSE.000300',index_name='沪深300',stocks_num=30):  
        """  
        计算国信金工因子  
        :param index_code: 指数代码  
        :param index_name: 指数名称  
        :param stocks_num: 持股数量       
        注：该多因子模型基于国信证券于2022-02-03发布的数量化投资周报《多因子选股周报》的因子组合;各因子的收益基于该因子方向多头30只股票的等权收益(研报中采用 MFE 组合构建方式，方法较为复杂，有待后期进一步研究），月末换仓，共有七大类因子：估值、反转、动量、成长、盈利、流动性、波动，由于缺少分析师等数据，公司治理和分析师大类因子、成长大类因子中的SUE、SUR和单季超预期幅度因子暂时空缺。  
        """        # 获取交易日历  
        year = int(self.current_date_str[:4])  
        dates = get_trading_dates_by_year(exchange='SHSE', start_year=year, end_year=year)['trade_date']  
        days_this_month = sum(1 for date in dates if self.current_date_str[:7] in date and date<=self.current_date_str)   
        date_months = np.sort(list(set([date[:7] for date in dates if len(date)>0 and date<=self.current_date_str])))  
        # 计算全部因子值  
        Factor_Return = pd.DataFrame()  
        for date_month in date_months:  
            # 当月第一天  
            first_date_month = date_month+'-01'  
            # 当月最后一天  
            end_date_month = self.get_last_day_of_month(int(date_month[:4]), int(date_month[5:7])) if date_month!=self.current_date_str[:7] else self.current_date_str  
            # 上个月最后一个交易日  
            end_date_last_month = get_previous_trading_date(exchange='SZSE', date=first_date_month)  
              
            # 获取成分股  
            index_stocks = list(get_history_constituents(index=index_code, start_date=end_date_last_month,end_date=end_date_last_month)[0]['constituents'].keys())  
            # 获取市值  
            self.total_market = stk_get_daily_mktvalue_pt(symbols=index_stocks, fields='tot_mv', trade_date=end_date_last_month, df=True).set_index('symbol')  
            # 计算因子值  
            factor_return = pd.DataFrame()  
            # 估值：BP(正向)  
            series = self.factor_bp(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'BP',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='BP')],axis=1)  
            # 估值：单季EP(正向)  
            series = self.factor_EP(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'单季EP',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='单季EP')],axis=1)  
            # 估值：单季SP(正向)  
            series = self.factor_SP(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'单季SP',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='单季SP')],axis=1)  
            # 估值：EPTTM(正向)  
            series = self.factor_EPTTM(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'EPTTM',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='EPTTM')],axis=1)  
            # 估值：SPTTM(正向)  
            series = self.factor_SPTTM(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'SPTTM',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='SPTTM')],axis=1)  
            # 估值：EPTTM分位点(正向)  
            series = self.factor_EPTTM_quantile(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'EPTTM分位点',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='EPTTM分位点')],axis=1)  
            # 估值：股息率(正向)  
            series = self.factor_dividend_yield(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'股息率',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='股息率')],axis=1)  
            # 反转：一个月反转(反向)  
            series = self.factor_inversion_1month(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=True)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'一个月反转',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='一个月反转')],axis=1)  
            # 反转：三个月反转(反向)  
            series = self.factor_inversion_3month(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=True)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'三个月反转',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='三个月反转')],axis=1)  
            # 动量：一年动量(正向)  
            series = self.factor_momentum_1year(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'一年动量',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='一年动量')],axis=1)  
            # 成长：单季净利同比增速(正向)  
            series = self.factor_net_profit_growth(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'单季净利同比增速',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='单季净利同比增速')],axis=1)  
            # 成长：单季营收同比增速(正向)  
            series = self.factor_operating_income_growth(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'单季营收同比增速',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='单季营收同比增速')],axis=1)  
            # 盈利：单季 ROE(正向)、DELTAROE(正向)  
            series_deltaroe,series_roe = self.factor_DELTAROE(index_code=index_code,symbols=index_stocks,date=end_date_last_month)  
            series_deltaroe = series_deltaroe.sort_values(ascending=False)  
            series_roe = series_roe.sort_values(ascending=False)  
            series_roe = series_roe[series_roe==series_roe]  
            if len(series_roe)<=stocks_num:  
                series_roe = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'单季 ROE',stocks_num))  
            series_deltaroe = series_deltaroe[series_deltaroe==series_deltaroe]  
            if len(series_deltaroe)<=stocks_num:  
                series_deltaroe = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'DELTAROE',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_roe[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='单季 ROE')],axis=1)  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_deltaroe[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='DELTAROE')],axis=1)  
            # 盈利：单季 ROA(正向)、DELTAROA(正向)  
            series_deltaroa,series_roa = self.factor_DELTAROA(index_code=index_code,symbols=index_stocks,date=end_date_last_month)  
            series_deltaroa = series_deltaroa.sort_values(ascending=False)  
            series_roa = series_roa.sort_values(ascending=False)  
            series_roa = series_roa[series_roa==series_roa]  
            if len(series_roa)<=stocks_num:  
                series_roa = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'单季 ROA',stocks_num))  
            series_deltaroa = series_deltaroa[series_deltaroa==series_deltaroa]  
            if len(series_deltaroa)<=stocks_num:  
                series_deltaroa = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'DELTAROA',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_roa[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='单季 ROA')],axis=1)  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_deltaroa[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='DELTAROA')],axis=1)  
            # 流动性：非流动性冲击(正向)  
            series = self.factor_not_liquidity_shock(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=False)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'非流动性冲击',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='非流动性冲击')],axis=1)  
            # 流动性：一个月换手(反向)、三个月换手(反向)  
            series_1month,series_3month = self.factor_turnover_rate(index_code=index_code,symbols=index_stocks,date=end_date_last_month)  
            series_1month = series_1month.sort_values(ascending=True)  
            series_3month = series_3month.sort_values(ascending=True)  
            series_1month = series_1month[series_1month==series_1month]  
            if len(series_1month)<=stocks_num:  
                series_1month = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'一个月换手',stocks_num))  
            series_3month = series_3month[series_3month==series_3month]  
            if len(series_3month)<=stocks_num:  
                series_3month = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'三个月换手',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_1month[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='一个月换手')],axis=1)  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_3month[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='三个月换手')],axis=1)  
            # 波动：特异度(反向)  
            series = self.factor_specificity(index_code=index_code,symbols=index_stocks,date=end_date_last_month).sort_values(ascending=True)  
            series = series[series==series]  
            if len(series)<=stocks_num:  
                series = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'特异度',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='特异度')],axis=1)  
            # 波动：一个月波动(反向)、三个月波动(反向)  
            series_1month,series_3month = self.factor_volatility(index_code=index_code,symbols=index_stocks,date=end_date_last_month)  
            series_1month = series_1month.sort_values(ascending=True)  
            series_3month = series_3month.sort_values(ascending=True)  
            series_1month = series_1month[series_1month==series_1month]  
            if len(series_1month)<=stocks_num:  
                series_1month = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'一个月波动',stocks_num))  
            series_3month = series_3month[series_3month==series_3month]  
            if len(series_3month)<=stocks_num:  
                series_3month = pd.Series()  
                print('{}本期{}有效数据量<={}个，不纳入计算'.format(end_date_last_month,'三个月波动',stocks_num))  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_1month[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='一个月波动')],axis=1)  
            factor_return = pd.concat([factor_return,self.cal_factor_return(series_3month[:stocks_num],start_date=end_date_last_month,end_date=end_date_month).to_frame(name='三个月波动')],axis=1)  
              
            # 汇总  
            Factor_Return = pd.concat([Factor_Return,factor_return])  
        # 汇总  
        Factor_Return.index = [x.strftime('%Y-%m-%d') for x in Factor_Return.index]  
        df = Factor_Return.iloc[-1,:].to_frame('当天收益/%')  
        df = pd.concat([df,((Factor_Return+1).iloc[-days_this_month:,:].cumprod()-1).iloc[-1,:].to_frame('本月收益/%')],axis=1)  
        df = pd.concat([df,((Factor_Return+1).cumprod()-1).iloc[-1,:].to_frame('本年收益/%')],axis=1)  
        df[['当天收益/%','本月收益/%','本年收益/%']] = df[['当天收益/%','本月收益/%','本年收益/%']]*100  
        df.reset_index(inplace=True)  
        df.rename(columns={'index':'因子名称'},inplace=True)  
        ## 因子方向  
        factor_direct = pd.DataFrame({'BP':'正向','单季EP':'正向','单季SP':'正向','EPTTM':'正向','SPTTM':'正向','EPTTM分位点':'正向',  
                                      '股息率':'正向','一个月反转':'反向','三个月反转':'反向','一年动量':'正向','单季净利同比增速':'正向',  
                                      '单季营收同比增速':'正向','单季营利同比增速':'正向','DELTAROE':'正向','单季 ROE':'正向',  
                                      'DELTAROA':'正向','单季 ROA':'正向','非流动性冲击':'反向','一个月换手':'反向','三个月换手':'反向',  
                                      '特异度':'反向','一个月波动':'反向','三个月波动':'反向',},index=['因子方向']).T.reset_index()  
        factor_direct.rename(columns={'index':'因子名称'},inplace=True)  
        df = df.merge(factor_direct,on=['因子名称'],how='left')  
        ## 所属大类因子名称  
        factor_direct = pd.DataFrame({'BP':'估值','单季EP':'估值','单季SP':'估值','EPTTM':'估值','SPTTM':'估值', 'EPTTM分位点':'估值',  
                                      '股息率':'估值','一个月反转':'反转','三个月反转':'反转','一年动量':'动量','单季净利同比增速':'成长',  
                                      '单季营收同比增速':'成长','单季营利同比增速':'成长','DELTAROE':'盈利','单季 ROE':'盈利',  
                                      'DELTAROA':'盈利','单季 ROA':'盈利','非流动性冲击':'流动性','一个月换手':'流动性','三个月换手':'流动性',  
                                      '特异度':'波动','一个月波动':'波动','三个月波动':'波动',},index=['因子类型']).T.reset_index()  
        factor_direct.rename(columns={'index':'因子名称'},inplace=True)  
        df = df.merge(factor_direct,on=['因子名称'],how='left')  
        df = df.sort_values(by='本年收益/%',ascending=False)  
        df.index = range(1,len(df)+1)  
        print('\n*************** {} {} 中各因子收益统计表 ***************'.format(self.current_date_str,index_name))  
        return df[['因子名称','因子类型','因子方向','当天收益/%','本月收益/%','本年收益/%']]


# 出水芙蓉形态策略
**策略说明：**

**形态的定义（通达信）：**

A赋值：收阳线

B赋值：A并且收盘价>收盘价的S日简单移动平均并且收盘价>收盘价的M日简单移动平均并且收盘价>收盘价的LL日简单移动平均

CC赋值：B并且开盘价<收盘价的M日简单移动平均并且开盘价<收盘价的LL日简单移动平均

输出CSFRO：CC并且(收盘价-开盘价)>0.0618*收盘价

该形态中常用的参数为：S为20，M为40，LL为60，最大不超过60。

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import pandas as pd  
import numpy as np  
import warnings  
warnings.filterwarnings("ignore")  
import statsmodels.api as sm  
  
def init(context):  
    # 策略参数  
    context.S = 20  
    context.M = 40  
    context.LL = 60  
    # 获取目标指数的成分股，中证1000：'SHSE.000852'，中证500：'SHSE.000905'，沪深300：'SHSE.000300'  
    context.index = 'SHSE.000852'  
    # 交易参数  
    context.fundamentals = pd.DataFrame()  
    context.holding_num = 20# 最大持股数量  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:30:00')  
  
  
def before_market(context):  
    context.fundamentals = pd.DataFrame()  
    # 所有持仓  
    Account_positions = context.account().positions()  
    holding_stocks = [posi['symbol'] for posi in Account_positions]  
    context.today = context.now.strftime('%Y-%m-%d')  
    if len(holding_stocks)<context.holding_num:  
        # 获取交易日  
        last_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=1)[0]# 上一交易日  
        # 获取目标指数的成分股  
        stocks = stk_get_index_constituents(index=context.index, trade_date=last_date)['symbol'].tolist()  
        stocks_str = ','.join(stocks)  
        # stocks,stocks_str = get_normal_stocks(last_date)  
  
        # 条件1：计算出水芙蓉形态  
        stocks__WATER_LOTUS = cal_STK_FORM_WATER_LOTUS(stocks_str,last_date,ma1=context.S,ma2=context.M,ma3=context.LL)  
        if len(stocks__WATER_LOTUS)==0:return  
  
        # 条件2：不严重缩量  
        to_buy_new = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=last_date, n=100)  
        all_data = history_new(security=stocks__WATER_LOTUS,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,low,volume,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in stocks__WATER_LOTUS:  
            the_all_data = all_data[all_data['symbol']==symbol]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            if volume_data.iloc[-1] / max(volume_data[:-1])>1.2:  
                to_buy_new.append(symbol)  
        to_buy = to_buy_new  
        if len(to_buy)==0:return  
  
        # 条件3：数量过多时，以市值从小到大买入  
        # to_buy = stocks__WATER_LOTUS  
        if len(to_buy)==0:  
            pass  
        else:  
            # 获取所有股票市值,并按升序排序  
            fundamental = stk_get_daily_mktvalue_pt(symbols=to_buy, fields='tot_mv', trade_date=last_date, df=True)  
            context.fundamentals = fundamental[fundamental['symbol'].isin(to_buy)].sort_values(by='tot_mv')  
            # 获取前N只股票  
            if len(context.fundamentals)==0:  
                context.fundamentals = pd.DataFrame()  
            else:  
                context.fundamentals = context.fundamentals.sort_values('tot_mv')  
  
  
def algo_1(context):  
    # 获取持仓  
    positions = context.account().positions()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    # 平不在标的池的股票  
    for position in positions:  
        symbol = position['symbol']  
        # 历史数据价格  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=60)# 上一交易日  
        start_date = date_list[0]  
        end_date = date_list[-1]  
        his_close = history_new(symbol,frequency='1d',start_time=start_date,end_time=end_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=end_date, df=True, type=False)  
        ma20 = his_close['close'].iloc[-20:].mean()  
        ma40 = his_close['close'].iloc[-40:].mean()  
        ma60 = his_close['close'].iloc[-60:].mean()   
        open_price = history(symbol=symbol, frequency='1d', start_time=context.today,  end_time=context.today, adjust=ADJUST_NONE, adjust_end_time=context.today, df= True)  
        # 价格低于MA20、MA40、MA60任一均线便出场  
        if len(open_price)>0 and (open_price['open'].iloc[0]<ma20 or open_price['open'].iloc[0]<ma40 or open_price['open'].iloc[0]<ma60):  
            lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.today, skip_st=False)  
            new_price = open_price['open'].iloc[0]  
            if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=new_price:  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
                print('{}：{} MA均价止盈止损'.format(context.now,symbol))  
            else:  
                print('{}:{}数据跌停或停牌导致未能卖出！'.format(context.now,symbol))  
              
    # 买在标的池中的股票  
    # 所有持仓  
    positions = get_position()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    if len(holding_stocks)<context.holding_num and len(context.fundamentals)>0:  
        context.fundamentals = context.fundamentals.iloc[:(context.holding_num-len(holding_stocks)),:]  
        print('{}:买入标的：{}'.format(context.now,list(context.fundamentals['symbol'])))  
        for n in range(len(context.fundamentals)):  
            symbol = context.fundamentals['symbol'].iloc[n]  
            if len(holding_stocks)<context.holding_num:  
                open_price = history(symbol=symbol, frequency='1d', start_time=context.today,  end_time=context.today, adjust=ADJUST_NONE, df= True)  
                upper_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
                if len(upper_limit)>0 and len(open_price)>0 and upper_limit[0]['upper_limit']!=open_price['open'].iloc[0]:  
                    new_price = open_price['open'].iloc[0]  
                    nav = context.account().cash['nav']  
                    trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/context.holding_num,price=new_price)  
                    order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
  
   
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def cal_STK_FORM_WATER_LOTUS(stocks,end_date,ma1=20,ma2=40,ma3=60):  
    """  
    形态因子-出水芙蓉  
    A:=CLOSE>OPEN;    B:=A&&CLOSE>MA(CLOSE,20)&&CLOSE>MA(CLOSE,40)&&CLOSE>MA(CLOSE,60);    CC:=B&&OPEN<MA(CLOSE,40)&&OPEN<MA(CLOSE,60);    CSFRO:CC&&(CLOSE-OPEN)>0.0618*CLOSE;    """    # 相关参数  
    csfro_coeff = 0.0618  
    factor_list = []  
    # 计算因子  
    start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=max(ma1,ma2,ma3))[0]# 上一交易日  
    data_df = history_new(stocks,frequency='1d',start_time=start_date,end_time=end_date,fields='eob,symbol,close,open',adjust=ADJUST_PREV,adjust_end_time=end_date, df=True, type=False)  
      
    stocks_list = list(set(data_df['symbol']))  
    for symbol in stocks_list:  
        data = data_df[data_df['symbol']==symbol]  
        if len(data)==0:continue  
        data.set_index('eob',inplace=True)  
        new_close,new_open = data['close'].iloc[-1],data['open'].iloc[-1]  
        condA = new_close>new_open  
        if not condA:continue  
  
        ma20 = data['close'].iloc[-ma1:].mean()  
        ma40 = data['close'].iloc[-ma2:].mean()  
        ma60 = data['close'].iloc[-ma3:].mean()  
        condB = new_close>ma20 and new_close>ma40 and new_close>ma60  
        if not condB:continue  
            condCC = new_open<ma40 and new_open<ma60  
        if not condCC:continue  
        condCSFRO = (new_close-new_open)>0.0618*new_close  
        if not condCSFRO:continue  
        factor_list.append(symbol)  
    return factor_list  
  
  
def is_Week_or_Month_first_day(date,periods_type):  
    """  
    判断日期是否为 周/月 的第1个交易日  
    :param date：目标日期  
    :param periods_type：类型，'week'为周，'month'为月  
    """    date = pd.Timestamp(date)  
    # 判断该日期是否为交易日  
    is_trading_date = get_trading_dates(exchange='SZSE', start_date=date, end_date=date)  
    if len(is_trading_date)==0:  
        return False     
      
    last_date = pd.Timestamp(get_previous_trading_date(exchange='SZSE', date=date))  
    status = None  
    if periods_type=='week':  
        status = date.week!=last_date.week  
    elif periods_type=='month':  
        status = date.month!=last_date.month  
    return status  
  
  
def get_normal_stocks(date,new_days=365):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    """    date = pd.Timestamp(date)  
    # 先剔除退市股、次新股和B股  
    df_code = get_instrumentinfos(sec_types=SEC_TYPE_STOCK, fields='symbol, listed_date, delisted_date', df=True)  
    df_code['listed_date'] = df_code['listed_date'].apply(lambda x:x.date())  
    df_code['delisted_date'] = df_code['delisted_date'].apply(lambda x:x.date())  
    all_stocks = [code for code in df_code[(df_code['listed_date']<=date-datetime.timedelta(days=new_days))&(df_code['delisted_date']>date)].symbol.to_list() if code[:6]!='SHSE.9' and code[:6]!='SZSE.2']  
    # 再剔除当前的停牌股和ST股  
    history_ins = get_history_instruments(symbols=all_stocks, start_date=date, end_date=date, fields='symbol,sec_level, is_suspended', df=True)  
    all_stocks = list(history_ins[(history_ins['sec_level']==1) & (history_ins['is_suspended']==0)]['symbol'])  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,type=True时仅支持单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    else:  
        trading_date = history('SHSE.000300', frequency=frequency, start_time=start_time, end_time=end_time, fields='eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
        trading_date = trading_date['eob']  
    space = 5  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space-1  
            if end>=len(trading_date):  
                data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)>=33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])   
    Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
    if type:  
        if len(Data)>0:  
            Data = Data.set_index(['eob','symbol'])  
            Data = Data.unstack()  
            Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:  
        pass  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='7895f8a7-4f76-11ed-afcd-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-07-30 08:00:00',  
        backtest_end_time='2024-08-05 19:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# 射击之心-量能
**1、****策略说明**

l  发现射击之星形态在结合了量能指标后，能够大幅提高策略的胜率和收益；

l  优化代码效率和委托方式。

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import pandas as pd  
import numpy as np  
import warnings  
warnings.filterwarnings("ignore")  
import statsmodels.api as sm  
  
def init(context):  
    # 策略参数  
    context.S = 5  
    context.max_rate_periods = 10#涨幅限制的统计周期  
    # 获取目标指数的成分股，中证1000：'SHSE.000852'，中证500：'SHSE.000905'，沪深300：'SHSE.000300'  
    context.index = 'SHSE.000852'  
    # 交易参数  
    context.fundamentals = pd.DataFrame()  
    context.holding_num = 10# 最大持股数量  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:35:00')  
  
  
def before_market(context):  
    context.fundamentals = pd.DataFrame()  
    # 所有持仓  
    Account_positions = context.account().positions()  
    holding_stocks = [posi['symbol'] for posi in Account_positions]  
    context.today = context.now.strftime('%Y-%m-%d')  
    if len(holding_stocks)<context.holding_num:  
        # 获取交易日  
        last_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=1)[0]# 上一交易日  
        stocks = stk_get_index_constituents(index=context.index, trade_date=last_date)['symbol'].tolist()  
        stocks_str = ','.join(stocks)  
        # stocks,stocks_str = get_normal_stocks(last_date)  
  
        # 条件1：形态  
        stocks_STAR = cal_STK_FORM_SHOOT_STAR(stocks_str,last_date,ma1=context.S)  
        if len(stocks_STAR)==0:return  
  
        # 条件2：涨幅过滤(过滤掉10日内涨幅大于等于0的股票)  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=context.max_rate_periods)[0]# 上一交易日  
        close_start = history(stocks_STAR,frequency='1d',start_time=start_date,end_time=start_date,fields='symbol,close',adjust=ADJUST_PREV,adjust_end_time=last_date, df=True)  
        close_end = history(stocks_STAR,frequency='1d',start_time=last_date,end_time=last_date,fields='symbol,close',adjust=ADJUST_PREV,adjust_end_time=last_date, df=True)  
        if len(close_start)==0 or len(close_end)==0:  
            return  
        else:  
            close_start = close_start.set_index('symbol')  
            close_end = close_end.set_index('symbol')  
        common_stocks = list(set(close_start.index)&set(close_end.index))  
        close_start = close_start.loc[common_stocks,:]  
        close_end = close_end.loc[common_stocks,:]  
        to_buy = list(close_end[close_end<close_start].dropna().index)  
        if len(to_buy)==0:return  
        # 条件3：不严重缩量  
        to_buy_new = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=last_date, n=100)  
        all_data = history_new(security=to_buy,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,low,volume,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in to_buy:  
            the_all_data = all_data[all_data['symbol']==symbol]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            if volume_data.iloc[-1] / max(volume_data[:-1])>0.8:  
                to_buy_new.append(symbol)  
        to_buy = to_buy_new  
        if len(to_buy)==0:return  
  
        # 条件4：数量过多时，以市值从小到大买入  
        if len(to_buy)==0:  
            pass  
        else:  
            # 获取所有股票市值,并按升序排序  
            fundamental = stk_get_daily_mktvalue_pt(symbols=to_buy, fields='tot_mv', trade_date=last_date, df=True)  
            context.fundamentals = fundamental[fundamental['symbol'].isin(to_buy)].sort_values(by='tot_mv')  
            # 获取前N只股票  
            if len(context.fundamentals)==0:  
                context.fundamentals = pd.DataFrame()  
            else:  
                context.fundamentals = context.fundamentals.sort_values('tot_mv')  
  
  
def algo_1(context):  
    # 获取持仓  
    positions = context.account().positions()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    if len(holding_stocks)>0:  
        # 历史数据价格  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=context.S)# 上一交易日  
        start_date = date_list[0]  
        end_date = date_list[-1]  
        his_close_all = history_new(holding_stocks,frequency='1d',start_time=start_date,end_time=end_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=end_date, df=True, type=False)  
              
        # 平不在标的池的股票  
        for position in positions:  
            symbol = position['symbol']  
            his_close = his_close_all[his_close_all['symbol']==symbol]  
            ma_price = his_close['close'].mean()  
            new_price = his_close['close'].iloc[-1]   
            # 价格低于MA均线便出场  
            if new_price<ma_price:  
                print('{}：{}MA均价止盈止损'.format(context.now,symbol))  
                lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
                if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=new_price:  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
                else:  
                    print('{}:{}数据跌停或数据缺失导致未能卖出！！！！！！！！！！！！！！！！！！！！！'.format(context.now,symbol))  
              
    # 买在标的池中的股票  
    # 所有持仓  
    if len(holding_stocks)<context.holding_num and len(holding_stocks)+len(context.fundamentals)>context.holding_num:  
        context.fundamentals = context.fundamentals.iloc[:(context.holding_num-len(holding_stocks)),:]  
        print('{}:股票标的：{}'.format(context.now,list(context.fundamentals['symbol'])))  
    for n in range(len(context.fundamentals)):  
        symbol = context.fundamentals['symbol'].iloc[n]  
        if len(holding_stocks)<context.holding_num:  
            new_price = current(symbols=symbol)[0]['price']  
            nav = context.account().cash['nav']  
            trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/context.holding_num,price=new_price)  
            order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
              
   
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def cal_STK_FORM_SHOOT_STAR(stocks,end_date,ma1=5):  
  
    """  
    形态因子-射击之星  
    MIN(OPEN,CLOSE)=LOW&&    HIGH-LOW>3*(MAX(OPEN,CLOSE)-LOW)&&    CLOSE>MA(CLOSE,5);    """    factor_list = []  
    # 计算因子  
    start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=ma1)[0]# 上一交易日  
    data_df = history_new(stocks,frequency='1d',start_time=start_date,end_time=end_date,fields='eob,symbol,close,open,low,high',adjust=ADJUST_PREV,adjust_end_time=end_date, df=True, type=False)  
      
    stocks_list = list(set(data_df['symbol']))  
    for symbol in stocks_list:  
        data = data_df[data_df['symbol']==symbol]  
        if len(data)==0:continue  
        data.set_index('eob',inplace=True)  
        new_close,new_open,new_low,new_high = data['close'].iloc[-1],data['open'].iloc[-1],data['low'].iloc[-1],data['high'].iloc[-1]  
        cond1 = min(new_close,new_open)==new_low  
        if not cond1:continue  
        cond2 = (new_high-new_low)>3*(max(new_open,new_close)-new_low)  
        if not cond2:continue  
        ma5 = np.mean(data_df['close'].iloc[-5:])  
        cond3 = new_close>ma5  
        if not cond3:continue  
        factor_list.append(symbol)  
    return factor_list  
  
  
def is_Week_or_Month_first_day(date,periods_type):  
    """  
    判断日期是否为 周/月 的第1个交易日  
    :param date：目标日期  
    :param periods_type：类型，'week'为周，'month'为月  
    """    date = pd.Timestamp(date)  
    # 判断该日期是否为交易日  
    is_trading_date = get_trading_dates(exchange='SZSE', start_date=date, end_date=date)  
    if len(is_trading_date)==0:  
        return False     
      
    last_date = pd.Timestamp(get_previous_trading_date(exchange='SZSE', date=date))  
    status = None  
    if periods_type=='week':  
        status = date.week!=last_date.week  
    elif periods_type=='month':  
        status = date.month!=last_date.month  
    return status  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_limit:是否剔除开盘涨停股票，默认为False,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,type=True时仅支持单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    else:  
        trading_date = history('SHSE.000300', frequency=frequency, start_time=start_time, end_time=end_time, fields='eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
        trading_date = trading_date['eob']  
    space = 5  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space-1  
            if end>=len(trading_date):  
                data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)>=33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])   
    Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
    if type:  
        if len(Data)>0:  
            Data = Data.set_index(['eob','symbol'])  
            Data = Data.unstack()  
            Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='7895f8a7-4f76-11ed-afcd-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 08:00:00',  
        backtest_end_time='2024-08-04 19:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# barra模型因子构建

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  
  

def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # # 每周最后一个交易日移仓换股  
    # if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # 每月最后一个交易日移仓换股  
    if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        factor = cal_StyleFactor_Size(context, security=all_stocks_str, date=last_date)  
        # 获取最小因子的前N只股票  
        to_buy = list(factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(ascending=True)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=1/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=1/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Size(context, security, date):  
    """  
    计算风格因子 Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 目标日期（int）  
    return:Size(DataFrame)    """    # get_fundamentals_n中end_date对标的是财报季度最后一天，而非财报发布日期，所以获取的数据会有未来数据  
    dfdata = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', start_date=date, end_date=date, df=True)    
    dfdata.set_index(['symbol'],inplace=True)  
    Size = dfdata['tot_mv']  
    Size = np.log(Size).replace([-np.inf,np.inf],np.nan).fillna(0)  
    # 去极值、标准化、有效样本数量限制、市值中性化  
    alpha_factor = winsorize_med(Size)  
    alpha_factor = standardlize(alpha_factor)  
    alpha_factor = neutralize_MarketValue(context, alpha_factor,date)  
    return alpha_factor  
      
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    if inclusive:  
        data[data>mean_+std_*scale]=mean_+std_*scale  
        data[data<mean_-std_*scale]=mean_-std_*scale  
    else:  
        data[data>mean_+std_*scale]=np.nan  
        data[data<mean_-std_*scale]=np.nan  
    return data  
  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
def neutralize_MarketValue(context, data,date,counts=1):  
    """  
    市值中性化  
    :param data:待处理数据  
    :param date:目标日期  
    :param counts：历史回溯天数  
    """    if isinstance(data,pd.Series):  
        data = data.to_frame()  
    security = data.index.to_list()  
    market_value = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', end_date=date, counts=counts, df=True)  
    max_date = market_value['trade_date'].max()  
    market_value = market_value[market_value['trade_date']==max_date][['symbol','tot_mv']].set_index('symbol')  
    x = sm.add_constant(market_value)  
    common_index = list(set(x.index) & set(data.index))  
    x = x.loc[common_index,:]  
    data = data.loc[common_index,:]  
    residual = sm.OLS(data, x).fit().resid# 此处使用最小二乘回归计算  
    return residual  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2014-01-01 08:00:00',  
        backtest_end_time='2024-11-30 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        )

# barra模型因子二

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  
  

def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # # 每周最后一个交易日移仓换股  
    # if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # 每月最后一个交易日移仓换股  
    if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        Alpha,Beta = cal_StyleFactor_Beta(context, security=all_stocks_str, date=last_date)  
        # 去极值、标准化、有效样本数量限制、市值中性化  
        alpha_factor = winsorize_med(Beta)  
        alpha_factor = standardlize(alpha_factor)  
        alpha_factor = neutralize(alpha_factor)  
        # 获取最大因子的前N只股票  
        to_buy = list(alpha_factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(last_date,ascending=False)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Beta(context, security, date, periods=252, helf_life=63):  
    """  
    计算风格因子 Beta    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param periods 估计区间，默认为252个交易日  
    :param helf_life 半衰期，默认为63个交易日  
    return:Alpha,Beta(DataFrame)    注：无风险收益以2%代替，市场收益以全A股票收益按流通市值加权,后边函数亦是  
    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    Alpha_df,Beta_df,Epsilon_df = pd.DataFrame(),pd.DataFrame(),pd.DataFrame  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=periods)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close.shift(1)/close-1).iloc[1:,:]  
    StockReturn.index = [date.strftime('%Y-%m-%d') for date in StockReturn.index]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
    # 计算市场超额收益率  
    NegotiableMV = NegotiableMV_df.loc[start_time:date,:]  
    NegotiableMVRatio = (NegotiableMV.T.astype(float)/NegotiableMV.sum(axis=1)).T  
    com_stocks = list(set(StockReturn.columns)&set(NegotiableMVRatio.columns))  
    com_date = list(set(StockReturn.index)&set(NegotiableMVRatio.index))  
    StockReturn_ = StockReturn.loc[com_date,com_stocks].fillna(0).sort_index()  
    NegotiableMVRatio = NegotiableMVRatio.loc[com_date,com_stocks].fillna(0).sort_index()  
    MarketReturn = np.dot(StockReturn_,NegotiableMVRatio.T)  
  
    MarketReturn = pd.DataFrame(MarketReturn,index=NegotiableMVRatio.index,columns=NegotiableMVRatio.index)  
    MarketReturnAlpha = np.diag(MarketReturn-RiskFreeReturnDaily)  
    StockReturnAlpha = (StockReturnAlpha.T.reindex(com_stocks).T).fillna(method='ffill')  
    # 以解析解求解  
    lambda_ = np.power(0.5,1/helf_life)  
    W_list = [np.power(lambda_,t) for t in range(periods-1,-1,-1)]  
    W = np.diag(W_list)  
    W = pd.DataFrame(W,index=StockReturnAlpha.index,columns=StockReturnAlpha.index)  
    MarketReturnAlpha = pd.DataFrame(MarketReturnAlpha, index=StockReturnAlpha.index)  
    ones = pd.DataFrame([1] * periods, index=StockReturnAlpha.index)  
    X = pd.concat([ones,MarketReturnAlpha],axis=1)  
    out_ = np.dot(np.dot(np.linalg.inv(np.dot(np.dot(X.T,W),X)),X.T),W)  
    Alpha = pd.DataFrame()  
    Beta = pd.DataFrame()  
    for i in range(StockReturnAlpha.shape[1]):  
        Y = StockReturnAlpha.iloc[:,i]  
        out = np.dot(out_,Y)  
        Alpha = pd.concat([Alpha,pd.DataFrame(out[0],index=['Alpha'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
        Beta = pd.concat([Beta,pd.DataFrame(out[1],index=['Beta'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
    Alpha = Alpha.T  
    Alpha.columns = [date]  
    Beta = Beta.T  
    Beta.columns = [date]  
    AlphaDF = pd.DataFrame(np.outer([1]*len(MarketReturnAlpha),Alpha),index=MarketReturnAlpha.index,columns=Alpha.index)  
    Epsilon = StockReturnAlpha-(AlphaDF+np.outer(MarketReturnAlpha,Beta))  
    Alpha_df = pd.concat([Alpha_df,Alpha],axis=1)  
    Beta_df = pd.concat([Beta_df,Beta],axis=1)  
    return Alpha_df,Beta_df  
  
      
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])  
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])  
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])  
    if inclusive:  
        data = np.minimum(data, np.asarray(uplimit))   
        data = np.maximum(data, np.asarray(downlimit))   
    else:  
        data = np.minimum(data, np.asarray(nan_df))   
        data = np.maximum(data, np.asarray(nan_df))   
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
  
def neutralize(data):  
    """  
    市值+行业中性化(data以code*date的形式)  
    :param data：待处理数据  
    :param date：目标日期  
    """    data = data.dropna(how='any')  
    security = list(data.index)  
    residual_df = pd.DataFrame()  
    month_date = []  
    for date in data.columns:  
        month = date[:7]  
        if month not in month_date:  
            month_date.append(month)  
            # 查询标的市值，默认历史回溯天数count=1  
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)  
            market_value = market_value.drop(['trade_date'], axis=1)  
            market_value['tot_mv'] = np.log(market_value['tot_mv'])  
            market_value = market_value.set_index('symbol')  
            # 查询标的行业代码，以哑变量格式存储  
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类  
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)  
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)  
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列  
            symbol_industry = symbol_industry.groupby('symbol').sum()  
            # 采用最小二乘法进行多元线性回归，计算残差  
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')  
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')  
        X = sm.add_constant(df_reg.iloc[:, 1:])  
        Y = df_reg.iloc[:, 0]  
        MLR_model = sm.OLS(Y, X)  
        residual = MLR_model.fit().resid  
        residual = residual.to_frame(name=date).rename_axis('symbol')  
        residual_df = pd.concat([residual_df,residual],axis=1)  
          
    return residual_df  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-12-22 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        )

# barra模型因子三

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  

def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # # 每周最后一个交易日移仓换股  
    # if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # 每月最后一个交易日移仓换股  
    if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        Momentum = cal_StyleFactor_Momentum(context, security=all_stocks_str, date=last_date)  
        # 去极值、标准化、有效样本数量限制、市值中性化  
        alpha_factor = winsorize_med(Momentum)  
        alpha_factor = standardlize(alpha_factor)  
        alpha_factor = neutralize(alpha_factor)  
        # 获取最小因子的前N只股票  
        to_buy = list(alpha_factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(last_date,ascending=True)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Size(context, security, date):  
    """  
    计算风格因子 Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 目标日期（int）  
    return:Size(DataFrame)    """    # get_fundamentals_n中end_date对标的是财报季度最后一天，而非财报发布日期，所以获取的数据会有未来数据  
    dfdata = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', start_date=date, end_date=date, df=True)    
    dfdata.set_index(['symbol'],inplace=True)  
    Size = dfdata['tot_mv']  
    Size = np.log(Size).replace([-np.inf,np.inf],np.nan).fillna(0)  
    # 去极值、标准化、有效样本数量限制、市值中性化  
    alpha_factor = winsorize_med(Size)  
    alpha_factor = standardlize(alpha_factor)  
    alpha_factor = neutralize_MarketValue(context, alpha_factor,date)  
    return alpha_factor  
  
  
def cal_StyleFactor_Beta(context, security, date, periods=252, helf_life=63):  
    """  
    计算风格因子 Beta    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param periods 估计区间，默认为252个交易日  
    :param helf_life 半衰期，默认为63个交易日  
    return:Alpha,Beta(DataFrame)    注：无风险收益以2%代替，市场收益以全A股票收益按流通市值加权,后边函数亦是  
    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    Alpha_df,Beta_df,Epsilon_df = pd.DataFrame(),pd.DataFrame(),pd.DataFrame  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=periods)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close.shift(1)/close-1).iloc[1:,:]  
    StockReturn.index = [date.strftime('%Y-%m-%d') for date in StockReturn.index]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
    # 计算市场超额收益率  
    NegotiableMV = NegotiableMV_df.loc[start_time:date,:]  
    NegotiableMVRatio = (NegotiableMV.T.astype(float)/NegotiableMV.sum(axis=1)).T  
    com_stocks = list(set(StockReturn.columns)&set(NegotiableMVRatio.columns))  
    com_date = list(set(StockReturn.index)&set(NegotiableMVRatio.index))  
    StockReturn_ = StockReturn.loc[com_date,com_stocks].fillna(0).sort_index()  
    NegotiableMVRatio = NegotiableMVRatio.loc[com_date,com_stocks].fillna(0).sort_index()  
    MarketReturn = np.dot(StockReturn_,NegotiableMVRatio.T)  
  
    MarketReturn = pd.DataFrame(MarketReturn,index=NegotiableMVRatio.index,columns=NegotiableMVRatio.index)  
    MarketReturnAlpha = np.diag(MarketReturn-RiskFreeReturnDaily)  
    StockReturnAlpha = (StockReturnAlpha.T.reindex(com_stocks).T).fillna(method='ffill')  
    # 以解析解求解  
    lambda_ = np.power(0.5,1/helf_life)  
    W_list = [np.power(lambda_,t) for t in range(periods-1,-1,-1)]  
    W = np.diag(W_list)  
    W = pd.DataFrame(W,index=StockReturnAlpha.index,columns=StockReturnAlpha.index)  
    MarketReturnAlpha = pd.DataFrame(MarketReturnAlpha, index=StockReturnAlpha.index)  
    ones = pd.DataFrame([1] * periods, index=StockReturnAlpha.index)  
    X = pd.concat([ones,MarketReturnAlpha],axis=1)  
    out_ = np.dot(np.dot(np.linalg.inv(np.dot(np.dot(X.T,W),X)),X.T),W)  
    Alpha = pd.DataFrame()  
    Beta = pd.DataFrame()  
    for i in range(StockReturnAlpha.shape[1]):  
        Y = StockReturnAlpha.iloc[:,i]  
        out = np.dot(out_,Y)  
        Alpha = pd.concat([Alpha,pd.DataFrame(out[0],index=['Alpha'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
        Beta = pd.concat([Beta,pd.DataFrame(out[1],index=['Beta'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
    Alpha = Alpha.T  
    Alpha.columns = [date]  
    Beta = Beta.T  
    Beta.columns = [date]  
    AlphaDF = pd.DataFrame(np.outer([1]*len(MarketReturnAlpha),Alpha),index=MarketReturnAlpha.index,columns=Alpha.index)  
    Epsilon = StockReturnAlpha-(AlphaDF+np.outer(MarketReturnAlpha,Beta))  
    Alpha_df = pd.concat([Alpha_df,Alpha],axis=1)  
    Beta_df = pd.concat([Beta_df,Beta],axis=1)  
    return Alpha_df,Beta_df  
  
  
def cal_StyleFactor_Momentum(context, security, date, T=504, L=21, helf_life=126):  
    """  
    计算风格因子 Momentum    :param security 待筛选股票池（list)  
    :param date 日期  
    :param T:估计区间，默认为504个交易日  
    :param L：滞后区间，默认为21个交易日  
    :param helf_life：半衰期，默认为126个交易日  
    return:Momentum(DataFrame)    """    RiskFreeReturn = 0.03  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=L+T)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = np.log(StockReturn+1)+np.log(RiskFreeReturnDaily+1)  
    StockReturnAlpha.index = [date.strftime('%Y-%m-%d') for date in StockReturnAlpha.index]  
    lambda_ = np.power(0.5,1/helf_life)  
      
    Momentum_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-T:,:]  
    W = pd.DataFrame([np.power(lambda_,t) for t in range(T-1,-1,-1)],index=StockReturnAlpha_.index)  
    rstr = np.dot(StockReturnAlpha_.T,W)  
    Momentum = pd.DataFrame(rstr,index=StockReturnAlpha_.columns,columns=[date])  
    Momentum_df = pd.concat([Momentum_df,Momentum],axis=1)  
    return Momentum_df  
  
      
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])  
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])  
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])  
    if inclusive:  
        data = np.minimum(data, np.asarray(uplimit))   
        data = np.maximum(data, np.asarray(downlimit))   
    else:  
        data = np.minimum(data, np.asarray(nan_df))   
        data = np.maximum(data, np.asarray(nan_df))   
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
  
def neutralize(data):  
    """  
    市值+行业中性化(data以code*date的形式)  
    :param data：待处理数据  
    :param date：目标日期  
    """    data = data.dropna(how='any')  
    security = list(data.index)  
    residual_df = pd.DataFrame()  
    month_date = []  
    for date in data.columns:  
        month = date[:7]  
        if month not in month_date:  
            month_date.append(month)  
            # 查询标的市值，默认历史回溯天数count=1  
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)  
            market_value = market_value.drop(['trade_date'], axis=1)  
            market_value['tot_mv'] = np.log(market_value['tot_mv'])  
            market_value = market_value.set_index('symbol')  
            # 查询标的行业代码，以哑变量格式存储  
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类  
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)  
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)  
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列  
            symbol_industry = symbol_industry.groupby('symbol').sum()  
            # 采用最小二乘法进行多元线性回归，计算残差  
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')  
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')  
        X = sm.add_constant(df_reg.iloc[:, 1:])  
        Y = df_reg.iloc[:, 0]  
        MLR_model = sm.OLS(Y, X)  
        residual = MLR_model.fit().resid  
        residual = residual.to_frame(name=date).rename_axis('symbol')  
        residual_df = pd.concat([residual_df,residual],axis=1)  
          
    return residual_df  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-12-31 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        )

# barra模型因子四
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  
  
def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # # 每周最后一个交易日移仓换股  
    # if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # 每月最后一个交易日移仓换股  
    if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        Alpha_df,Beta_df,Epsilon_dict = cal_StyleFactor_Beta(context, security=all_stocks_str, date=last_date)  
        Residual_Volatility = cal_StyleFactor_Residual_Volatility(context, security=all_stocks_str, date=last_date, Epsilon=Epsilon_dict)  
        # 去极值、标准化、有效样本数量限制、市值中性化  
        alpha_factor = winsorize_med(Residual_Volatility)  
        alpha_factor = standardlize(alpha_factor)  
        alpha_factor = neutralize(alpha_factor)  
        # 获取最小因子的前N只股票  
        to_buy = list(alpha_factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(last_date,ascending=True)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Size(context, security, date):  
    """  
    计算风格因子 Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 目标日期（int）  
    return:Size(DataFrame)    """    # get_fundamentals_n中end_date对标的是财报季度最后一天，而非财报发布日期，所以获取的数据会有未来数据  
    dfdata = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', start_date=date, end_date=date, df=True)    
    dfdata.set_index(['symbol'],inplace=True)  
    Size = dfdata['tot_mv']  
    Size = np.log(Size).replace([-np.inf,np.inf],np.nan).fillna(0)  
    # 去极值、标准化、有效样本数量限制、市值中性化  
    alpha_factor = winsorize_med(Size)  
    alpha_factor = standardlize(alpha_factor)  
    alpha_factor = neutralize_MarketValue(context, alpha_factor,date)  
    return alpha_factor  
  
  
def cal_StyleFactor_Beta(context, security, date, periods=252, helf_life=63):  
    """  
    计算风格因子 Beta    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param periods 估计区间，默认为252个交易日  
    :param helf_life 半衰期，默认为63个交易日  
    return:Alpha,Beta(DataFrame)    注：无风险收益以2%代替，市场收益以全A股票收益按流通市值加权,后边函数亦是  
    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    Alpha_df,Beta_df,Epsilon_df = pd.DataFrame(),pd.DataFrame(),pd.DataFrame  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=periods)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close.shift(1)/close-1).iloc[1:,:]  
    StockReturn.index = [date.strftime('%Y-%m-%d') for date in StockReturn.index]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
    # 计算市场超额收益率  
    NegotiableMV = NegotiableMV_df.loc[start_time:date,:]  
    NegotiableMVRatio = (NegotiableMV.T.astype(float)/NegotiableMV.sum(axis=1)).T  
    com_stocks = list(set(StockReturn.columns)&set(NegotiableMVRatio.columns))  
    com_date = list(set(StockReturn.index)&set(NegotiableMVRatio.index))  
    StockReturn_ = StockReturn.loc[com_date,com_stocks].fillna(0).sort_index()  
    NegotiableMVRatio = NegotiableMVRatio.loc[com_date,com_stocks].fillna(0).sort_index()  
    MarketReturn = np.dot(StockReturn_,NegotiableMVRatio.T)  
  
    MarketReturn = pd.DataFrame(MarketReturn,index=NegotiableMVRatio.index,columns=NegotiableMVRatio.index)  
    MarketReturnAlpha = np.diag(MarketReturn-RiskFreeReturnDaily)  
    StockReturnAlpha = (StockReturnAlpha.T.reindex(com_stocks).T).fillna(method='ffill')  
    # 以解析解求解  
    lambda_ = np.power(0.5,1/helf_life)  
    W_list = [np.power(lambda_,t) for t in range(periods-1,-1,-1)]  
    W = np.diag(W_list)  
    W = pd.DataFrame(W,index=StockReturnAlpha.index,columns=StockReturnAlpha.index)  
    MarketReturnAlpha = pd.DataFrame(MarketReturnAlpha, index=StockReturnAlpha.index)  
    ones = pd.DataFrame([1] * periods, index=StockReturnAlpha.index)  
    X = pd.concat([ones,MarketReturnAlpha],axis=1)  
    out_ = np.dot(np.dot(np.linalg.inv(np.dot(np.dot(X.T,W),X)),X.T),W)  
    Alpha = pd.DataFrame()  
    Beta = pd.DataFrame()  
    for i in range(StockReturnAlpha.shape[1]):  
        Y = StockReturnAlpha.iloc[:,i]  
        out = np.dot(out_,Y)  
        Alpha = pd.concat([Alpha,pd.DataFrame(out[0],index=['Alpha'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
        Beta = pd.concat([Beta,pd.DataFrame(out[1],index=['Beta'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
    Alpha = Alpha.T  
    Alpha.columns = [date]  
    Beta = Beta.T  
    Beta.columns = [date]  
    AlphaDF = pd.DataFrame(np.outer([1]*len(MarketReturnAlpha),Alpha),index=MarketReturnAlpha.index,columns=Alpha.index)  
    Epsilon = StockReturnAlpha-(AlphaDF+np.outer(MarketReturnAlpha,Beta))  
    Alpha_df = pd.concat([Alpha_df,Alpha],axis=1)  
    Beta_df = pd.concat([Beta_df,Beta],axis=1)  
    Epsilon_dict = {date:Epsilon}  
    return Alpha_df,Beta_df,Epsilon_dict  
  
  
def cal_StyleFactor_Momentum(context, security, date, T=504, L=21, helf_life=126):  
    """  
    计算风格因子 Momentum    :param security 待筛选股票池（list)  
    :param date 日期  
    :param T:估计区间，默认为504个交易日  
    :param L：滞后区间，默认为21个交易日  
    :param helf_life：半衰期，默认为126个交易日  
    return:Momentum(DataFrame)    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=L+T)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = np.log(StockReturn+1)+np.log(RiskFreeReturnDaily+1)  
    StockReturnAlpha.index = [date.strftime('%Y-%m-%d') for date in StockReturnAlpha.index]  
    lambda_ = np.power(0.5,1/helf_life)  
      
    Momentum_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-T:,:]  
    W = pd.DataFrame([np.power(lambda_,t) for t in range(T-1,-1,-1)],index=StockReturnAlpha_.index)  
    rstr = np.dot(StockReturnAlpha_.T,W)  
    Momentum = pd.DataFrame(rstr,index=StockReturnAlpha_.columns,columns=[date])  
    Momentum_df = pd.concat([Momentum_df,Momentum],axis=1)  
    return Momentum_df  
  
  
def get_return(data, frequency='daily', type='trade', cycle=None):  
    """  
    获取股票区间收益率  
    :param data：收盘价（df：日期*股票代码）  
    :param frequency 频率，daily为日频，W为周频，M为月频，other为其他，默认为月频  
    :param type 频率类型，nature为自然周、自然月；trade为交易周（追溯5天）、交易月（追溯21天）,默认为trade模式  
    :param cycle 周期天数，需前置参数 非daily和非nature  
    注：部分退市股后代码被重新使用的股票，其退市后价格为0，其收益率则计算为-1，为此将-1替换为NaN  
    """    if frequency != 'daily':  
        if type == 'nature':  
            close0 = data.copy()  
            close0.index = pd.DatetimeIndex(close0.index)  
            close = close0.resample(frequency).last()  
            first_date_df = pd.DataFrame(close0.index.tolist(), index=close0.index,  
                                         columns=['first_trading_date']).resample(frequency).first()  
            close.index = [date.strftime('%Y%m%d') for date in first_date_df['first_trading_date']]  
        else:  
            close = data.copy()  
            if cycle is None:  
                length = 5 if frequency=='W' else 21  
            else:  
                length = cycle  
            close = close.iloc[::-length,:].sort_index()  
    else:  
        close = data.copy()  
    rate = close.rolling(window=2, min_periods=2).apply(lambda x: x[1] / x[0] - 1).iloc[1:,:].replace([-1, np.inf, -np.inf], np.nan).fillna(0)  
    return rate  
  
  
def cal_StyleFactor_Residual_Volatility(context, security,date,Epsilon,coef1=0.74,coef2=0.16,coef3=0.10,coef1_n=252,coef1_halflife=42,coef2_n=12):  
    """  
    计算风格因子 Residual_Volatility    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Epsilon 残差收益率（在计算beta是返回的参数）  
    :param coef1 第一个子因子（DASTD）的权重系数，默认为0.74  
    :param coef2 第二个子因子（CMRA）的权重系数，默认为0.16  
    :param coef3 第三个子因子（HSIGMA）的权重系数，默认为0.10  
    :param coef1_n 第一个子因子的估计区间，默认为252个交易日  
    :param coef1_halflife 第一个子因子的半衰期，默认为42个交易日  
    :param coef2_n 第二个子因子的估计区间，默认为12个交易月（每月为21个交易日）  
    return:Residual_Volatility(DataFrame)    注：三个子因子叠加前需要做标准化处理  
       CMRA的计算参考CNE6的方法，未采用CNE5的方法  
    """## 计算DASTD  
    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=coef1_n+1)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
      
    Residual_volatility_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-coef1_n:,:]  
    StockReturnAlpha_ -= StockReturnAlpha_.mean()  
    # 计算权重  
    lambda_ = np.power(0.5,1/coef1_halflife)  
    W = [np.power(lambda_,t) for t in range(coef1_n-1,-1,-1)]  
    Weight = pd.DataFrame(W,index=StockReturnAlpha_.index)  
    # 计算子因子  
    DASTD = np.dot((StockReturnAlpha_*StockReturnAlpha_).T,Weight)/len(Weight)  
    DASTD = pd.DataFrame(DASTD,index=StockReturnAlpha_.columns)  
    DASTD = standardlize(DASTD)  
  
    ## 计算CMRA  
    StockReturnMonth = get_return(close.loc[:date,:].iloc[-coef1_n:,:], frequency='M', type='trade').iloc[-coef2_n:,:]  
    StockReturnMonth = StockReturnMonth.sort_index(ascending=False)  
    RiskFreeReturnMonth = np.power(1+RiskFreeReturn, 1 / 12) - 1  
    Z = (np.log(1+StockReturnMonth)-np.log(1+RiskFreeReturnMonth)).cumsum()  
    # zmin = 1+Z.min()  
    # zmin[zmin<0]=0.0001    # CMRA = np.log(1+Z.max())-np.log(zmin)# CNE5的方法  
    CMRA = Z.max()-Z.min()# CNE6的方法  
    CMRA = pd.DataFrame(CMRA,index=StockReturnAlpha_.columns)  
    CMRA = standardlize(CMRA)  
    # 计算HSIGMA  
    HSIGMA = Epsilon[date].std()  
    HSIGMA = pd.DataFrame(HSIGMA,index=StockReturnAlpha_.columns)  
    HSIGMA = standardlize(HSIGMA)  
  
    # 整合DASTD、CMRA和HSIGMA  
    Residual_volatility = coef1*DASTD+coef2*CMRA+coef3*HSIGMA  
    Residual_volatility.columns = [date]  
    Residual_volatility_df = pd.concat([Residual_volatility_df,Residual_volatility],axis=1)  
    return Residual_volatility_df  
  
      
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])  
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])  
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])  
    if inclusive:  
        data = np.minimum(data, np.asarray(uplimit))   
        data = np.maximum(data, np.asarray(downlimit))   
    else:  
        data = np.minimum(data, np.asarray(nan_df))   
        data = np.maximum(data, np.asarray(nan_df))   
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
  
def neutralize(data):  
    """  
    市值+行业中性化(data以code*date的形式)  
    :param data：待处理数据  
    :param date：目标日期  
    """    data = data.dropna(how='any')  
    security = list(data.index)  
    residual_df = pd.DataFrame()  
    month_date = []  
    for date in data.columns:  
        month = date[:7]  
        if month not in month_date:  
            month_date.append(month)  
            # 查询标的市值，默认历史回溯天数count=1  
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)  
            market_value = market_value.drop(['trade_date'], axis=1)  
            market_value['tot_mv'] = np.log(market_value['tot_mv'])  
            market_value = market_value.set_index('symbol')  
            # 查询标的行业代码，以哑变量格式存储  
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类  
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)  
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)  
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列  
            symbol_industry = symbol_industry.groupby('symbol').sum()  
            # 采用最小二乘法进行多元线性回归，计算残差  
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')  
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')  
        X = sm.add_constant(df_reg.iloc[:, 1:])  
        Y = df_reg.iloc[:, 0]  
        MLR_model = sm.OLS(Y, X)  
        residual = MLR_model.fit().resid  
        residual = residual.to_frame(name=date).rename_axis('symbol')  
        residual_df = pd.concat([residual_df,residual],axis=1)  
          
    return residual_df  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-12-31 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        )

# barra模型因子五
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  
  
def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # # 每周最后一个交易日移仓换股  
    # if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # 每月最后一个交易日移仓换股  
    if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        Size = cal_StyleFactor_Size(context, security=all_stocks_str, date=last_date)  
        NonLinear_Size = cal_StyleFactor_NonLinear_Size(context, security=all_stocks_str, date=last_date, Size=Size)  
        # 去极值、标准化、有效样本数量限制、市值中性化  
        alpha_factor = winsorize_med(NonLinear_Size)  
        alpha_factor = standardlize(alpha_factor)  
        alpha_factor = neutralize(alpha_factor)  
        # 获取最大因子的前N只股票  
        to_buy = list(alpha_factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(last_date,ascending=False)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Size(context, security, date):  
    """  
    计算风格因子 Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 目标日期（int）  
    return:Size(DataFrame)    """    # get_fundamentals_n中end_date对标的是财报季度最后一天，而非财报发布日期，所以获取的数据会有未来数据  
    dfdata = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', start_date=date, end_date=date, df=True)    
    dfdata.set_index(['symbol'],inplace=True)  
    Size = dfdata['tot_mv']  
    Size = np.log(Size).replace([-np.inf,np.inf],np.nan).fillna(0)  
    return Size  
  
  
def cal_StyleFactor_Beta(context, security, date, periods=252, helf_life=63):  
    """  
    计算风格因子 Beta    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param periods 估计区间，默认为252个交易日  
    :param helf_life 半衰期，默认为63个交易日  
    return:Alpha,Beta(DataFrame)    注：无风险收益以2%代替，市场收益以全A股票收益按流通市值加权,后边函数亦是  
    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    Alpha_df,Beta_df,Epsilon_df = pd.DataFrame(),pd.DataFrame(),pd.DataFrame  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=periods)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close.shift(1)/close-1).iloc[1:,:]  
    StockReturn.index = [date.strftime('%Y-%m-%d') for date in StockReturn.index]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
    # 计算市场超额收益率  
    NegotiableMV = NegotiableMV_df.loc[start_time:date,:]  
    NegotiableMVRatio = (NegotiableMV.T.astype(float)/NegotiableMV.sum(axis=1)).T  
    com_stocks = list(set(StockReturn.columns)&set(NegotiableMVRatio.columns))  
    com_date = list(set(StockReturn.index)&set(NegotiableMVRatio.index))  
    StockReturn_ = StockReturn.loc[com_date,com_stocks].fillna(0).sort_index()  
    NegotiableMVRatio = NegotiableMVRatio.loc[com_date,com_stocks].fillna(0).sort_index()  
    MarketReturn = np.dot(StockReturn_,NegotiableMVRatio.T)  
  
    MarketReturn = pd.DataFrame(MarketReturn,index=NegotiableMVRatio.index,columns=NegotiableMVRatio.index)  
    MarketReturnAlpha = np.diag(MarketReturn-RiskFreeReturnDaily)  
    StockReturnAlpha = (StockReturnAlpha.T.reindex(com_stocks).T).fillna(method='ffill')  
    # 以解析解求解  
    lambda_ = np.power(0.5,1/helf_life)  
    W_list = [np.power(lambda_,t) for t in range(periods-1,-1,-1)]  
    W = np.diag(W_list)  
    W = pd.DataFrame(W,index=StockReturnAlpha.index,columns=StockReturnAlpha.index)  
    MarketReturnAlpha = pd.DataFrame(MarketReturnAlpha, index=StockReturnAlpha.index)  
    ones = pd.DataFrame([1] * periods, index=StockReturnAlpha.index)  
    X = pd.concat([ones,MarketReturnAlpha],axis=1)  
    out_ = np.dot(np.dot(np.linalg.inv(np.dot(np.dot(X.T,W),X)),X.T),W)  
    Alpha = pd.DataFrame()  
    Beta = pd.DataFrame()  
    for i in range(StockReturnAlpha.shape[1]):  
        Y = StockReturnAlpha.iloc[:,i]  
        out = np.dot(out_,Y)  
        Alpha = pd.concat([Alpha,pd.DataFrame(out[0],index=['Alpha'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
        Beta = pd.concat([Beta,pd.DataFrame(out[1],index=['Beta'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
    Alpha = Alpha.T  
    Alpha.columns = [date]  
    Beta = Beta.T  
    Beta.columns = [date]  
    AlphaDF = pd.DataFrame(np.outer([1]*len(MarketReturnAlpha),Alpha),index=MarketReturnAlpha.index,columns=Alpha.index)  
    Epsilon = StockReturnAlpha-(AlphaDF+np.outer(MarketReturnAlpha,Beta))  
    Alpha_df = pd.concat([Alpha_df,Alpha],axis=1)  
    Beta_df = pd.concat([Beta_df,Beta],axis=1)  
    Epsilon_dict = {date:Epsilon}  
    return Alpha_df,Beta_df,Epsilon_dict  
  
  
def cal_StyleFactor_Momentum(context, security, date, T=504, L=21, helf_life=126):  
    """  
    计算风格因子 Momentum    :param security 待筛选股票池（list)  
    :param date 日期  
    :param T:估计区间，默认为504个交易日  
    :param L：滞后区间，默认为21个交易日  
    :param helf_life：半衰期，默认为126个交易日  
    return:Momentum(DataFrame)    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=L+T)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = np.log(StockReturn+1)+np.log(RiskFreeReturnDaily+1)  
    StockReturnAlpha.index = [date.strftime('%Y-%m-%d') for date in StockReturnAlpha.index]  
    lambda_ = np.power(0.5,1/helf_life)  
      
    Momentum_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-T:,:]  
    W = pd.DataFrame([np.power(lambda_,t) for t in range(T-1,-1,-1)],index=StockReturnAlpha_.index)  
    rstr = np.dot(StockReturnAlpha_.T,W)  
    Momentum = pd.DataFrame(rstr,index=StockReturnAlpha_.columns,columns=[date])  
    Momentum_df = pd.concat([Momentum_df,Momentum],axis=1)  
    return Momentum_df  
  
  
def get_return(data, frequency='daily', type='trade', cycle=None):  
    """  
    获取股票区间收益率  
    :param data：收盘价（df：日期*股票代码）  
    :param frequency 频率，daily为日频，W为周频，M为月频，other为其他，默认为月频  
    :param type 频率类型，nature为自然周、自然月；trade为交易周（追溯5天）、交易月（追溯21天）,默认为trade模式  
    :param cycle 周期天数，需前置参数 非daily和非nature  
    注：部分退市股后代码被重新使用的股票，其退市后价格为0，其收益率则计算为-1，为此将-1替换为NaN  
    """    if frequency != 'daily':  
        if type == 'nature':  
            close0 = data.copy()  
            close0.index = pd.DatetimeIndex(close0.index)  
            close = close0.resample(frequency).last()  
            first_date_df = pd.DataFrame(close0.index.tolist(), index=close0.index,  
                                         columns=['first_trading_date']).resample(frequency).first()  
            close.index = [date.strftime('%Y%m%d') for date in first_date_df['first_trading_date']]  
        else:  
            close = data.copy()  
            if cycle is None:  
                length = 5 if frequency=='W' else 21  
            else:  
                length = cycle  
            close = close.iloc[::-length,:].sort_index()  
    else:  
        close = data.copy()  
    rate = close.rolling(window=2, min_periods=2).apply(lambda x: x[1] / x[0] - 1).iloc[1:,:].replace([-1, np.inf, -np.inf], np.nan).fillna(0)  
    return rate  
  
  
def cal_StyleFactor_Residual_Volatility(context, security,date,Epsilon,coef1=0.74,coef2=0.16,coef3=0.10,coef1_n=252,coef1_halflife=42,coef2_n=12):  
    """  
    计算风格因子 Residual_Volatility    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Epsilon 残差收益率（在计算beta是返回的参数）  
    :param coef1 第一个子因子（DASTD）的权重系数，默认为0.74  
    :param coef2 第二个子因子（CMRA）的权重系数，默认为0.16  
    :param coef3 第三个子因子（HSIGMA）的权重系数，默认为0.10  
    :param coef1_n 第一个子因子的估计区间，默认为252个交易日  
    :param coef1_halflife 第一个子因子的半衰期，默认为42个交易日  
    :param coef2_n 第二个子因子的估计区间，默认为12个交易月（每月为21个交易日）  
    return:Residual_Volatility(DataFrame)    注：三个子因子叠加前需要做标准化处理  
       CMRA的计算参考CNE6的方法，未采用CNE5的方法  
    """## 计算DASTD  
    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=coef1_n+1)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
      
    Residual_volatility_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-coef1_n:,:]  
    StockReturnAlpha_ -= StockReturnAlpha_.mean()  
    # 计算权重  
    lambda_ = np.power(0.5,1/coef1_halflife)  
    W = [np.power(lambda_,t) for t in range(coef1_n-1,-1,-1)]  
    Weight = pd.DataFrame(W,index=StockReturnAlpha_.index)  
    # 计算子因子  
    DASTD = np.dot((StockReturnAlpha_*StockReturnAlpha_).T,Weight)/len(Weight)  
    DASTD = pd.DataFrame(DASTD,index=StockReturnAlpha_.columns)  
    DASTD = standardlize(DASTD)  
  
    ## 计算CMRA  
    StockReturnMonth = get_return(close.loc[:date,:].iloc[-coef1_n:,:], frequency='M', type='trade').iloc[-coef2_n:,:]  
    StockReturnMonth = StockReturnMonth.sort_index(ascending=False)  
    RiskFreeReturnMonth = np.power(1+RiskFreeReturn, 1 / 12) - 1  
    Z = (np.log(1+StockReturnMonth)-np.log(1+RiskFreeReturnMonth)).cumsum()  
    # zmin = 1+Z.min()  
    # zmin[zmin<0]=0.0001    # CMRA = np.log(1+Z.max())-np.log(zmin)# CNE5的方法  
    CMRA = Z.max()-Z.min()# CNE6的方法  
    CMRA = pd.DataFrame(CMRA,index=StockReturnAlpha_.columns)  
    CMRA = standardlize(CMRA)  
    # 计算HSIGMA  
    HSIGMA = Epsilon[date].std()  
    HSIGMA = pd.DataFrame(HSIGMA,index=StockReturnAlpha_.columns)  
    HSIGMA = standardlize(HSIGMA)  
  
    # 整合DASTD、CMRA和HSIGMA  
    Residual_volatility = coef1*DASTD+coef2*CMRA+coef3*HSIGMA  
    Residual_volatility.columns = [date]  
    Residual_volatility_df = pd.concat([Residual_volatility_df,Residual_volatility],axis=1)  
    return Residual_volatility_df  
  
  
  
def get_weight(context, security, start_date, end_date, ratio=True):  
    """  
    获取股票市值权重(流通市值)  
    :param security：股票代码（这里是secucode）  
    :param start_date:开始日期  
    :param end_date:结束日期  
    :param ratio:是否返回比率的形式，默认为True  
    return:返回市值权重  
    """    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_date, end_date=end_date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
      
    if not ratio:  
        return NegotiableMV_df  
    weight = NegotiableMV_df.astype(float) / NegotiableMV_df.sum()[0]  
    return weight  
  
  
def cal_StyleFactor_NonLinear_Size(context, security, date, Size):  
    """  
    计算风格因子 NonLinear_Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Size Size因子  
    return:NonLinear_Size(DataFrame)    """    NonLinear_Size_df = pd.DataFrame()    
    Size_ = Size.to_frame()  
    NonLinear_Size = np.power(standardlize(Size_, date),3)  
    x = sm.add_constant(Size_)  
    sigma = get_weight(context, list(Size_.index), date, date, ratio=True).T  
    NonLinear_Size = sm.WLS(NonLinear_Size, x, weights = sigma).fit().resid  
    NonLinear_Size = pd.DataFrame(NonLinear_Size,columns=[date])  
    NonLinear_Size_df = pd.concat([NonLinear_Size_df,NonLinear_Size],axis=1)  
    return NonLinear_Size_df  
  
  
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])  
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])  
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])  
    if inclusive:  
        data = np.minimum(data, np.asarray(uplimit))   
        data = np.maximum(data, np.asarray(downlimit))   
    else:  
        data = np.minimum(data, np.asarray(nan_df))   
        data = np.maximum(data, np.asarray(nan_df))   
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
  
def neutralize(data):  
    """  
    市值+行业中性化(data以code*date的形式)  
    :param data：待处理数据  
    :param date：目标日期  
    """    data = data.dropna(how='any')  
    security = list(data.index)  
    residual_df = pd.DataFrame()  
    month_date = []  
    for date in data.columns:  
        month = date[:7]  
        if month not in month_date:  
            month_date.append(month)  
            # 查询标的市值，默认历史回溯天数count=1  
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)  
            market_value = market_value.drop(['trade_date'], axis=1)  
            market_value['tot_mv'] = np.log(market_value['tot_mv'])  
            market_value = market_value.set_index('symbol')  
            # 查询标的行业代码，以哑变量格式存储  
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类  
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)  
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)  
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列  
            symbol_industry = symbol_industry.groupby('symbol').sum()  
            # 采用最小二乘法进行多元线性回归，计算残差  
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')  
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')  
        X = sm.add_constant(df_reg.iloc[:, 1:])  
        Y = df_reg.iloc[:, 0]  
        MLR_model = sm.OLS(Y, X)  
        residual = MLR_model.fit().resid  
        residual = residual.to_frame(name=date).rename_axis('symbol')  
        residual_df = pd.concat([residual_df,residual],axis=1)  
          
    return residual_df  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-12-29 08:00:00',  
        backtest_end_time='2024-12-31 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        

# barra模型因子六
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  

def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 每周最后一个交易日移仓换股  
    if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # # 每月最后一个交易日移仓换股  
    # if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        Book_To_Price = cal_StyleFactor_Book_To_Price(security=all_stocks_str, date=last_date)  
        # 去极值、标准化、有效样本数量限制、市值中性化  
        alpha_factor = winsorize_med(Book_To_Price)  
        alpha_factor = standardlize(alpha_factor)  
        alpha_factor = neutralize(alpha_factor)  
        # 获取最大因子的前N只股票  
        to_buy = list(alpha_factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(last_date,ascending=False)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Size(context, security, date):  
    """  
    计算风格因子 Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 目标日期（int）  
    return:Size(DataFrame)    """    # get_fundamentals_n中end_date对标的是财报季度最后一天，而非财报发布日期，所以获取的数据会有未来数据  
    dfdata = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', start_date=date, end_date=date, df=True)    
    dfdata.set_index(['symbol'],inplace=True)  
    Size = dfdata['tot_mv']  
    Size = np.log(Size).replace([-np.inf,np.inf],np.nan).fillna(0)  
    return Size  
  
  
def cal_StyleFactor_Beta(context, security, date, periods=252, helf_life=63):  
    """  
    计算风格因子 Beta    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param periods 估计区间，默认为252个交易日  
    :param helf_life 半衰期，默认为63个交易日  
    return:Alpha,Beta(DataFrame)    注：无风险收益以2%代替，市场收益以全A股票收益按流通市值加权,后边函数亦是  
    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    Alpha_df,Beta_df,Epsilon_df = pd.DataFrame(),pd.DataFrame(),pd.DataFrame  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=periods)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close.shift(1)/close-1).iloc[1:,:]  
    StockReturn.index = [date.strftime('%Y-%m-%d') for date in StockReturn.index]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
    # 计算市场超额收益率  
    NegotiableMV = NegotiableMV_df.loc[start_time:date,:]  
    NegotiableMVRatio = (NegotiableMV.T.astype(float)/NegotiableMV.sum(axis=1)).T  
    com_stocks = list(set(StockReturn.columns)&set(NegotiableMVRatio.columns))  
    com_date = list(set(StockReturn.index)&set(NegotiableMVRatio.index))  
    StockReturn_ = StockReturn.loc[com_date,com_stocks].fillna(0).sort_index()  
    NegotiableMVRatio = NegotiableMVRatio.loc[com_date,com_stocks].fillna(0).sort_index()  
    MarketReturn = np.dot(StockReturn_,NegotiableMVRatio.T)  
  
    MarketReturn = pd.DataFrame(MarketReturn,index=NegotiableMVRatio.index,columns=NegotiableMVRatio.index)  
    MarketReturnAlpha = np.diag(MarketReturn-RiskFreeReturnDaily)  
    StockReturnAlpha = (StockReturnAlpha.T.reindex(com_stocks).T).fillna(method='ffill')  
    # 以解析解求解  
    lambda_ = np.power(0.5,1/helf_life)  
    W_list = [np.power(lambda_,t) for t in range(periods-1,-1,-1)]  
    W = np.diag(W_list)  
    W = pd.DataFrame(W,index=StockReturnAlpha.index,columns=StockReturnAlpha.index)  
    MarketReturnAlpha = pd.DataFrame(MarketReturnAlpha, index=StockReturnAlpha.index)  
    ones = pd.DataFrame([1] * periods, index=StockReturnAlpha.index)  
    X = pd.concat([ones,MarketReturnAlpha],axis=1)  
    out_ = np.dot(np.dot(np.linalg.inv(np.dot(np.dot(X.T,W),X)),X.T),W)  
    Alpha = pd.DataFrame()  
    Beta = pd.DataFrame()  
    for i in range(StockReturnAlpha.shape[1]):  
        Y = StockReturnAlpha.iloc[:,i]  
        out = np.dot(out_,Y)  
        Alpha = pd.concat([Alpha,pd.DataFrame(out[0],index=['Alpha'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
        Beta = pd.concat([Beta,pd.DataFrame(out[1],index=['Beta'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
    Alpha = Alpha.T  
    Alpha.columns = [date]  
    Beta = Beta.T  
    Beta.columns = [date]  
    AlphaDF = pd.DataFrame(np.outer([1]*len(MarketReturnAlpha),Alpha),index=MarketReturnAlpha.index,columns=Alpha.index)  
    Epsilon = StockReturnAlpha-(AlphaDF+np.outer(MarketReturnAlpha,Beta))  
    Alpha_df = pd.concat([Alpha_df,Alpha],axis=1)  
    Beta_df = pd.concat([Beta_df,Beta],axis=1)  
    Epsilon_dict = {date:Epsilon}  
    return Alpha_df,Beta_df,Epsilon_dict  
  
  
def cal_StyleFactor_Momentum(context, security, date, T=504, L=21, helf_life=126):  
    """  
    计算风格因子 Momentum    :param security 待筛选股票池（list)  
    :param date 日期  
    :param T:估计区间，默认为504个交易日  
    :param L：滞后区间，默认为21个交易日  
    :param helf_life：半衰期，默认为126个交易日  
    return:Momentum(DataFrame)    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=L+T)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = np.log(StockReturn+1)+np.log(RiskFreeReturnDaily+1)  
    StockReturnAlpha.index = [date.strftime('%Y-%m-%d') for date in StockReturnAlpha.index]  
    lambda_ = np.power(0.5,1/helf_life)  
      
    Momentum_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-T:,:]  
    W = pd.DataFrame([np.power(lambda_,t) for t in range(T-1,-1,-1)],index=StockReturnAlpha_.index)  
    rstr = np.dot(StockReturnAlpha_.T,W)  
    Momentum = pd.DataFrame(rstr,index=StockReturnAlpha_.columns,columns=[date])  
    Momentum_df = pd.concat([Momentum_df,Momentum],axis=1)  
    return Momentum_df  
  
  
def get_return(data, frequency='daily', type='trade', cycle=None):  
    """  
    获取股票区间收益率  
    :param data：收盘价（df：日期*股票代码）  
    :param frequency 频率，daily为日频，W为周频，M为月频，other为其他，默认为月频  
    :param type 频率类型，nature为自然周、自然月；trade为交易周（追溯5天）、交易月（追溯21天）,默认为trade模式  
    :param cycle 周期天数，需前置参数 非daily和非nature  
    注：部分退市股后代码被重新使用的股票，其退市后价格为0，其收益率则计算为-1，为此将-1替换为NaN  
    """    if frequency != 'daily':  
        if type == 'nature':  
            close0 = data.copy()  
            close0.index = pd.DatetimeIndex(close0.index)  
            close = close0.resample(frequency).last()  
            first_date_df = pd.DataFrame(close0.index.tolist(), index=close0.index,  
                                         columns=['first_trading_date']).resample(frequency).first()  
            close.index = [date.strftime('%Y%m%d') for date in first_date_df['first_trading_date']]  
        else:  
            close = data.copy()  
            if cycle is None:  
                length = 5 if frequency=='W' else 21  
            else:  
                length = cycle  
            close = close.iloc[::-length,:].sort_index()  
    else:  
        close = data.copy()  
    rate = close.rolling(window=2, min_periods=2).apply(lambda x: x[1] / x[0] - 1).iloc[1:,:].replace([-1, np.inf, -np.inf], np.nan).fillna(0)  
    return rate  
  
  
def cal_StyleFactor_Residual_Volatility(context, security,date,Epsilon,coef1=0.74,coef2=0.16,coef3=0.10,coef1_n=252,coef1_halflife=42,coef2_n=12):  
    """  
    计算风格因子 Residual_Volatility    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Epsilon 残差收益率（在计算beta是返回的参数）  
    :param coef1 第一个子因子（DASTD）的权重系数，默认为0.74  
    :param coef2 第二个子因子（CMRA）的权重系数，默认为0.16  
    :param coef3 第三个子因子（HSIGMA）的权重系数，默认为0.10  
    :param coef1_n 第一个子因子的估计区间，默认为252个交易日  
    :param coef1_halflife 第一个子因子的半衰期，默认为42个交易日  
    :param coef2_n 第二个子因子的估计区间，默认为12个交易月（每月为21个交易日）  
    return:Residual_Volatility(DataFrame)    注：三个子因子叠加前需要做标准化处理  
       CMRA的计算参考CNE6的方法，未采用CNE5的方法  
    """## 计算DASTD  
    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=coef1_n+1)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
      
    Residual_volatility_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-coef1_n:,:]  
    StockReturnAlpha_ -= StockReturnAlpha_.mean()  
    # 计算权重  
    lambda_ = np.power(0.5,1/coef1_halflife)  
    W = [np.power(lambda_,t) for t in range(coef1_n-1,-1,-1)]  
    Weight = pd.DataFrame(W,index=StockReturnAlpha_.index)  
    # 计算子因子  
    DASTD = np.dot((StockReturnAlpha_*StockReturnAlpha_).T,Weight)/len(Weight)  
    DASTD = pd.DataFrame(DASTD,index=StockReturnAlpha_.columns)  
    DASTD = standardlize(DASTD)  
  
    ## 计算CMRA  
    StockReturnMonth = get_return(close.loc[:date,:].iloc[-coef1_n:,:], frequency='M', type='trade').iloc[-coef2_n:,:]  
    StockReturnMonth = StockReturnMonth.sort_index(ascending=False)  
    RiskFreeReturnMonth = np.power(1+RiskFreeReturn, 1 / 12) - 1  
    Z = (np.log(1+StockReturnMonth)-np.log(1+RiskFreeReturnMonth)).cumsum()  
    # zmin = 1+Z.min()  
    # zmin[zmin<0]=0.0001    # CMRA = np.log(1+Z.max())-np.log(zmin)# CNE5的方法  
    CMRA = Z.max()-Z.min()# CNE6的方法  
    CMRA = pd.DataFrame(CMRA,index=StockReturnAlpha_.columns)  
    CMRA = standardlize(CMRA)  
    # 计算HSIGMA  
    HSIGMA = Epsilon[date].std()  
    HSIGMA = pd.DataFrame(HSIGMA,index=StockReturnAlpha_.columns)  
    HSIGMA = standardlize(HSIGMA)  
  
    # 整合DASTD、CMRA和HSIGMA  
    Residual_volatility = coef1*DASTD+coef2*CMRA+coef3*HSIGMA  
    Residual_volatility.columns = [date]  
    Residual_volatility_df = pd.concat([Residual_volatility_df,Residual_volatility],axis=1)  
    return Residual_volatility_df  
  
  
  
def get_weight(context, security, start_date, end_date, ratio=True):  
    """  
    获取股票市值权重(流通市值)  
    :param security：股票代码（这里是secucode）  
    :param start_date:开始日期  
    :param end_date:结束日期  
    :param ratio:是否返回比率的形式，默认为True  
    return:返回市值权重  
    """    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_date, end_date=end_date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
      
    if not ratio:  
        return NegotiableMV_df  
    weight = NegotiableMV_df.astype(float) / NegotiableMV_df.sum()[0]  
    return weight  
  
  
def cal_StyleFactor_NonLinear_Size(context, security, date, Size):  
    """  
    计算风格因子 NonLinear_Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Size Size因子  
    return:NonLinear_Size(DataFrame)    """    NonLinear_Size_df = pd.DataFrame()    
    Size_ = Size.to_frame()  
    NonLinear_Size = np.power(standardlize(Size_, date),3)  
    x = sm.add_constant(Size_)  
    sigma = get_weight(context, list(Size_.index), date, date, ratio=True).T  
    NonLinear_Size = sm.WLS(NonLinear_Size, x, weights = sigma).fit().resid  
    NonLinear_Size = pd.DataFrame(NonLinear_Size,columns=[date])  
    NonLinear_Size_df = pd.concat([NonLinear_Size_df,NonLinear_Size],axis=1)  
    return NonLinear_Size_df  
  
  
def cal_StyleFactor_Book_To_Price(security,date):  
    """  
    计算风格因子 Book_To_Price    :param security 待筛选股票池（list)  
    :param date 开始日期  
    return:Book_To_Price(DataFrame)    """    total_data = pd.DataFrame()  
    # 获取总市值  
    tot_mv_df = stk_get_daily_mktvalue_pt(symbols=security, fields='tot_mv', trade_date=date, df=True)[['trade_date','symbol','tot_mv']]  
    # 获取 归属于母公司股东权益合计   
ttl_eqy_pcom_df = stk_get_fundamentals_balance_pt(symbols=security, fields='ttl_eqy_pcom', data_type=None, date=date, df=True)[['symbol','ttl_eqy_pcom']]      
    data = tot_mv_df.merge(ttl_eqy_pcom_df,on=['symbol'])  
    total_data = pd.concat([total_data,data])  
    total_data['Book_To_Price'] = total_data['ttl_eqy_pcom']/total_data['tot_mv']  
    total_data.set_index(['trade_date','symbol'],inplace=True)  
    Book_To_Price = total_data[['Book_To_Price']].unstack()  
    Book_To_Price.columns = Book_To_Price.columns.droplevel(level=0)  
    return Book_To_Price.T  
  
  
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])  
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])  
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])  
    if inclusive:  
        data = np.minimum(data, np.asarray(uplimit))   
        data = np.maximum(data, np.asarray(downlimit))   
    else:  
        data = np.minimum(data, np.asarray(nan_df))   
        data = np.maximum(data, np.asarray(nan_df))   
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
  
def neutralize(data):  
    """  
    市值+行业中性化(data以code*date的形式)  
    :param data：待处理数据  
    :param date：目标日期  
    """    data = data.dropna(how='any')  
    security = list(data.index)  
    residual_df = pd.DataFrame()  
    month_date = []  
    for date in data.columns:  
        month = date[:7]  
        if month not in month_date:  
            month_date.append(month)  
            # 查询标的市值，默认历史回溯天数count=1  
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)  
            market_value = market_value.drop(['trade_date'], axis=1)  
            market_value['tot_mv'] = np.log(market_value['tot_mv'])  
            market_value = market_value.set_index('symbol')  
            # 查询标的行业代码，以哑变量格式存储  
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类  
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)  
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)  
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列  
            symbol_industry = symbol_industry.groupby('symbol').sum()  
            # 采用最小二乘法进行多元线性回归，计算残差  
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')  
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')  
        X = sm.add_constant(df_reg.iloc[:, 1:])  
        Y = df_reg.iloc[:, 0]  
        MLR_model = sm.OLS(Y, X)  
        residual = MLR_model.fit().resid  
        residual = residual.to_frame(name=date).rename_axis('symbol')  
        residual_df = pd.concat([residual_df,residual],axis=1)  
          
    return residual_df  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-12-29 08:00:00',  
        backtest_end_time='2024-12-31 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        )

# barra模型因子七
## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import math  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
from datetime import datetime,timedelta  
  
def init(context):  
    # 最大持股数量  
    context.holding_num = 50  
    # 目标标的（All为全市场)  
    context.base_security='ALL'  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')   
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # 上一个交易日  
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
    # # 每周最后一个交易日移仓换股  
    # if context.now.weekday()>datetime.strptime(next_date, '%Y-%m-%d').weekday():  
    # 每月最后一个交易日移仓换股  
    if context.now.month!=datetime.strptime(next_date, '%Y-%m-%d').month:  
        if context.base_security=='ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks,all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = list(stk_get_index_constituents(index=context.base_security, trade_date=last_date)['symbol'])  
            all_stocks_str = ','.join(all_stocks)  
        # 计算因子  
        Liquidity = cal_StyleFactor_Liquidity(context, security=all_stocks_str, date=last_date)  
        # 去极值、标准化、有效样本数量限制、市值中性化  
        alpha_factor = winsorize_med(Liquidity)  
        alpha_factor = standardlize(alpha_factor)  
        alpha_factor = neutralize(alpha_factor)  
        # 获取最大因子的前N只股票  
        to_buy = list(alpha_factor.replace([-np.inf,np.inf],np.nan).dropna().sort_values(last_date,ascending=False)[:context.holding_num].index)  
        print(context.now,'待买入股票{}只：{}'.format(len(to_buy),to_buy))  
  
        ## 股票交易  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=holding_symbol, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=holding_symbol, trade_date=today, skip_st=False)  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_price)==0 or len(new_info)==0:continue  
                    new_price = new_price[-1]['close']  
                    if new_info[0]['lower_limit']!=new_price:             # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in holding_symbol:  
                if symbol not in to_buy:  
                    new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                    if len(new_info)==0:continue  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        if context.mode==MODE_BACKTEST:  
            data_price = history(symbol=to_buy, frequency='1d', start_time=today,  end_time=today, adjust=ADJUST_NONE, df=False)  
            data_info = get_symbols(sec_type1=1010, symbols=to_buy, trade_date=today, skip_st=False)  
            for symbol in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if new_info[0]['upper_limit']!=new_price:# 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
        else:  
            for symbol in to_buy:  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_info)==0:continue  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=0.98/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                 
    if code.startswith('SHSE.68'):  
        trade_volume = int(np.floor(available_amount/price))  
        trade_volume = 0 if trade_volume<200 else int(np.round(trade_volume/100)*100)  
    else:  
        trade_volume = int(np.round(available_amount/price/100)*100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def get_trading_dates_new(context, exchange, start_date, end_date):  
    """  
    获取两个日期间的交易日  
    """    start_date = pd.Timestamp(start_date)  
    start_date_str = start_date.strftime('%Y-%m-%d')  
    end_date = pd.Timestamp(end_date)  
    end_date_str = end_date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=end_date.year)  
    # 判断start_date是否存在于context.trading_dates中  
    if start_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_date.year, end_year=int(context.trading_dates['date'].iloc[-1][:4])+1)  
    # 判断end_date是否存在于context.trading_dates中  
    if end_date_str not in list(context.trading_dates['date']):  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=int(context.trading_dates['date'].iloc[0][:4])-1, end_year=end_date.year)  
    # 计算start_date所在得index位置  
    start_date_index = context.trading_dates[context.trading_dates['date']==start_date_str].index[0]  
    # 计算end_date所在得index位置  
    end_date_index = context.trading_dates[context.trading_dates['date']==end_date_str].index[0]  
    # 计算区间得交易日  
    trading_dates = context.trading_dates.loc[start_date_index:end_date_index,'trade_date'].tolist()  
    trading_dates = [date for date in trading_dates if date!='']  
    return trading_dates  
  
  
def cal_StyleFactor_Size(context, security, date):  
    """  
    计算风格因子 Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 目标日期（int）  
    return:Size(DataFrame)    """    # get_fundamentals_n中end_date对标的是财报季度最后一天，而非财报发布日期，所以获取的数据会有未来数据  
    dfdata = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='tot_mv', start_date=date, end_date=date, df=True)    
    dfdata.set_index(['symbol'],inplace=True)  
    Size = dfdata['tot_mv']  
    Size = np.log(Size).replace([-np.inf,np.inf],np.nan).fillna(0)  
    return Size  
  
  
def cal_StyleFactor_Beta(context, security, date, periods=252, helf_life=63):  
    """  
    计算风格因子 Beta    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param periods 估计区间，默认为252个交易日  
    :param helf_life 半衰期，默认为63个交易日  
    return:Alpha,Beta(DataFrame)    注：无风险收益以2%代替，市场收益以全A股票收益按流通市值加权,后边函数亦是  
    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    Alpha_df,Beta_df,Epsilon_df = pd.DataFrame(),pd.DataFrame(),pd.DataFrame  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=periods)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close.shift(1)/close-1).iloc[1:,:]  
    StockReturn.index = [date.strftime('%Y-%m-%d') for date in StockReturn.index]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
    # 计算市场超额收益率  
    NegotiableMV = NegotiableMV_df.loc[start_time:date,:]  
    NegotiableMVRatio = (NegotiableMV.T.astype(float)/NegotiableMV.sum(axis=1)).T  
    com_stocks = list(set(StockReturn.columns)&set(NegotiableMVRatio.columns))  
    com_date = list(set(StockReturn.index)&set(NegotiableMVRatio.index))  
    StockReturn_ = StockReturn.loc[com_date,com_stocks].fillna(0).sort_index()  
    NegotiableMVRatio = NegotiableMVRatio.loc[com_date,com_stocks].fillna(0).sort_index()  
    MarketReturn = np.dot(StockReturn_,NegotiableMVRatio.T)  
  
    MarketReturn = pd.DataFrame(MarketReturn,index=NegotiableMVRatio.index,columns=NegotiableMVRatio.index)  
    MarketReturnAlpha = np.diag(MarketReturn-RiskFreeReturnDaily)  
    StockReturnAlpha = (StockReturnAlpha.T.reindex(com_stocks).T).fillna(method='ffill')  
    # 以解析解求解  
    lambda_ = np.power(0.5,1/helf_life)  
    W_list = [np.power(lambda_,t) for t in range(periods-1,-1,-1)]  
    W = np.diag(W_list)  
    W = pd.DataFrame(W,index=StockReturnAlpha.index,columns=StockReturnAlpha.index)  
    MarketReturnAlpha = pd.DataFrame(MarketReturnAlpha, index=StockReturnAlpha.index)  
    ones = pd.DataFrame([1] * periods, index=StockReturnAlpha.index)  
    X = pd.concat([ones,MarketReturnAlpha],axis=1)  
    out_ = np.dot(np.dot(np.linalg.inv(np.dot(np.dot(X.T,W),X)),X.T),W)  
    Alpha = pd.DataFrame()  
    Beta = pd.DataFrame()  
    for i in range(StockReturnAlpha.shape[1]):  
        Y = StockReturnAlpha.iloc[:,i]  
        out = np.dot(out_,Y)  
        Alpha = pd.concat([Alpha,pd.DataFrame(out[0],index=['Alpha'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
        Beta = pd.concat([Beta,pd.DataFrame(out[1],index=['Beta'],columns=[StockReturnAlpha.columns[i]])],axis=1)  
    Alpha = Alpha.T  
    Alpha.columns = [date]  
    Beta = Beta.T  
    Beta.columns = [date]  
    AlphaDF = pd.DataFrame(np.outer([1]*len(MarketReturnAlpha),Alpha),index=MarketReturnAlpha.index,columns=Alpha.index)  
    Epsilon = StockReturnAlpha-(AlphaDF+np.outer(MarketReturnAlpha,Beta))  
    Alpha_df = pd.concat([Alpha_df,Alpha],axis=1)  
    Beta_df = pd.concat([Beta_df,Beta],axis=1)  
    Epsilon_dict = {date:Epsilon}  
    return Alpha_df,Beta_df,Epsilon_dict  
  
  
def cal_StyleFactor_Momentum(context, security, date, T=504, L=21, helf_life=126):  
    """  
    计算风格因子 Momentum    :param security 待筛选股票池（list)  
    :param date 日期  
    :param T:估计区间，默认为504个交易日  
    :param L：滞后区间，默认为21个交易日  
    :param helf_life：半衰期，默认为126个交易日  
    return:Momentum(DataFrame)    """    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=L+T)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = np.log(StockReturn+1)+np.log(RiskFreeReturnDaily+1)  
    StockReturnAlpha.index = [date.strftime('%Y-%m-%d') for date in StockReturnAlpha.index]  
    lambda_ = np.power(0.5,1/helf_life)  
      
    Momentum_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-T:,:]  
    W = pd.DataFrame([np.power(lambda_,t) for t in range(T-1,-1,-1)],index=StockReturnAlpha_.index)  
    rstr = np.dot(StockReturnAlpha_.T,W)  
    Momentum = pd.DataFrame(rstr,index=StockReturnAlpha_.columns,columns=[date])  
    Momentum_df = pd.concat([Momentum_df,Momentum],axis=1)  
    return Momentum_df  
  
  
def get_return(data, frequency='daily', type='trade', cycle=None):  
    """  
    获取股票区间收益率  
    :param data：收盘价（df：日期*股票代码）  
    :param frequency 频率，daily为日频，W为周频，M为月频，other为其他，默认为月频  
    :param type 频率类型，nature为自然周、自然月；trade为交易周（追溯5天）、交易月（追溯21天）,默认为trade模式  
    :param cycle 周期天数，需前置参数 非daily和非nature  
    注：部分退市股后代码被重新使用的股票，其退市后价格为0，其收益率则计算为-1，为此将-1替换为NaN  
    """    if frequency != 'daily':  
        if type == 'nature':  
            close0 = data.copy()  
            close0.index = pd.DatetimeIndex(close0.index)  
            close = close0.resample(frequency).last()  
            first_date_df = pd.DataFrame(close0.index.tolist(), index=close0.index,  
                                         columns=['first_trading_date']).resample(frequency).first()  
            close.index = [date.strftime('%Y%m%d') for date in first_date_df['first_trading_date']]  
        else:  
            close = data.copy()  
            if cycle is None:  
                length = 5 if frequency=='W' else 21  
            else:  
                length = cycle  
            close = close.iloc[::-length,:].sort_index()  
    else:  
        close = data.copy()  
    rate = close.rolling(window=2, min_periods=2).apply(lambda x: x[1] / x[0] - 1).iloc[1:,:].replace([-1, np.inf, -np.inf], np.nan).fillna(0)  
    return rate  
  
  
def cal_StyleFactor_Residual_Volatility(context, security,date,Epsilon,coef1=0.74,coef2=0.16,coef3=0.10,coef1_n=252,coef1_halflife=42,coef2_n=12):  
    """  
    计算风格因子 Residual_Volatility    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Epsilon 残差收益率（在计算beta是返回的参数）  
    :param coef1 第一个子因子（DASTD）的权重系数，默认为0.74  
    :param coef2 第二个子因子（CMRA）的权重系数，默认为0.16  
    :param coef3 第三个子因子（HSIGMA）的权重系数，默认为0.10  
    :param coef1_n 第一个子因子的估计区间，默认为252个交易日  
    :param coef1_halflife 第一个子因子的半衰期，默认为42个交易日  
    :param coef2_n 第二个子因子的估计区间，默认为12个交易月（每月为21个交易日）  
    return:Residual_Volatility(DataFrame)    注：三个子因子叠加前需要做标准化处理  
       CMRA的计算参考CNE6的方法，未采用CNE5的方法  
    """## 计算DASTD  
    RiskFreeReturn = 0.02  
    RiskFreeReturnDaily = np.power(1+RiskFreeReturn, 1 / 365) - 1  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=coef1_n+1)  
    start_time = date_list[0]  
    close = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,close',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
    StockReturn = (close/close.shift(1)-1).iloc[1:,:]  
    StockReturnAlpha = StockReturn-RiskFreeReturnDaily  
      
    Residual_volatility_df = pd.DataFrame()  
    StockReturnAlpha_ = StockReturnAlpha.loc[:date,:].iloc[-coef1_n:,:]  
    StockReturnAlpha_ -= StockReturnAlpha_.mean()  
    # 计算权重  
    lambda_ = np.power(0.5,1/coef1_halflife)  
    W = [np.power(lambda_,t) for t in range(coef1_n-1,-1,-1)]  
    Weight = pd.DataFrame(W,index=StockReturnAlpha_.index)  
    # 计算子因子  
    DASTD = np.dot((StockReturnAlpha_*StockReturnAlpha_).T,Weight)/len(Weight)  
    DASTD = pd.DataFrame(DASTD,index=StockReturnAlpha_.columns)  
    DASTD = standardlize(DASTD)  
  
    ## 计算CMRA  
    StockReturnMonth = get_return(close.loc[:date,:].iloc[-coef1_n:,:], frequency='M', type='trade').iloc[-coef2_n:,:]  
    StockReturnMonth = StockReturnMonth.sort_index(ascending=False)  
    RiskFreeReturnMonth = np.power(1+RiskFreeReturn, 1 / 12) - 1  
    Z = (np.log(1+StockReturnMonth)-np.log(1+RiskFreeReturnMonth)).cumsum()  
    # zmin = 1+Z.min()  
    # zmin[zmin<0]=0.0001    # CMRA = np.log(1+Z.max())-np.log(zmin)# CNE5的方法  
    CMRA = Z.max()-Z.min()# CNE6的方法  
    CMRA = pd.DataFrame(CMRA,index=StockReturnAlpha_.columns)  
    CMRA = standardlize(CMRA)  
    # 计算HSIGMA  
    HSIGMA = Epsilon[date].std()  
    HSIGMA = pd.DataFrame(HSIGMA,index=StockReturnAlpha_.columns)  
    HSIGMA = standardlize(HSIGMA)  
  
    # 整合DASTD、CMRA和HSIGMA  
    Residual_volatility = coef1*DASTD+coef2*CMRA+coef3*HSIGMA  
    Residual_volatility.columns = [date]  
    Residual_volatility_df = pd.concat([Residual_volatility_df,Residual_volatility],axis=1)  
    return Residual_volatility_df  
  
  
  
def get_weight(context, security, start_date, end_date, ratio=True):  
    """  
    获取股票市值权重(流通市值)  
    :param security：股票代码（这里是secucode）  
    :param start_date:开始日期  
    :param end_date:结束日期  
    :param ratio:是否返回比率的形式，默认为True  
    return:返回市值权重  
    """    # 获取流通市值  
    NegotiableMV_df = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_date, end_date=end_date, df=True)      
    NegotiableMV_df.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV_df = NegotiableMV_df.unstack()  
    NegotiableMV_df.columns = NegotiableMV_df.columns.droplevel(level=0)  
      
    if not ratio:  
        return NegotiableMV_df  
    weight = NegotiableMV_df.astype(float) / NegotiableMV_df.sum()[0]  
    return weight  
  
  
def cal_StyleFactor_NonLinear_Size(context, security, date, Size):  
    """  
    计算风格因子 NonLinear_Size    :param security 待筛选股票池（list)（这里是secucode）  
    :param date 日期  
    :param Size Size因子  
    return:NonLinear_Size(DataFrame)    """    NonLinear_Size_df = pd.DataFrame()    
    Size_ = Size.to_frame()  
    NonLinear_Size = np.power(standardlize(Size_, date),3)  
    x = sm.add_constant(Size_)  
    sigma = get_weight(context, list(Size_.index), date, date, ratio=True).T  
    NonLinear_Size = sm.WLS(NonLinear_Size, x, weights = sigma).fit().resid  
    NonLinear_Size = pd.DataFrame(NonLinear_Size,columns=[date])  
    NonLinear_Size_df = pd.concat([NonLinear_Size_df,NonLinear_Size],axis=1)  
    return NonLinear_Size_df  
  
  
def cal_StyleFactor_Book_To_Price(security,date):  
    """  
    计算风格因子 Book_To_Price    :param security 待筛选股票池（list)  
    :param date 开始日期  
    return:Book_To_Price(DataFrame)    """    total_data = pd.DataFrame()  
    # 获取总市值  
    tot_mv_df = stk_get_daily_mktvalue_pt(symbols=security, fields='tot_mv', trade_date=date, df=True)[['trade_date','symbol','tot_mv']]  
    # 获取 归属于母公司股东权益合计   
ttl_eqy_pcom_df = stk_get_fundamentals_balance_pt(symbols=security, fields='ttl_eqy_pcom', data_type=None, date=date, df=True)[['symbol','ttl_eqy_pcom']]      
    data = tot_mv_df.merge(ttl_eqy_pcom_df,on=['symbol'])  
    total_data = pd.concat([total_data,data])  
    total_data['Book_To_Price'] = total_data['ttl_eqy_pcom']/total_data['tot_mv']  
    total_data.set_index(['trade_date','symbol'],inplace=True)  
    Book_To_Price = total_data[['Book_To_Price']].unstack()  
    Book_To_Price.columns = Book_To_Price.columns.droplevel(level=0)  
    return Book_To_Price.T  
  
  
def cal_StyleFactor_Liquidity(context, security,date,coef1=0.35,coef2=0.35,coef3=0.30,coef1_n=21,coef2_n=3*21,coef3_n=12*21):  
    """  
    计算风格因子 Liquidity    :param security 待筛选股票池（list)  
    :param date 开始日期  
    :param coef1 第一个子因子（STOM）的权重系数，默认为0.35  
    :param coef2 第二个子因子（STOQ）的权重系数，默认为0.35  
    :param coef3 第三个子因子（STOA）的权重系数，默认为0.30  
    :param coef1_n 第一个子因子的估计区间，默认为21个交易日（一个交易月）  
    :param coef2_n 第二个子因子的估计区间，默认为3个交易月  
    :param coef3_n 第二个子因子的估计区间，默认为12个交易月  
    return:Liquidity(DataFrame)    """    max_length = coef3_n  
    # 计算个股超额收益率  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=max_length)  
    start_time = date_list[0]  
    AmountValue = history_new(security,frequency='1d',start_time=start_time,end_time=date,fields='symbol,eob,volume',adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=True)  
      
    # 获取流通市值  
    NegotiableMV = stk_get_daily_mktvalue_pt_new(context, symbols=security, fields='a_mv_ex_ltd', start_date=start_time, end_date=date, df=True)      
    NegotiableMV.set_index(['trade_date','symbol'],inplace=True)  
    NegotiableMV = NegotiableMV.unstack()  
    NegotiableMV.columns = NegotiableMV.columns.droplevel(level=0)  
    # 换手率  
    TurnoverRate = np.multiply(AmountValue,1/NegotiableMV)  
    # 月换手率  
    STOMs = np.log(TurnoverRate.rolling(window=coef1_n,min_periods=coef1_n).sum())  
    STOMs = STOMs.sort_index()  
    STOMs.index = [date.strftime('%Y-%m-%d') for date in STOMs.index]  
      
    # 季度换手率  
    STOQs = np.log(TurnoverRate.rolling(window=coef2_n,min_periods=coef2_n).sum())  
    STOQs = STOQs.sort_index()  
    STOQs.index = [date.strftime('%Y-%m-%d') for date in STOQs.index]  
      
    # 年度换手率  
    STOAs = np.log(TurnoverRate.rolling(window=coef3_n,min_periods=coef3_n).sum())  
    STOAs = STOAs.sort_index()  
    STOAs.index = [date.strftime('%Y-%m-%d') for date in STOAs.index]  
      
      
    Liquidity_df = pd.DataFrame()      
    new_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=coef3_n)  
    coef1_n_time = new_date_list[-coef1_n]  
    coef2_n_time = new_date_list[-coef2_n]  
    coef3_n_time = new_date_list[-coef3_n]  
    # 计算STOM  
    STOM = STOMs.loc[date,:]  
    STOM = standardlize(STOM)  
    # 计算STOQ  
    STOQ = STOQs.loc[date,:]  
    STOQ = standardlize(STOQ)  
    # 计算STOA  
    STOA = STOAs.loc[date,:]  
    STOA = standardlize(STOA)  
  
    # 整合STOM、STOQ、STOA  
    Liquidity = coef1*STOM+coef2*STOQ+coef3*STOA  
    Liquidity = pd.DataFrame(Liquidity,columns=[date]).fillna(0)  
    Liquidity_df = pd.concat([Liquidity_df,Liquidity],axis=1)  
    return Liquidity_df  
  
  
def stk_get_daily_mktvalue_pt_new(context, symbols, fields, start_date=None, end_date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，当有count时就不采用start_date,count为正整数  
    """    if counts!=None:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=counts)[0]  
    date_list = get_trading_dates_new(context, exchange='SZSE', start_date=start_date, end_date=end_date)  
      
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit=pd.DataFrame([mean_+std_*scale]*data.shape[0])  
    downlimit=pd.DataFrame([mean_-std_*scale]*data.shape[0])  
    nan_df = pd.DataFrame([[np.nan]*data.shape[1]]*data.shape[0])  
    if inclusive:  
        data = np.minimum(data, np.asarray(uplimit))   
        data = np.maximum(data, np.asarray(downlimit))   
    else:  
        data = np.minimum(data, np.asarray(nan_df))   
        data = np.maximum(data, np.asarray(nan_df))   
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def valid_sample_size(data,min_size_rate=2/3):  
    """  
    判断有效样本数量是否满足最低限制(data以code*date的形式)  
    :param min_size_rate:最小有效样本数量比例，默认最低比例为2/3  
    """    min_size = int(round(data.shape[1]*min_size_rate))  
    nan_data = np.isnan(data).sum(axis=1)  
    security = nan_data[nan_data<min_size].index  
    data = data.loc[security,:]  
    return data  
  
  
  
def neutralize(data):  
    """  
    市值+行业中性化(data以code*date的形式)  
    :param data：待处理数据  
    :param date：目标日期  
    """    data = data.dropna(how='any')  
    security = list(data.index)  
    residual_df = pd.DataFrame()  
    month_date = []  
    for date in data.columns:  
        month = date[:7]  
        if month not in month_date:  
            month_date.append(month)  
            # 查询标的市值，默认历史回溯天数count=1  
            market_value = stk_get_daily_mktvalue_pt(symbols=security,fields='tot_mv',trade_date=date,df=True)  
            market_value = market_value.drop(['trade_date'], axis=1)  
            market_value['tot_mv'] = np.log(market_value['tot_mv'])  
            market_value = market_value.set_index('symbol')  
            # 查询标的行业代码，以哑变量格式存储  
            symbol_industry = stk_get_symbol_industry(symbols=data.index, source="sw2021", level=1, date=date) # 采用申万一级行业分类  
            symbol_industry.drop(['sec_name','industry_name'], axis=1, inplace=True)  
            symbol_industry.drop_duplicates(subset=['symbol','industry_code'], keep='first', inplace=True)  
            symbol_industry = pd.get_dummies(symbol_industry, columns=['industry_code']) # 将行业代码单列转换为哑变量格式多列  
            symbol_industry = symbol_industry.groupby('symbol').sum()  
            # 采用最小二乘法进行多元线性回归，计算残差  
            x = pd.merge(market_value, symbol_industry, left_index=True, right_index=True, how='inner')  
        df_reg = pd.merge(data[[date]], x, left_index=True, right_index=True, how='inner')  
        X = sm.add_constant(df_reg.iloc[:, 1:])  
        Y = df_reg.iloc[:, 0]  
        MLR_model = sm.OLS(Y, X)  
        residual = MLR_model.fit().resid  
        residual = residual.to_frame(name=date).rename_axis('symbol')  
        residual_df = pd.concat([residual_df,residual],axis=1)  
          
    return residual_df  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='491e33e5-b6da-11ef-b022-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-12-29 08:00:00',  
        backtest_end_time='2024-12-31 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123  
        )

# 跨期套利
**策略说明：**

原版的跨期套利部分细节不够准确，新版本做了如下改动：

①、按实际佣金测算了回测的佣金，让结果更真实（附上一份交易所手续费表供查询）；

②、原策略委托有两种方式，一种是通过current查询价格进行限价单委托，另一种是采用order_close_all()进行市价委托；但回测中未订阅实时数据的情况下，current返回的是日频价格，市价单也是用的日频价格进行撮合，与实盘中进行盘口撮合的价格相差较大，均调整为以开盘价进行限价委托的方式；

③、修正计算历史价差时未包含最新价差。

## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
import multiprocessing  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。  
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差突破上轨时做空价差组合,突破下轨时做多价差组合。  
  
调整：成交价为昨日收盘价，不够准确，取消order_close_all，以开盘价限价委托  
'''  
  
  
def init(context):  
    context.contract_A = 'DCE.J'# 主力合约  
    context.contract_B = 'DCE.J22'# 次主力合约  
    context.main_contract = None# 具体的主力合约  
    context.minor_contract = None# 具体的次主力合约  
    # 设置回溯周期  
    # context.periods_time = 31  
    # # 布林带上下轨倍数  
    # context.boll_multiple = 1.5  
    # # 止盈止损倍数  
    # context.stoppoint_multiple = 3  
    # 清仓信号  
    context.close_all = False  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        minor_contract_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {}  
            for dic in main_contract_list:  
                context.main_contract_list[dic['trade_date']] = dic['symbol']  
        if len(minor_contract_list)>0:  
            context.minor_contract_list = {}  
            for dic in minor_contract_list:  
                context.minor_contract_list[dic['trade_date']] = dic['symbol']  
    # 设置定时任务：夜盘21点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):      
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约和次主力合约  
    date_list = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)  
    if context.now.hour>15:  
        date = date_list[0]   
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    pre_date = date_list[-1]  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list and date in context.minor_contract_list:  
        main_contract = context.main_contract_list[date]  
        minor_contract = context.minor_contract_list[date]  
    else:  
        main_contract = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)[0]['symbol']  
        minor_contract = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)[0]['symbol']  
    # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约  
    Account_positions = context.account().positions()   
    if main_contract!=context.main_contract or minor_contract!=context.minor_contract:  
        if Account_positions:  
            for posi in Account_positions:  
                if posi['symbol']==context.main_contract and main_contract!=context.main_contract:  
                    print('{}：主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract))  
                    context.close_all = True  
                if posi['symbol']==context.minor_contract and minor_contract!=context.minor_contract:  
                    print('{}：次主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract))  
                    context.close_all = True  
        # 更新主力合约  
        context.main_contract = main_contract  
        context.minor_contract = minor_contract  
    # 当context.close_all为True时，清仓  
    if context.close_all:  
        context.close_all = False  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            side = posi['side']  
            trade_side = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
            price_new = history(symbol=symbol, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
            order_volume(symbol=symbol, side=trade_side, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
        print('{}:平仓'.format(context.now))  
  
    # 获取历史数据  
    close_main = history_n(symbol=context.main_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    close_minor = history_n(symbol=context.minor_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    # 计算布林带  
    spread_history = close_main - close_minor# 历史价差  
    spread_new = close_main.iloc[-1] - close_minor.iloc[-1]# 最新价差  
    spread_history_mean = np.mean(spread_history)# 历史价差的均值  
    spread_history_std = np.std(spread_history)# 历史价差的标准差  
    upper = spread_history_mean + context.boll_multiple * spread_history_std# 布林带上轨  
    lower = spread_history_mean - context.boll_multiple * spread_history_std# 布林带下轨  
    upper_stoppoint = spread_history_mean + context.stoppoint_multiple * spread_history_std# 上轨止损线  
    lower_stoppoint = spread_history_mean - context.stoppoint_multiple * spread_history_std# 下轨止损线  
  
    # 获取仓位  
    position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,Account_positions))        # 多头仓位  
    position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,Account_positions))      # 空头仓位  
  
    # 没有仓位时  
    if not position_short and not position_long:  
        price_df = history(symbol=[context.main_contract,context.minor_contract], frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
        price_main = price_df[price_df['symbol']==context.main_contract]['open'].iloc[0]  
        price_minor = price_df[price_df['symbol']==context.minor_contract]['open'].iloc[0]  
  
        if spread_new > upper:  
            print('{}:做空价差组合(买入{}，卖出{})'.format(context.now,context.minor_contract,context.main_contract))  
            order_volume(symbol=context.main_contract,side=OrderSide_Sell,volume=1,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_main)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_minor)  
  
        if spread_new < lower:  
            print('{}:做多价差组合(买入{}，卖出{})'.format(context.now,context.main_contract,context.minor_contract))  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_main)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price_minor)  
  
    # 设计平仓信号  
    # 做多价差组合时  
    if position_long:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new >= spread_history_mean or spread_new <= lower_stoppoint:  
            price_df = history(symbol=[context.main_contract,context.minor_contract], frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
            price_main = price_df[price_df['symbol']==context.main_contract]['open'].iloc[0]  
            price_minor = price_df[price_df['symbol']==context.minor_contract]['open'].iloc[0]  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_main)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_minor)  
            print('{}:平仓'.format(context.now))  
  
    # 做空价差组合时  
    if position_short:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new <= spread_history_mean or spread_new >= upper_stoppoint:  
            price_df = history(symbol=[context.main_contract,context.minor_contract], frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
            price_main = price_df[price_df['symbol']==context.main_contract]['open'].iloc[0]  
            price_minor = price_df[price_df['symbol']==context.minor_contract]['open'].iloc[0]  
            order_volume(symbol=context.main_contract,side=OrderSide_Buy,volume=1,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_main)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_minor)  
            print('{}:平仓'.format(context.now))  
  

def on_backtest_finished(context, indicator):  
    # 回测业绩指标数据  
    data = [  
        indicator['pnl_ratio'], indicator['pnl_ratio_annual'],indicator['sharp_ratio'],   
        indicator['max_drawdown'], context.periods_time, context.boll_multiple, context.stoppoint_multiple  
    ]  
    # 将超参加入context.result  
    context.result.append(data)  
  
  
def run_strategy(periods_time, boll_multiple, stoppoint_multiple):  
    # 导入上下文  
    from gm.model.storage import context  
    # 用context传入参数  
    context.periods_time = periods_time                                 # 设置回溯周期  
    context.boll_multiple = boll_multiple                               # 布林带上下轨倍数  
    context.stoppoint_multiple = boll_multiple+stoppoint_multiple       # 止盈止损倍数  
    # context.result用以存储超参  
    context.result = []  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='74b217f7-6b27-11ef-a083-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 21:00:00',  
        backtest_end_time='2023-12-31 15:00:00',  
        # backtest_start_time='2024-01-01 21:00:00',  
        # backtest_end_time='2024-07-31 15:00:00',        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=200000,  
        backtest_commission_ratio=0.00012,  
        backtest_slippage_ratio=0.0000,  
        backtest_match_mode=1)  
    return context.result  
  
  
if __name__ == '__main__':  
    # 参数组合列表  
    print('构建参数组：')  
    paras_list = []  
    # 循环输入参数数值回测  
    for i in range(21, 126, 21):  
        for x in range(5, 21, 5):  
            for y in range(5, 21, 5):  
                paras_list.append([i,x*0.1,y*0.1])  
    print(paras_list)  
  
    # 多进程并行  
    print('多进程并行运行参数优化...')  
    processes_list = []  
    pool = multiprocessing.Pool(processes=12,  
                                maxtasksperchild=1)  # create 12 processes  
    for i in range(len(paras_list)):  
        processes_list.append(  
            pool.apply_async(func=run_strategy,  
                             args=(paras_list[i][0],paras_list[i][1],paras_list[i][2])))  
    pool.close()  
    pool.join()  
    print('运行结束！')  
  
    # 获取组合的回测结果,并导出  
    info = [pro.get()[0] for pro in processes_list]  
    info = pd.DataFrame(info,  
                        columns=[  
                            'pnl_ratio', 'pnl_ratio_annual', 'sharp_ratio',  
                            'max_drawdown', 'periods_time', 'boll_multiple', 'stoppoint_multiple'  
                        ])  
    print(info)  
    info.to_csv('info.csv', index=False)

# 孕线形态策略
**1、****策略说明**

**形态的定义，**身怀六甲/孕线**（通达信）：**

ABS(REF(CLOSE,1)-REF(OPEN,1))/REF(CLOSE,1)>0.04&&

ABS(CLOSE-OPEN)/CLOSE<0.005&&

MAX(CLOSE,OPEN)<MAX(REF(CLOSE,1),REF(OPEN,1))&&

MIN(CLOSE,OPEN)>MIN(REF(CLOSE,1),REF(OPEN,1));

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import MyTT  
import datetime  
import numpy as np  
import pandas as pd  
import warnings  
warnings.filterwarnings("ignore")  
import statsmodels.api as sm  
    
def init(context):  
    # 交易参数  
    context.fundamentals = pd.DataFrame()  
    context.history_close = pd.DataFrame()  
    context.stop_rate = 0.05# 移动止盈止损比例  
    context.holding_num = 10# 最大持股数量  
    # 定时任务  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:30:00')  
  
  
def before_market(context):  
    # 所有持仓  
    Account_positions = context.account().positions()  
    holding_stocks = [posi['symbol'] for posi in Account_positions]  
    if len(holding_stocks)<context.holding_num:  
        today = context.now.strftime('%Y-%m-%d')  
        # 获取交易日  
        last_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
        stocks,stocks_str = get_normal_stocks(context, date=last_date, skip_suspended=True, skip_st=True, skip_limit=True)  
  
        # 条件1：符合形态（孕线）  
        stocks_HARAMI = cal_STK_FORM_HARAMI(context, stocks_str,last_date)  
        if len(stocks_HARAMI)==0:  
            return  
  
        # 条件2：符合下跌趋势（处于布林带中轨以下区间）  
        if len(context.history_close)==0:  
            start_date = get_previous_n_trading_dates(exchange='SHSE', date=today, n=100)[0]  
            context.history_close = history_new(stocks,frequency='1d',start_time=start_date,end_time=last_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=last_date, df=True, type=True)  
        else:  
            history_close = history_new(stocks,frequency='1d',start_time=last_date,end_time=last_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=last_date, df=True, type=True)  
            context.history_close = pd.concat([context.history_close,history_close])  
            context.history_close.drop(index=context.history_close.index[0])   
  
  
        new_close = context.history_close.loc[:,stocks_HARAMI]  
        upper_df,middle_df,lower_df = cal_BOLL(stocks_HARAMI,last_date,new_close)  
        new_price = new_close.iloc[-1,:].to_frame().T  
        new_price.index = middle_df.index  
        new_price = new_price[middle_df.columns]  
        to_buy = list(middle_df[new_price<=middle_df].dropna().columns)  
  
        # 条件3：数量过多时，以市值从小到大买入  
        context.fundamentals = pd.DataFrame()  
        if len(to_buy)==0:  
            pass  
        else:  
            # 获取所有股票市值,并按升序排序  
            fundamental = stk_get_daily_mktvalue_pt(symbols=to_buy, fields='tot_mv', trade_date=last_date, df=True)  
            context.fundamentals = fundamental[fundamental['symbol'].isin(to_buy)].sort_values(by='tot_mv')  
            # 获取前N只股票  
            if len(context.fundamentals)==0:  
                context.fundamentals = pd.DataFrame()  
            else:  
                context.fundamentals = context.fundamentals.sort_values('tot_mv')  
  
  
def algo_1(context):  
    # 获取持仓  
    positions = context.account().positions()  
    # 移动止盈止损  
    for position in positions:  
        symbol = position['symbol']  
        created_at = position['created_at']  
        his_high = history_new(symbol,frequency='3600s',start_time=created_at,end_time=context.now,fields='eob,symbol,high',adjust=ADJUST_PREV,adjust_end_time=context.now, df=True, type=False)  
        new_price = current(symbols=symbol)[0]['price']  
        # 移动止盈止损  
        if new_price<max(his_high['high'])*(1-context.stop_rate):  
            lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
            if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=new_price:  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
            else:  
                print('{}:{}数据跌停或数据缺失导致未能卖出！！！！！！！！！！！！！！！！！！！！！'.format(context.now,symbol))  
              
  
    # 买在标的池中的股票  
    # 所有持仓  
    holding_stocks = [posi['symbol'] for posi in positions]  
    if len(holding_stocks)<context.holding_num and len(holding_stocks)+len(context.fundamentals)>context.holding_num:  
        context.fundamentals = context.fundamentals.iloc[:(context.holding_num-len(holding_stocks)),:]  
        print('{}:股票标的：{}'.format(context.now,list(context.fundamentals['symbol'])))  
    for symbol in list(context.fundamentals['symbol']):  
        if len(holding_stocks)<context.holding_num:  
            new_price = current(symbols=symbol)[0]['price']  
            nav = context.account().cash['nav']  
            trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/context.holding_num,price=new_price)  
            order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
              
   
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def cal_STK_FORM_HARAMI(context,stocks,end_date,n=2):  
    """  
    形态因子-身怀六甲/孕线  
    ABS(REF(CLOSE,1)-REF(OPEN,1))/REF(CLOSE,1)>0.04&&    ABS(CLOSE-OPEN)/CLOSE<0.005&&    MAX(CLOSE,OPEN)<MAX(REF(CLOSE,1),REF(OPEN,1))&&    MIN(CLOSE,OPEN)>MIN(REF(CLOSE,1),REF(OPEN,1));    """    factor_list = []  
    # 计算因子  
    start_date = get_previous_n_trading_dates(exchange='SHSE', date=end_date, n=n)[0]  
    data_df = history_new(stocks,frequency='1d',start_time=start_date,end_time=end_date,fields='eob,symbol,close,open',adjust=ADJUST_PREV,adjust_end_time=end_date, df=True, type=False).set_index('eob')  
      
    stocks_list = list(set(data_df['symbol']))  
    for symbol in stocks_list:  
        data = data_df[data_df['symbol']==symbol]  
        if len(data)<n:continue  
        ref_close_price = data['close'].iloc[-2]  
        ref_open_price = data['open'].iloc[-2]  
        cond1 = abs(ref_close_price-ref_open_price)/ref_close_price>0.04  
        if not cond1:continue  
        close_price = data['close'].iloc[-1]  
        open_price = data['open'].iloc[-1]  
        cond2 = abs(close_price-open_price)/close_price<0.005  
        if not cond2:continue  
        cond3 = max(close_price,open_price)<max(ref_close_price,ref_open_price)  
        if not cond3:continue  
        cond4 = min(close_price,open_price)>min(ref_close_price,ref_open_price)  
        if not cond4:continue  
        factor_list.append(symbol)  
    return factor_list  
  
  
def cal_BOLL(securityList,date,close_price):  
    """  
    计算布林带  
    """    # 计算布林带  
    upper_df,middle_df,lower_df = pd.DataFrame(index=[date],columns=securityList),pd.DataFrame(index=[date],columns=securityList),pd.DataFrame(index=[date],columns=securityList)  
      
    for code in securityList:  
        close = close_price[code][-100:]  
        upper, middle, lower = MyTT.BOLL(close,N=20, P=2)    
        upper_df.loc[date,code]=upper[-1]  
        middle_df.loc[date,code]=middle[-1]  
        lower_df.loc[date,code]=lower[-1]  
    return upper_df,middle_df,lower_df  
  
  
def get_N_trading_date(context,date,counts=1,model='previous',exchange='SHSE'):  
    """  
    获取end_date前N个交易日,end_date为datetime格式，不包括date日期  
    :param date：目标日期  
    :param counts：历史回溯天数，默认为1，即前一天  
    :param model: 模式，默认为previous，即前N个交易日；next模式为后N个交易日  
    """    date = pd.Timestamp(date)  
    date_str = date.strftime('%Y-%m-%d')  
    # 判断context.trading_dates是否存在  
    if 'trading_dates' not in {k: v for k, v in context.__dict__.items() if not k.startswith('__')}.keys():  
        context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=date.year-1, end_year=datetime.datetime.now().year)  
    # 判断当前日期是否为交易日  
    pre_times = 1  
    if date_str not in list(context.trading_dates['trade_date']) and str(date.year) in set([x[:4] for x in context.trading_dates['trade_date']]):# 非交易日  
        date = context.trading_dates[context.trading_dates['date']==date_str]['pre_trade_date'].iloc[0]  
        pre_times = 0  
    date = pd.Timestamp(date)  
    date_str = date.strftime('%Y-%m-%d')  
    # 历史N个交易日  
    while True:  
        trading_dates_dropna = context.trading_dates.replace('',np.nan).dropna(how='any')  
        if model == 'previous':  
            previous_date_df = trading_dates_dropna[trading_dates_dropna['trade_date']<=date_str]  
            if len(previous_date_df)>=counts+1:  
                return previous_date_df['trade_date'].iloc[-counts-pre_times]  
            else:  
                start_year = min(int(context.trading_dates['date'].iloc[0][:4])-1,date.year)  
                end_year = int(context.trading_dates['date'].iloc[-1][:4])  
                context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_year, end_year=end_year)  
        elif model == 'next':  
            previous_date_df = trading_dates_dropna[trading_dates_dropna['trade_date']>=date_str]  
            if len(previous_date_df)>=counts+1:  
                return previous_date_df['trade_date'].iloc[counts]  
            else:  
                start_year = int(context.trading_dates['date'].iloc[0][:4])  
                end_year = max(int(context.trading_dates['date'].iloc[-1][:4])+1,date.year)  
                context.trading_dates = get_trading_dates_by_year(exchange=exchange, start_year=start_year, end_year=end_year)  
                  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_limit:是否剔除开盘涨停股票，默认为False,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
      
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:  
        pass  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
         
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='f6894fdd-4b25-11ef-8a34-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2022-01-01 08:00:00',  
        backtest_end_time='2024-07-29 19:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# VOV因子对冲策略
**1、****策略说明**

原策略是东北证券的《基于高频数据的风险不确定性因子---因子选股系列之五》。

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import pandas as pd  
import numpy as np  
import warnings  
  
warnings.filterwarnings("ignore")  
import statsmodels.api as sm  
  
global factor_account_id, stock_account_id  
factor_account_id = '681083af-5203-11ee-8044-00163e022aa6'  
stock_account_id = '300d2c5e-507b-11ee-90b5-00163e022aa6'  
  
"""  
VOV因子对冲策略  
逻辑：做多VOV因子，同时做空沪深300股指期货合约进行对冲。  
  
20240716：1、将老版本SDK修正为新版SDK，涉及获取股票代码、交易日期等函数。  
"""  
  
def init(context):  
    context.index_symbol = 'SHSE.000300'  # 目标标的（ALL为全市场，中证1000 SHSE.000852，中证500 SHSE.000905，沪深300 SHSE.000300）  
    context.factor_frequency = '300s'  # 因子的日内频率  
    context.factor_periods = 21  # 因子的计算周期  
    context.future_symbol = 'CFFEX.IF'  # 目标合约  
    context.main_contract = None  # 主力合约  
    context.multiplier = None  # 合约乘数  
    # 交易参数  
    context.holding_num = 20  
    context.trade_stocks = []  
  
    # 设置开仓在股票和期货的资金百分比(期货在后面自动进行杠杆相关的调整)(仿真和实盘中，因为期货和股票是在两个独立的账户中下单，所以下述比例可以设为1)  
    if context.mode == MODE_BACKTEST:  
        context.percentage_stock = 0.49  
        context.percentage_futures = 0.49  
  
        global factor_account_id, stock_account_id  
        factor_account_id = context.strategy_id  
        stock_account_id = context.strategy_id  
    else:  
        context.percentage_stock = 1  
        context.percentage_futures = 1  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')  
  
  
def algo(context):  
    # 当前日期  
    today = context.now.strftime('%Y-%m-%d')  
    # 每月第一个交易日调仓  
    is_start = is_Week_or_Month_first_day(date=context.now, periods_type='month')  
    if is_start:  
        # 当前日期  
        today = context.now.strftime('%Y-%m-%d')  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.factor_periods)  
        # 上一个交易日  
        last_date = date_list[-1]  
        # 前21个交易日  
        last_21_date = date_list[0]  
        if context.index_symbol == 'ALL':  
            # 获取全A股票（剔除停牌股和ST股）  
            all_stocks, all_stocks_str = get_normal_stocks(context, context.now)  
        else:  
            # 获取指数成分股  
            all_stocks = stk_get_index_constituents(index=context.index_symbol, trade_date=today)  
            all_stocks_str = ','.join(all_stocks['symbol'].tolist())  
            # 过滤停牌和ST的成分股  
            not_suspended_info = get_symbols(sec_type1=1010, symbols=all_stocks_str.split(','),  
                                             trade_date=context.now.strftime('%Y-%m-%d'), skip_suspended=True,  
                                             skip_st=True)  
            all_stocks = [item['symbol'] for item in not_suspended_info]  
        # 计算VOV因子  
        factor = cal_Factor_VOV(stocks=all_stocks, start_date=last_21_date, end_date=today,  
                                factor_frequency=context.factor_frequency, factor_periods=context.factor_periods)  
        # 去极值、标准化、市值中性化  
        factor.dropna(inplace=True)  
        factor = winsorize_med(factor)  
        factor = standardlize(factor)  
        factor = neutralize_MarketValue(factor, last_date)  
        if isinstance(factor, pd.Series):  
            factor = factor.replace([-np.inf, np.inf], np.nan).dropna().sort_values(ascending=True)  
        else:  
            factor = factor.replace([-np.inf, np.inf], np.nan).dropna().sort_values(by=factor.columns[0],  
                                                                                    ascending=True)  
        # 获取最小因子的前N只股票  
        context.trade_stocks = list(factor[:context.holding_num].index)  
        print('{} 待买入股票{}只：{}'.format(context.now, len(context.trade_stocks), context.trade_stocks))  
  
        ## 股票交易(回测用当前价格，限价下单；仿真实盘用市价下单)  
        # 获取持仓  
        positions = context.account(account_id=stock_account_id).positions()  
        new_prices = current([position['symbol'] for position in positions])  
        # 卖出不在trade_stocks中的持仓  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in context.trade_stocks and symbol[:len(context.future_symbol)] != context.future_symbol:  
                new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))  
                if len(new_price) != 0:  
                    order_volume(symbol=symbol,  
                                 volume=position['available'] - position['volume_today'],  
                                 side=OrderSide_Sell,  
                                 order_type=OrderType_Market,  
                                 position_effect=PositionEffect_Close,  
                                 price=round(new_price[0]['price'] * 0.99, 2),  
                                 account=stock_account_id)  
        # 买入股票  
        new_prices = current(context.trade_stocks)  
        # 获取股票的权重  
        percent_stocks = context.percentage_stock / len(context.trade_stocks)  
        # 账户信息  
        acc_data = context.account(account_id=stock_account_id).cash  
        for symbol in context.trade_stocks:  
            new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))[0]['price']  
            volume = int(  
                round(min(acc_data['nav'] * percent_stocks, acc_data['available']) / (new_price * 1.02) / 100) * 100)  
            if volume == 0: continue  
            order_volume(symbol=symbol,  
                         volume=volume,  
                         side=OrderSide_Buy,  
                         order_type=OrderType_Market,  
                         position_effect=PositionEffect_Open,  
                         account=stock_account_id)  
  
    # 主力合约  
    main_contract = fut_get_continuous_contracts(csymbol=context.future_symbol, start_date=today, end_date=today)[0][  
        'symbol']  
    if main_contract != context.main_contract:  
        context.main_contract = main_contract  
        # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）  
        Account_positions = context.account(account_id=factor_account_id).positions()  
        for posi in Account_positions:  
            if context.main_contract[:len(context.future_symbol)] == posi['symbol'][:len(context.future_symbol)]:  
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now, posi['symbol'], context.main_contract))  
                # # 开盘价（日频数据）  
                # new_price = history_n(symbol=posi['symbol'], frequency='1d', count=1, end_time=context.now, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']  
                # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）  
                new_price = current(symbols=posi['symbol'])[0]['price']  
                order_volume(symbol=posi['symbol'],  
                             volume=posi['available'],  
                             side=OrderSide_Buy,  
                             order_type=OrderType_Market,  
                             position_effect=PositionEffect_Close,  
                             price=round(new_price * 1.01, 2),  
                             account=factor_account_id)  
        else:  
            # 获取股指期货的保证金比率和合约乘数  
            symbols_data = get_symbols(sec_type1=1040, symbols=main_contract, trade_date=today, df=False)[0]  
            margin_ratio = symbols_data['margin_ratio']  
            multiplier = symbols_data['multiplier']  
            # 更新股指期货的权重  
            percent = context.percentage_futures * margin_ratio  
            # 卖出股指期货对冲  
            # 注意：股指期货的percent参数是按照期货的保证金来算比例，不是按照合约价值， 比如说0.1就是用0.1的仓位的资金全部买入期货。  
            # # 开盘价（日频数据）  
            # new_price = history_n(symbol=context.main_contract, frequency='1d', count=1, end_time=context.now, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']  
            # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的订阅频率的收盘价）  
            new_price = current(symbols=context.main_contract)[0]['price']  
            order_target_percent(symbol=context.main_contract,  
                                 percent=percent,  
                                 order_type=OrderType_Limit,  
                                 position_side=PositionSide_Short,  
                                 price=new_price,  
                                 account=factor_account_id)  
  
    ## 股票交易(回测用当前价格，限价下单；仿真实盘用市价下单)  
    # 获取持仓  
    positions = context.account(account_id=stock_account_id).positions()  
    new_prices = current([position['symbol'] for position in positions])  
    # 卖出不在trade_stocks中的持仓  
    for position in positions:  
        symbol = position['symbol']  
        if symbol not in context.trade_stocks and symbol[:len(context.future_symbol)] != context.future_symbol:  
            new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))  
            if len(new_price) != 0:  
                order_volume(symbol=symbol,  
                             volume=position['available'] - position['volume_today'],  
                             side=OrderSide_Sell,  
                             order_type=OrderType_Market,  
                             position_effect=PositionEffect_Close,  
                             price=round(new_price[0]['price'] * 0.99, 2),  
                             account=stock_account_id)  
  
  
def cal_Factor_VOV(stocks, start_date, end_date, factor_frequency, factor_periods):  
    """  
    计算风险不确定性因子 VOV    :param stocks：待筛选股票池  
    :param start_date：开始日期  
    :param end_date：结束日期  
    :param factor_frequency：因子定义中的日内频率  
    :param factor_periods：因子定义中的窗口期长度  
    """    df = history_new(security=stocks, frequency=factor_frequency, start_time=start_date, end_time=end_date,  
                     fields='symbol,eob,close', skip_suspended=True, fill_missing=None, adjust=ADJUST_PREV,  
                     adjust_end_time=end_date, df=True)  
    r = np.square(np.log(df / df.shift(1)))  
    r = r.iloc[1:]  
    r.fillna(r.mean(), inplace=True)  
    rv = r.groupby(r.index.date).sum()  
    rv_mean = rv.mean()  
    diff_square = np.square(rv.sub(rv_mean, axis='columns'))  
    vov = np.sqrt(diff_square.sum() / (factor_periods - 1)) / rv_mean  
    return vov.to_frame(name='VOV')  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True,  
                      skip_upper_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended,  
                              skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info) > 0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x: x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x: x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date'] <= date - datetime.timedelta(days=new_days)) & (  
                    stocks_info['delisted_date'] > next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_upper_limit and context.mode == MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date, end_time=date, fields='open,symbol',  
                                adjust=ADJUST_NONE, df=True)  
            stocks_info = stocks_info.merge(low_price, on=['symbol'])  
            all_stocks = stocks_info[stocks_info['open'] != stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks, all_stocks_str, stocks_info  
    else:  
        return all_stocks, all_stocks_str  
  
  
def is_Week_or_Month_first_day(date, periods_type):  
    """  
    判断日期是否为 周/月 的第1个交易日  
    :param date：目标日期  
    :param periods_type：类型，'week'为周，'month'为月  
    """    date = pd.Timestamp(date)  
    # 判断该日期是否为交易日  
    is_trading_date = get_trading_dates(exchange='SZSE', start_date=date, end_date=date)  
    if len(is_trading_date) == 0:  
        return False  
  
        # 上一个交易日  
    last_date = pd.Timestamp(get_previous_trading_date(exchange='SZSE', date=date))  
    status = None  
    if periods_type == 'week':  
        status = date.week != last_date.week  
    elif periods_type == 'month':  
        status = date.month != last_date.month  
    return status  
  
  
def history_new(security, frequency, start_time, end_time, fields, skip_suspended=True, fill_missing=None,  
                adjust=ADJUST_PREV, adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency == '1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency == 'tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time,  
                               fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing,  
                               adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time,  
                               fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing,  
                               adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security, str):  
        security = security.split(',')  
    space = 30000 // len(security)  
    # 获取数据  
    if len(trading_date) <= space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields,  
                       skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust,  
                       adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date) / space))):  
            start = n * space  
            end = start + space  
            if end >= len(trading_date):  
                if frequency == '1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start],  
                                   end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended,  
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency == 'tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0],  
                                   end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended,  
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0],  
                                   end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended,  
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency == '1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start],  
                                   end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended,  
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0],  
                                   end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended,  
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data) == 33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data, data])  
    if df and len(Data) > 0:  
        if frequency == 'tick':  
            Data.sort_values(['symbol', 'created_at'], inplace=True)  
            Data.drop_duplicates(subset=['created_at', 'symbol'], keep='first', inplace=True)  
        else:  
            Data.sort_values(['symbol', 'eob'], inplace=True)  
            Data.drop_duplicates(subset=['eob', 'symbol'], keep='first', inplace=True)  
        if type:  
            if len(Data) > 0:  
                if frequency == 'tick':  
                    Data = Data.set_index(['created_at', 'symbol'])  
                else:  
                    Data = Data.set_index(['eob', 'symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值(data以code*date的形式)  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    uplimit = pd.DataFrame([mean_ + std_ * scale] * data.shape[0])  
    downlimit = pd.DataFrame([mean_ - std_ * scale] * data.shape[0])  
    nan_df = pd.DataFrame([[np.nan] * data.shape[1]] * data.shape[0])  
    if inclusive:  
        data = np.minimum(data, uplimit)  
        data = np.maximum(data, downlimit)  
    else:  
        data = np.minimum(data, nan_df)  
        data = np.maximum(data, nan_df)  
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化(data以code*date的形式)  
    :param data：待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def neutralize_MarketValue(data, date, counts=1):  
    """  
    市值中性化  
    :param data:待处理数据  
    :param date:目标日期  
    :param counts：历史回溯天数  
    """    if isinstance(data, pd.Series):  
        data = data.to_frame()  
    security = data.index.to_list()  
    market_value = stk_get_daily_mktvalue_pt_new(symbols=security, fields='tot_mv', date=date, counts=counts, df=True)  
    market_value_mean = market_value.groupby('symbol')['tot_mv'].mean()  
    x = sm.add_constant(market_value_mean)  
    common_index = list(set(x.index) & set(data.index))  
    x = x.loc[common_index, :]  
    data = data.loc[common_index, :]  
    residual = sm.OLS(data, x).fit().resid  # 此处使用最小二乘回归计算  
    return residual  
  
  
def stk_get_daily_mktvalue_pt_new(symbols, fields, date=None, counts: int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，count为正整数  
    """    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=counts)  
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total, df_new])  
        else:  
            df_total = df_total + df_new  
    return df_total  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        elif side == 2:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        elif side == 2:  
            side_effect = '平多仓'  
    order_type_word = '限价' if order_type == 1 else '市价'  
    if status == 3:  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now, symbol, order_type_word, side_effect, price,  
                                                         volume))  
    elif status == 8:  
        print('{}:拒单，原因：{}, 标的：{}'.format(context.now, order['ord_rej_reason_detail'], symbol))  
  
  
def on_backtest_finished(context, indicator):  
    print('*' * 50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE，回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE，前复权:ADJUST_PREV，后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='strategy_id',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='{{token}}',  
        backtest_start_time='2022-01-01 08:00:00',  
        backtest_end_time='2024-06-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# “持续异常交易量”选股因子PATV
1、**策略说明**

策略源自《量化研究2023年中期投资策略：“持续异常交易量”选股因子PATV》

## coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *

import datetime
import pandas as pd
import numpy as np
import warnings

warnings.filterwarnings("ignore")
import statsmodels.api as sm

global factor_account_id, stock_account_id
factor_account_id = '681083af-5203-11ee-8044-00163e022aa6'
stock_account_id = '300d2c5e-507b-11ee-90b5-00163e022aa6'

"""
VOV因子对冲策略
逻辑：做多VOV因子，同时做空沪深300股指期货合约进行对冲。

20240716：1、将老版本SDK修正为新版SDK，涉及获取股票代码、交易日期等函数。
"""

def init(context):
    context.index_symbol = 'SHSE.000300'  # 目标标的（ALL为全市场，中证1000 SHSE.000852，中证500 SHSE.000905，沪深300 SHSE.000300）
    context.factor_frequency = '300s'  # 因子的日内频率
    context.factor_periods = 21  # 因子的计算周期
    context.future_symbol = 'CFFEX.IF'  # 目标合约
    context.main_contract = None  # 主力合约
    context.multiplier = None  # 合约乘数
    # 交易参数
    context.holding_num = 20
    context.trade_stocks = []

    # 设置开仓在股票和期货的资金百分比(期货在后面自动进行杠杆相关的调整)(仿真和实盘中，因为期货和股票是在两个独立的账户中下单，所以下述比例可以设为1)
    if context.mode == MODE_BACKTEST:
        context.percentage_stock = 0.49
        context.percentage_futures = 0.49

        global factor_account_id, stock_account_id
        factor_account_id = context.strategy_id
        stock_account_id = context.strategy_id
    else:
        context.percentage_stock = 1
        context.percentage_futures = 1
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')


def algo(context):
    # 当前日期
    today = context.now.strftime('%Y-%m-%d')
    # 每月第一个交易日调仓
    is_start = is_Week_or_Month_first_day(date=context.now, periods_type='month')
    if is_start:
        # 当前日期
        today = context.now.strftime('%Y-%m-%d')
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.factor_periods)
        # 上一个交易日
        last_date = date_list[-1]
        # 前21个交易日
        last_21_date = date_list[0]
        if context.index_symbol == 'ALL':
            # 获取全A股票（剔除停牌股和ST股）
            all_stocks, all_stocks_str = get_normal_stocks(context, context.now)
        else:
            # 获取指数成分股
            all_stocks = stk_get_index_constituents(index=context.index_symbol, trade_date=today)
            all_stocks_str = ','.join(all_stocks['symbol'].tolist())
            # 过滤停牌和ST的成分股
            not_suspended_info = get_symbols(sec_type1=1010, symbols=all_stocks_str.split(','),
                                             trade_date=context.now.strftime('%Y-%m-%d'), skip_suspended=True,
                                             skip_st=True)
            all_stocks = [item['symbol'] for item in not_suspended_info]
        # 计算VOV因子
        factor = cal_Factor_VOV(stocks=all_stocks, start_date=last_21_date, end_date=today,
                                factor_frequency=context.factor_frequency, factor_periods=context.factor_periods)
        # 去极值、标准化、市值中性化
        factor.dropna(inplace=True)
        factor = winsorize_med(factor)
        factor = standardlize(factor)
        factor = neutralize_MarketValue(factor, last_date)
        if isinstance(factor, pd.Series):
            factor = factor.replace([-np.inf, np.inf], np.nan).dropna().sort_values(ascending=True)
        else:
            factor = factor.replace([-np.inf, np.inf], np.nan).dropna().sort_values(by=factor.columns[0],
                                                                                    ascending=True)
        # 获取最小因子的前N只股票
        context.trade_stocks = list(factor[:context.holding_num].index)
        print('{} 待买入股票{}只：{}'.format(context.now, len(context.trade_stocks), context.trade_stocks))

        ## 股票交易(回测用当前价格，限价下单；仿真实盘用市价下单)
        # 获取持仓
        positions = context.account(account_id=stock_account_id).positions()
        new_prices = current([position['symbol'] for position in positions])
        # 卖出不在trade_stocks中的持仓
        for position in positions:
            symbol = position['symbol']
            if symbol not in context.trade_stocks and symbol[:len(context.future_symbol)] != context.future_symbol:
                new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))
                if len(new_price) != 0:
                    order_volume(symbol=symbol,
                                 volume=position['available'] - position['volume_today'],
                                 side=OrderSide_Sell,
                                 order_type=OrderType_Market,
                                 position_effect=PositionEffect_Close,
                                 price=round(new_price[0]['price'] * 0.99, 2),
                                 account=stock_account_id)
        # 买入股票
        new_prices = current(context.trade_stocks)
        # 获取股票的权重
        percent_stocks = context.percentage_stock / len(context.trade_stocks)
        # 账户信息
        acc_data = context.account(account_id=stock_account_id).cash
        for symbol in context.trade_stocks:
            new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))[0]['price']
            volume = int(
                round(min(acc_data['nav'] * percent_stocks, acc_data['available']) / (new_price * 1.02) / 100) * 100)
            if volume == 0: continue
            order_volume(symbol=symbol,
                         volume=volume,
                         side=OrderSide_Buy,
                         order_type=OrderType_Market,
                         position_effect=PositionEffect_Open,
                         account=stock_account_id)

    # 主力合约
    main_contract = fut_get_continuous_contracts(csymbol=context.future_symbol, start_date=today, end_date=today)[0][
        'symbol']
    if main_contract != context.main_contract:
        context.main_contract = main_contract
        # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出（注：本策略交易以开盘价为交易价格，当调整定时任务时间时，需调整对应价格）
        Account_positions = context.account(account_id=factor_account_id).positions()
        for posi in Account_positions:
            if context.main_contract[:len(context.future_symbol)] == posi['symbol'][:len(context.future_symbol)]:
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now, posi['symbol'], context.main_contract))
                # # 开盘价（日频数据）
                # new_price = history_n(symbol=posi['symbol'], frequency='1d', count=1, end_time=context.now, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']
                # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的最近一分钟的收盘价）
                new_price = current(symbols=posi['symbol'])[0]['price']
                order_volume(symbol=posi['symbol'],
                             volume=posi['available'],
                             side=OrderSide_Buy,
                             order_type=OrderType_Market,
                             position_effect=PositionEffect_Close,
                             price=round(new_price * 1.01, 2),
                             account=factor_account_id)
        else:
            # 获取股指期货的保证金比率和合约乘数
            symbols_data = get_symbols(sec_type1=1040, symbols=main_contract, trade_date=today, df=False)[0]
            margin_ratio = symbols_data['margin_ratio']
            multiplier = symbols_data['multiplier']
            # 更新股指期货的权重
            percent = context.percentage_futures * margin_ratio
            # 卖出股指期货对冲
            # 注意：股指期货的percent参数是按照期货的保证金来算比例，不是按照合约价值， 比如说0.1就是用0.1的仓位的资金全部买入期货。
            # # 开盘价（日频数据）
            # new_price = history_n(symbol=context.main_contract, frequency='1d', count=1, end_time=context.now, fields='open', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)[0]['open']
            # 当前价（tick数据，免费版本有时间权限限制；实时模式，返回当前最新 tick 数据，回测模式，返回回测当前时间点的订阅频率的收盘价）
            new_price = current(symbols=context.main_contract)[0]['price']
            order_target_percent(symbol=context.main_contract,
                                 percent=percent,
                                 order_type=OrderType_Limit,
                                 position_side=PositionSide_Short,
                                 price=new_price,
                                 account=factor_account_id)

    ## 股票交易(回测用当前价格，限价下单；仿真实盘用市价下单)
    # 获取持仓
    positions = context.account(account_id=stock_account_id).positions()
    new_prices = current([position['symbol'] for position in positions])
    # 卖出不在trade_stocks中的持仓
    for position in positions:
        symbol = position['symbol']
        if symbol not in context.trade_stocks and symbol[:len(context.future_symbol)] != context.future_symbol:
            new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))
            if len(new_price) != 0:
                order_volume(symbol=symbol,
                             volume=position['available'] - position['volume_today'],
                             side=OrderSide_Sell,
                             order_type=OrderType_Market,
                             position_effect=PositionEffect_Close,
                             price=round(new_price[0]['price'] * 0.99, 2),
                             account=stock_account_id)


def cal_Factor_VOV(stocks, start_date, end_date, factor_frequency, factor_periods):
    """
    计算风险不确定性因子 VOV
    :param stocks：待筛选股票池
    :param start_date：开始日期
    :param end_date：结束日期
    :param factor_frequency：因子定义中的日内频率
    :param factor_periods：因子定义中的窗口期长度
    """
    df = history_new(security=stocks, frequency=factor_frequency, start_time=start_date, end_time=end_date,
                     fields='symbol,eob,close', skip_suspended=True, fill_missing=None, adjust=ADJUST_PREV,
                     adjust_end_time=end_date, df=True)
    r = np.square(np.log(df / df.shift(1)))
    r = r.iloc[1:]
    r.fillna(r.mean(), inplace=True)
    rv = r.groupby(r.index.date).sum()
    rv_mean = rv.mean()
    diff_square = np.square(rv.sub(rv_mean, axis='columns'))
    vov = np.sqrt(diff_square.sum() / (factor_periods - 1)) / rv_mean
    return vov.to_frame(name='VOV')


def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True,
                      skip_upper_limit=False, return_info=False):
    """
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））
    :param date：目标日期
    :param new_days:新股上市天数，默认为365天
    :param skip_suspended:是否剔除停牌股，默认为True
    :param skip_st:是否剔除ST股，默认为True
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效
    """
    date = pd.Timestamp(date).replace(tzinfo=None)
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])
    # A股，剔除停牌和ST股票
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended,
                              skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)
    if len(stocks_info) > 0:
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x: x.replace(tzinfo=None))
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x: x.replace(tzinfo=None))
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)
        stocks_info = stocks_info[(stocks_info['listed_date'] <= date - datetime.timedelta(days=new_days)) & (
                    stocks_info['delisted_date'] > next_20date)]
        all_stocks = list(stocks_info['symbol'])
        # 剔除开盘涨停股
        if skip_upper_limit and context.mode == MODE_BACKTEST:
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date, end_time=date, fields='open,symbol',
                                adjust=ADJUST_NONE, df=True)
            stocks_info = stocks_info.merge(low_price, on=['symbol'])
            all_stocks = stocks_info[stocks_info['open'] != stocks_info['upper_limit']]['symbol'].tolist()
    else:
        all_stocks = []
    all_stocks_str = ','.join(all_stocks)
    if return_info:
        return all_stocks, all_stocks_str, stocks_info
    else:
        return all_stocks, all_stocks_str


def is_Week_or_Month_first_day(date, periods_type):
    """
    判断日期是否为 周/月 的第1个交易日
    :param date：目标日期
    :param periods_type：类型，'week'为周，'month'为月
    """
    date = pd.Timestamp(date)
    # 判断该日期是否为交易日
    is_trading_date = get_trading_dates(exchange='SZSE', start_date=date, end_date=date)
    if len(is_trading_date) == 0:
        return False

        # 上一个交易日
    last_date = pd.Timestamp(get_previous_trading_date(exchange='SZSE', date=date))
    status = None
    if periods_type == 'week':
        status = date.week != last_date.week
    elif periods_type == 'month':
        status = date.month != last_date.month
    return status


def history_new(security, frequency, start_time, end_time, fields, skip_suspended=True, fill_missing=None,
                adjust=ADJUST_PREV, adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):
    """
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame
    """
    Data = pd.DataFrame()
    if frequency == '1d':
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))
    elif frequency == 'tick':
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time,
                               fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing,
                               adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time,
                               fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing,
                               adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    # 计算合理间隔
    if isinstance(security, str):
        security = security.split(',')
    space = 30000 // len(security)
    # 获取数据
    if len(trading_date) <= space:
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields,
                       skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust,
                       adjust_end_time=adjust_end_time, df=df)
    else:
        for n in range(int(np.ceil(len(trading_date) / space))):
            start = n * space
            end = start + space
            if end >= len(trading_date):
                if frequency == '1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start],
                                   end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended,
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                elif frequency == 'tick':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0],
                                   end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended,
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0],
                                   end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended,
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            else:
                if frequency == '1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start],
                                   end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended,
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0],
                                   end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended,
                                   fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            if len(data) == 33000:
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')
            Data = pd.concat([Data, data])
    if df and len(Data) > 0:
        if frequency == 'tick':
            Data.sort_values(['symbol', 'created_at'], inplace=True)
            Data.drop_duplicates(subset=['created_at', 'symbol'], keep='first', inplace=True)
        else:
            Data.sort_values(['symbol', 'eob'], inplace=True)
            Data.drop_duplicates(subset=['eob', 'symbol'], keep='first', inplace=True)
        if type:
            if len(Data) > 0:
                if frequency == 'tick':
                    Data = Data.set_index(['created_at', 'symbol'])
                else:
                    Data = Data.set_index(['eob', 'symbol'])
                Data = Data.unstack()
                Data.columns = Data.columns.droplevel(level=0)
    return Data


def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):
    """
    去极值(data以code*date的形式)
    :param data：待处理数据[Series]
    :param scale：标准差倍数，默认为3
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN
    :param inf2nan：True为将inf转化为nan，False不转化
    """
    data = data.astype('float')
    if inf2nan:
        data = data.replace([np.inf, -np.inf], np.nan)
    std_ = data.std()
    mean_ = data.mean()
    uplimit = pd.DataFrame([mean_ + std_ * scale] * data.shape[0])
    downlimit = pd.DataFrame([mean_ - std_ * scale] * data.shape[0])
    nan_df = pd.DataFrame([[np.nan] * data.shape[1]] * data.shape[0])
    if inclusive:
        data = np.minimum(data, uplimit)
        data = np.maximum(data, downlimit)
    else:
        data = np.minimum(data, nan_df)
        data = np.maximum(data, nan_df)
    return data


def standardlize(data, inf2nan=True):
    """
    标准化(data以code*date的形式)
    :param data：待处理数据
    :param inf2nan：是否将inf转化为nan
    """
    if inf2nan:
        data = data.replace([np.inf, -np.inf], np.nan)
    return (data - data.mean()) / data.std()


def neutralize_MarketValue(data, date, counts=1):
    """
    市值中性化
    :param data:待处理数据
    :param date:目标日期
    :param counts：历史回溯天数
    """
    if isinstance(data, pd.Series):
        data = data.to_frame()
    security = data.index.to_list()
    market_value = stk_get_daily_mktvalue_pt_new(symbols=security, fields='tot_mv', date=date, counts=counts, df=True)
    market_value_mean = market_value.groupby('symbol')['tot_mv'].mean()
    x = sm.add_constant(market_value_mean)
    common_index = list(set(x.index) & set(data.index))
    x = x.loc[common_index, :]
    data = data.loc[common_index, :]
    residual = sm.OLS(data, x).fit().resid  # 此处使用最小二乘回归计算
    return residual


def stk_get_daily_mktvalue_pt_new(symbols, fields, date=None, counts: int = None, df=False):
    """
    多日期调用stk_get_daily_mktvalue_pt函数，count为正整数
    """
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=counts)
    # 循环获取数据
    df_total = pd.DataFrame()
    for date in date_list:
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)
        if df:
            df_total = pd.concat([df_total, df_new])
        else:
            df_total = df_total + df_new
    return df_total


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if effect == 1:
        if side == 1:
            side_effect = '开多仓'
        elif side == 2:
            side_effect = '开空仓'
    else:
        if side == 1:
            side_effect = '平空仓'
        elif side == 2:
            side_effect = '平多仓'
    order_type_word = '限价' if order_type == 1 else '市价'
    if status == 3:
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now, symbol, order_type_word, side_effect, price,
                                                         volume))
    elif status == 8:
        print('{}:拒单，原因：{}, 标的：{}'.format(context.now, order['ord_rej_reason_detail'], symbol))


def on_backtest_finished(context, indicator):
    print('*' * 50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
        strategy_id策略ID, 由系统生成
        filename文件名, 请与本文件名保持一致
        mode运行模式, 实时模式:MODE_LIVE，回测模式:MODE_BACKTEST
        token绑定计算机的ID, 可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE，前复权:ADJUST_PREV，后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
        '''
    run(strategy_id='strategy_id',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='{{token}}',
        backtest_start_time='2022-01-01 08:00:00',
        backtest_end_time='2024-06-30 16:00:00',
        backtest_adjust=ADJUST_NONE,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0007,
        backtest_slippage_ratio=0.00123,
        backtest_match_mode=1)

# 量稳换手率
**1、****策略说明：**

计算STR因子，获取最小因子的前N只股票，并以RSRS指标进行策略择时

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
import datetime  
import numpy as np  
import pandas as pd  
import statsmodels.api as sm  
  
def init(context):  
    # 每天的09:30 定时执行algo任务  
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')  
    context.num = 20  
    context.alpha_STR_history_num = 20  # STR因子的历史回溯天数  
    # 大盘指数  
    context.symbol = 'SHSE.000852'  # 'SHSE.000905'#  
    # RSRS参数  
    context.periods_RSRS_N = 18  
    context.periods_RSRS_M = 600  
    context.RSRS_upper = 0.7  
    context.RSRS_lower = -0.7  
  
  
def algo(context):  
    # 当前时间str  
    today = context.now.strftime("%Y-%m-%d")  
    # 下一个交易日  
    next_date = get_next_n_trading_dates(exchange='SHSE', date=today, n=1)[0]  
  
    # 上一个交易日  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=today, n=context.alpha_STR_history_num)  
    last_date = pre_date_list[-1]  
    start_date = pre_date_list[0]  
    # 计算Dual Thrust通道  
    cal_RSRS(context, context.symbol)  
    # 每月最后一个交易日时换股  
    if today[5:7] != next_date[5:7]:  
        # 获取全A股票（剔除停牌股和ST股）  
        all_stocks, all_stocks_str = get_normal_stocks(context, context.now)  
        # 计算STR因子  
        STR = get_alpha_STR(context, all_stocks_str, start_date=start_date, end_date=last_date)  
        # 获取最小因子的前N只股票  
        trade_stocks = list(  
            STR.replace([-np.inf, np.inf], np.nan).dropna().sort_values(ascending=True)[:context.num].index)  
        print(context.now, '待买入股票：{}'.format(trade_stocks))  
  
        ## 股票交易(回测用当前价格，限价下单；仿真实盘用市价下单)  
        # 获取持仓  
        positions = context.account().positions()  
        # 卖出不在trade_stocks中的持仓(跌停不卖出)  
        new_prices = current([position['symbol'] for position in positions])  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in trade_stocks:  
                new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))  
                if context.mode == MODE_BACKTEST:  
                    price_limit = get_history_instruments(symbol, fields='lower_limit', start_date=context.now,  
                                                          end_date=context.now, df=True)  
                    if len(price_limit) != 0 and len(new_price) != 0 and price_limit['lower_limit'][0] != round(  
                            new_price[0]['price'], 2):  
                        # new_price为空时，是开盘后无成交的现象，此处忽略该情况，可能会包含涨跌停的股票  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,  
                                             position_side=PositionSide_Long, price=new_price[0]['price'])  
                else:  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,  
                                         position_side=PositionSide_Long, price=round(new_price[0]['price'] * 0.99, 2))  
  
        # 买入股票（涨停不买入）  
        if context.zscore >= context.RSRS_lower:  
            new_prices = current(trade_stocks)  
            for symbol in trade_stocks:  
                new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))  
                if context.mode == MODE_BACKTEST:  
                    price_limit = get_history_instruments(symbol, fields='upper_limit', start_date=context.now,  
                                                          end_date=context.now, df=True)  
                    if len(price_limit) != 0 and len(new_price) != 0 and price_limit['upper_limit'][0] != round(  
                            new_price[0]['price'], 2):  
                        # new_price为空时，是开盘后无成交的现象，此处忽略该情况，可能会包含涨跌停的股票  
                        order_target_percent(symbol=symbol, percent=1 / len(trade_stocks), order_type=OrderType_Limit,  
                                             position_side=PositionSide_Long, price=new_price[0]['price'])  
                else:  
                    order_target_percent(symbol=symbol, percent=1 / len(trade_stocks), order_type=OrderType_Market,  
                                         position_side=PositionSide_Long, price=round(new_price[0]['price'] * 1.01, 2))  
  
    # 如果最新价跌破下轨，卖出持仓(跌停不卖出)  
    if context.zscore < context.RSRS_lower:  
        # 获取持仓  
        positions = context.account().positions()  
        new_prices = current([position['symbol'] for position in positions])  
        for position in positions:  
            symbol = position['symbol']  
            new_price = list(filter(lambda x: x[r'symbol'] == symbol, new_prices))  
            if context.mode == MODE_BACKTEST:  
                price_limit = get_history_instruments(symbol, fields='lower_limit', start_date=context.now,  
                                                      end_date=context.now, df=True)  
                if len(price_limit) != 0 and len(new_price) != 0 and price_limit['lower_limit'][0] != round(  
                        new_price[0]['price'], 2):  
                    # new_price为空时，是开盘后无成交的现象，此处忽略该情况，可能会包含涨跌停的股票  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit,  
                                         position_side=PositionSide_Long, price=new_price[0]['price'])  
            else:  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,  
                                     position_side=PositionSide_Long, price=round(new_price[0]['price'] * 0.99, 2))  
  
  
def get_alpha_STR(context, security, start_date, end_date):  
    """计算STR因子数据（包含date当日）  
    :param security：目标股票，'***,***,***'格式  
    :param date：目标日期  
    :param counts：历史回溯天数  
    """    # 计算STR  
    turnrate = get_fundamentals_new(table='trading_derivative_indicator', symbols=security, start_date=start_date, end_date=end_date, fields='TURNRATE', df=True)  
    turnrate = turnrate.drop_duplicates(keep='first').set_index(['pub_date', 'symbol'])  
    turnrate = turnrate[['TURNRATE']].unstack()  
    turnrate.columns = turnrate.columns.droplevel(level=0)  
    turnrate = turnrate.fillna(method='ffill').iloc[-counts:, :]  
    STR = turnrate.std().dropna()  
    # 去极值  
    STR = winsorize_med(STR)  
    # 标准化  
    STR = standardlize(STR)  
    # 中性化  
    STR = neutralize_MarketValue(context, STR, date)  
    return STR  
  
  
def get_fundamentals_new(table, symbols, start_date, end_date, fields, df):  
    df_total = pd.DataFrame()  
    for i in range(len(symbols)//200+1):  
        security_ = symbols[i*200:(i+1)*200]  
        df_ = get_fundamentals(table=table, symbols=security_, start_date=start_date, end_date=end_date, fields=fields, limit=40000, df=df)  
        df_total = pd.concat([df_total,df_])  
    return df_total  
  
  
def cal_RSRS(context, symbol):  
    """计算单只标的的RSRS数据"""  
    end_time = get_previous_N_trading_date(date=context.now, counts=1, exchange='SHSE')  
    prices = history_n(symbol=symbol, frequency='1d', end_time=end_time, fields='symbol,high,low,eob',  
                       count=context.periods_RSRS_M, df=True)  
    highs = prices.high  
    lows = prices.low  
    # 做回归计算  
    context.ans = []  
    for i in range(len(highs))[context.periods_RSRS_N:]:  
        data_high = highs.iloc[i - context.periods_RSRS_N + 1:i + 1]  
        data_low = lows.iloc[i - context.periods_RSRS_N + 1:i + 1]  
        X = sm.add_constant(data_low)  
        model = sm.OLS(data_high, X)  
        results = model.fit()  
        context.ans.append(results.params[1])  
  
    # 计算标准化的RSRS指标  
    # 计算均值序列  
    section = context.ans[-context.periods_RSRS_M:]  
    # 计算均值序列  
    mu = np.mean(section)  
    # 计算标准化RSRS指标序列  
    sigma = np.std(section)  
    context.zscore = (section[-1] - mu) / sigma  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    if inclusive:  
        data[data > mean_ + std_ * scale] = mean_ + std_ * scale  
        data[data < mean_ - std_ * scale] = mean_ - std_ * scale  
    else:  
        data[data > mean_ + std_ * scale] = np.nan  
        data[data < mean_ - std_ * scale] = np.nan  
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def neutralize_MarketValue(context, data, date, counts=1):  
    """  
    市值中性化  
    :param data:待处理数据  
    :param date:目标日期  
    :param counts：历史回溯天数  
    """    if isinstance(data, pd.Series):  
        data = data.to_frame()  
    security = data.index.to_list()  
    market_value = get_fundamentals_n(table='trading_derivative_indicator', symbols=security, end_date=date,  
                                      fields='TOTMKTCAP', count=counts, df=True)  
    max_date = market_value['pub_date'].max()  
    market_value = market_value[market_value['pub_date'] == max_date][['symbol', 'TOTMKTCAP']].set_index('symbol')  
    x = sm.add_constant(market_value)  
    common_index = list(set(x.index) & set(data.index))  
    x = x.loc[common_index, :]  
    data = data.loc[common_index, :]  
    residual = sm.OLS(data, x).fit().resid  # 此处使用最小二乘回归计算  
    return residual  
  
  
def get_previous_N_trading_date(date, counts=1, exchange='SHSE'):  
    """  
    获取end_date前N个交易日,end_date为datetime格式，不包括date日期  
    :param date：目标日期  
    :param counts：历史回溯天数，默认为1，即前一天  
    """    date = pd.Timestamp(date)  
    previous_N_trading_date = \  
    get_trading_dates(exchange=exchange, start_date=date - datetime.timedelta(days=max(counts + 30, counts * 3)),  
                      end_date=date)[-counts - 1]  
    return previous_N_trading_date  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['open']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(context, security, frequency, start_time, end_time, fields, skip_suspended=True, fill_missing=None,  
                adjust=ADJUST_PREV, adjust_end_time='backtest_end_time', df=True):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：context.backtest_end_time  
    """    if adjust_end_time == 'backtest_end_time':  
        adjust_end_time = context.backtest_end_time  
    Data = pd.DataFrame()  
    if frequency == '1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    else:  
        trading_date = history('SHSE.000300', frequency=frequency, start_time=start_time, end_time=end_time,  
                               fields='eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust,  
                               adjust_end_time=adjust_end_time, df=df)  
        trading_date = trading_date['eob']  
    space = 5  
    if len(trading_date) <= space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields,  
                       skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust,  
                       adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date) / space))):  
            start = n * space  
            end = start + space - 1  
            if end >= len(trading_date):  
                data = history(security, frequency=frequency, start_time=trading_date.iloc[start],  
                               end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended,  
                               fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                data = history(security, frequency=frequency, start_time=trading_date.iloc[start],  
                               end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended,  
                               fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data) >= 33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data, data])  
    Data.drop_duplicates(keep='first', inplace=True)  
    if len(Data) > 0:  
        Data = Data.set_index(['eob', 'symbol'])  
        Data = Data.unstack()  
        Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            elif side == 2:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            elif side == 2:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type == 1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now, symbol, order_type_word, side_effect, price,  
                                                         volume))  
    elif status == 5:  
        print('{}:拒单原因：{}'.format(context.now, order['ord_rej_reason_detail']))  
  
  
def on_backtest_finished(context, indicator):  
    print('*' * 50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        '''    run(strategy_id='315df69e-5c18-11ee-869d-00163e02e3d9',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='41c6860c10ca26a3d49232d7df232c4370211ac3',  
        backtest_start_time='2021-12-31 08:00:00',  
        backtest_end_time='2023-08-27 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0007,  # 买入万二手续费+卖出万二手续费和千1印花税，免5  
        backtest_slippage_ratio=0.00123)

# 小市值因子叠加
**1、****策略说明**

全市场股票下剔除黑名单股票，以小市值因子叠加低波动率因子和质量因子进行复合选股，每周进行换股。

      
## coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *
import datetime
import pandas as pd
import numpy as np

"""
本策略根据可实盘模式进行编程，仅供参考学习，建议使用前根据自身资金情况以及风险承受能力调整策略。

逻辑：全市场股票下剔除黑名单股票，以小市值因子叠加低波动率因子和质量因子进行复合选股，每周进行换股。
"""

def init(context):
    # 定义股票池数量
    context.num = 20
    # 波动率周期
    context.volatility = 252
    # 定时任务
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:15:00')
    schedule(schedule_func=trade_algo, date_rule='1d', time_rule='09:30:00')


def algo(context):
    context.trading_status = False# 交易状态，TRUE时启动交易，FALSE关闭
    # 上一个交易日
    now_str = context.now.strftime('%Y-%m-%d')
    last_date = get_previous_n_trading_dates(exchange='SHSE', date=now_str, n=1)[0]
    if context.now.weekday()<=datetime.datetime.strptime(last_date, '%Y-%m-%d').weekday():# 周一
        # 获取A股代码（剔除停牌股、ST股、次新股（365天）,科创版）
        all_stock,all_stock_str = get_normal_stocks(context, context.now)
        # 加强：ROE>0
        fundamental = stk_get_finance_deriv_pt(symbols=all_stock, fields='roe_weight', date=last_date, df=True)
        all_stock = list(fundamental[fundamental['roe_weight']>0]['symbol'])
        # 获取所有股票市值,并按升序排序
        fundamental = stk_get_daily_mktvalue_pt(symbols=all_stock, fields='tot_mv', trade_date=last_date, df=True).sort_values('tot_mv')
        # 获取前2N只股票
        to_buy = list(fundamental.iloc[:context.num*2,:]['symbol'])
        # # 计算波动率
        # 开始日期（计算日频收益率）
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=context.now.strftime('%Y-%m-%d'), n=context.volatility+1)[0]
        # 获取收盘价
        close = history_new(security=to_buy,frequency='1d',start_time=start_date,end_time=last_date,fields='eob,symbol,close',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=last_date, df=True)
        # 日频收益率
        ret = (close/close.shift(1)-1).iloc[1:,:]
        # 波动率
        volatility = ret.std().sort_values()
        # 目标股票
        context.to_buy = list(volatility.iloc[:context.num].index)
        print('{}:本次股票池有{}只:{}'.format(context.now,len(context.to_buy),context.to_buy))
        context.trading_status = True


def trade_algo(context):
    # 卖出交易(回测用当前价格，限价下单；仿真实盘用市价下单)
    if context.trading_status:
        positions = context.account().positions()
        # 平不在标的池的股票(实盘中市价单要指定保护价，回测中可不填写，以提高速度，延长回测开始时间)
        if context.mode==MODE_BACKTEST:
            for position in positions:
                symbol = position['symbol']
                if symbol not in context.to_buy:
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long)
        else:
            new_prices = current([position['symbol'] for position in positions])
            for position in positions:
                symbol = position['symbol']
                if symbol not in context.to_buy:
                    new_price = list(filter(lambda x:x[r'symbol'] == symbol, new_prices))
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long, price=round(new_price[0]['price']*0.99,2))

        # 买入交易(回测用当前价格，限价下单；仿真实盘用市价下单)
        # 买在标的池中的股票(实盘中市价单要指定保护价，回测中可不填写，以提高速度，延长回测开始时间)
        if context.mode==MODE_BACKTEST:
            for symbol in context.to_buy:
                order_target_percent(symbol=symbol, percent=1/context.num, position_side=PositionSide_Long, order_type=OrderType_Market)
        else:
            new_prices = current(context.to_buy)
            for symbol in context.to_buy:
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, new_prices))
                order_target_percent(symbol=symbol, percent=1/context.num, position_side=PositionSide_Long, order_type=OrderType_Market,price=round(new_price[0]['price']*1.01,2))


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            elif side == 2:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            elif side == 2:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type==1 else '市价'
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))
       

def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False, return_info=False):
    """
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））
    :param date：目标日期
    :param new_days:新股上市天数，默认为365天
    :param skip_suspended:是否剔除停牌股，默认为True
    :param skip_st:是否剔除ST股，默认为True
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效
    """
    date = pd.Timestamp(date).replace(tzinfo=None)
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])
    # A股，剔除停牌和ST股票
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)
    if len(stocks_info)>0:
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]
        all_stocks = list(stocks_info['symbol'])
        # 剔除开盘涨停股
        if skip_upper_limit and context.mode==MODE_BACKTEST:
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)
            stocks_info = stocks_info.merge(low_price,on=['symbol'])
            all_stocks = stocks_info[stocks_info['open']!=stocks_info['upper_limit']]['symbol'].tolist()
    else:
        all_stocks = []
    all_stocks_str = ','.join(all_stocks)
    if return_info:
        return all_stocks,all_stocks_str,stocks_info
    else:
        return all_stocks,all_stocks_str


def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):
    """
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame
    """
    Data = pd.DataFrame()
    if frequency=='1d':
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))
    elif frequency=='tick':
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    # 计算合理间隔
    if isinstance(security,str):
        security = security.split(',')
    space = 30000//len(security)
    # 获取数据
    if len(trading_date)<=space:
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
    else:
        for n in range(int(np.ceil(len(trading_date)/space))):
            start = n*space
            end = start+space
            if end>=len(trading_date):
                if frequency=='1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                elif frequency=='tick':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            else:
                if frequency=='1d':
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
                else:
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)
            if len(data)==33000:
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')
            Data = pd.concat([Data,data])
    if df and len(Data)>0:
        if frequency=='tick': 
            Data.sort_values(['symbol','created_at'],inplace=True)
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)
        else:
            Data.sort_values(['symbol','eob'],inplace=True)
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)
        if type:
            if len(Data)>0:
                if frequency=='tick':
                    Data = Data.set_index(['created_at','symbol'])
                else:
                    Data = Data.set_index(['eob','symbol'])
                Data = Data.unstack()
                Data.columns = Data.columns.droplevel(level=0)
    return Data

    
def on_backtest_finished(context, indicator):
    print('*'*50)
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')


if __name__ == '__main__':
    '''
    strategy_id策略ID,由系统生成
    filename文件名,请与本文件名保持一致
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
    token绑定计算机的ID,可在系统设置-密钥管理中生成
    backtest_start_time回测开始时间
    backtest_end_time回测结束时间
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
    backtest_initial_cash回测初始资金
    backtest_commission_ratio回测佣金比例
    backtest_slippage_ratio回测滑点比例
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
    '''
    run(strategy_id='a310e623-27c0-11ef-bda8-f46b8c02346f',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        backtest_start_time='2014-01-01 08:00:00',
        backtest_end_time='2024-06-10 16:00:00',
        backtest_adjust=ADJUST_NONE,
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0007,
        backtest_slippage_ratio=0.00123,
        backtest_match_mode=1)

# 优化换手率
**1、****策略说明：**

基于东吴金工《“技术分析拥抱选股因子”系列研究（八）：优加换手率：解决1+1＜2的难题》研报复现。

## coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
import multiprocessing  
from sklearn.linear_model import LinearRegression  
  
'''  
本策略计算优加换手率因子，通过10分组回测筛选有效因子；  
10分组回测基本思想：设定所需优化的参数数值范围及步长，将参数数值循环输入进策略，进行遍历回测，  
                 记录每次回测结果和参数，根据某种规则将回测结果排序，找到最好的参数。  
1、定义策略函数  
2、多进程循环输入参数数值  
3、获取回测报告，生成DataFrame格式  
4、排序  
'''  
  
def init(context):  
    # 每月的第一个交易日的09:40:00执行策略algo  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:50:00')  
    # 设置交易标的  
    context.symbol = None  
    # 设置买入股票资金比例  
    context.ratio = 1  
    # 持股数量  
    context.holding_num = 30  
  
  
def MAD(data, N):  
    """  
    ---N倍中位数去极值---  
    1 求所有因子的中位数 median    2 求每个因子与中位数的绝对偏差值，求得到绝对偏差值的中位数 new_median    3 根据参数 N 确定合理的范围为 [median?N*new_median,median+N*new_median]，并针对超出合理范围的因子值做调整  
    """    median = data.quantile(0.5)  
    new_median = abs(data - median).quantile(0.5)  
    # 定义N倍的中位数上下限  
    high = median + N * new_median  
    low = median - N * new_median  
    # 替换上下限  
    data = np.where(data > high, high, data)  
    data = np.where(data < low, low, data)  
    return data  
  
def Standardize(data):  
    """  
    ---数据标准化---  
    标准化后的数值 = (原始值 - 原始值均值) / 原始值标准差  
    """    mean = data.mean()  
    std = data.std()  
    return (data - mean) / std  
  
def LR(x, y):  
    '''  
    ---建立回归方程---  
    '''    lr = LinearRegression()  
    # 拟合  
    lr.fit(x, y)  
    # 预测  
    y_predict = lr.predict(x)  
    # 求残差  
    data = y - y_predict  
    return data  
  
def algo(context):  
    # 当前时间  
    now_date = context.now.strftime('%Y-%m-%d')  
  
    # 获取上一个交易日日期，即为上个月月底的日期  
    last_date = get_previous_trading_date(exchange='SHSE', date=now_date)  
    # 下一交易日  
    next_day = get_N_trading_date(context,now_date,counts=1,model='next',exchange='SHSE')  
    # 判断是否为每个月最后一个交易日  
    if context.now.month!=pd.Timestamp(next_day).month:  
        # 获取指数成分股  
        stocks = get_history_constituents(index='SHSE.000852', start_date=last_date, end_date=last_date)\  
                                        [0]['constituents'].keys()  
  
        # 获取沪深300成分股过去20个交易日的换手率  
        fundamentals = get_fundamentals_n_new(table='trading_derivative_indicator', symbols=list(stocks),  
                                        end_date=last_date, count=20, fields='TURNRATE, NEGOTIABLEMV',df=True)  
  
        # 对股票进行分组，计算每只股票过去20个交易日的换手率的均值、市值均值  
        fundamental = fundamentals.groupby('symbol')[['TURNRATE', 'NEGOTIABLEMV']].mean()  
        fundamental.columns = ['turn_mean', 'pe_mean']  
  
        # 对股票进行分组，计算每只股票过去20个交易日的换手率的标准差  
        fundamental['turn_std'] = fundamentals.groupby('symbol')['TURNRATE'].std()  
  
        # 计算量小换手率因子：对每只股票过去20个交易日的换手率的均值，做市值中性化处理  
        # 提取回归数据 x1: 市值, y1: 换手率均值  
        fundamental['turn_mean'] = MAD(fundamental['turn_mean'], 3)  
        fundamental['turn_mean'] = Standardize(fundamental['turn_mean'])  
        x1 = fundamental['pe_mean'].values.reshape(-1, 1)  
        y1 = fundamental['turn_mean']  
        # 市值中性化处理得出量小换手率因子  
        fundamental['turn_20'] = LR(x1, y1)  
  
        # 计算量稳换手率因子：对每只股票过去20个交易日的换手率的标准差，做市值中性化处理  
        # 提取回归数据 x2: 市值, y2: 换手率标准差  
        fundamental['turn_std'] = MAD(fundamental['turn_std'], 3)  
        fundamental['turn_std'] = Standardize(fundamental['turn_std'])  
        x2 = fundamental['pe_mean'].values.reshape(-1, 1)  
        y2 = fundamental['turn_std']  
        # 市值中性化处理得出量稳换手率因子  
        fundamental['str'] = LR(x2, y2)  
  
        # 计算优加换手率：最终得分，即为优加换手率因子的因子值  
        # 先将所有样本按照量稳因子从小到大排序，打分 1,2,……,N-1,N，N 为当期样本数量，记为“得分 1”；  
        fundamental.sort_values(by='str', inplace=True)  
        fundamental['score_1'] = range(1, len(fundamental) + 1)  
  
        # 取量稳因子排名靠前的50%样本，再将它们按照量小因子从大到小排序，打分 1,2,…,N/2，记为“得分 2”；  
        # 这些股票的最终得分为 “得分 1”+“得分 2”        fund_1 = fundamental.iloc[:int(len(fundamental) / 2)]  
        fund1 = fund_1.copy()  
        fund1.sort_values(by='turn_20', ascending=False, inplace=True)  
        fund1['score_2'] = range(1, len(fund1) + 1)  
        fund1['scores'] = fund1['score_1'] + fund1['score_2']  
  
        # 量稳因子排名靠后的50%样本，则将它们按照量小因子从小到大排序，打分 1,2,…,N/2，记为“得分 3”；  
        # 这些股票的最终得分为 “得分 1”+“得分 3”        fund_2 = fundamental.iloc[int(len(fundamental) / 2):]  
        fund2 = fund_2.copy()  
        fund2.sort_values(by='turn_20', inplace=True)  
        fund2['score_3'] = range(1, len(fund2) + 1)  
        fund2['scores'] = fund2['score_1'] + fund2['score_3']  
  
        # 合并fund1、fund2，按照最终得分从小到大排序  
        fundamental = pd.concat([fund1, fund2], join='inner')  
        fundamental.sort_values(by='scores', inplace=True)  
  
        # 进行10分组回测：第N次回测，对第N组的优加换手率进行回测，其中，对第N组中10%优加换手率较小的股票进行买入  
        data = fundamental.iloc[:context.holding_num]  
        to_buy = data.index.to_list()  
  
        # 获取持仓  
        positions = context.account().positions()  
        holding_symbols = [posi['symbol'] for posi in positions]  
        new_prices = history(symbol=holding_symbols, frequency='1d', start_time=now_date, end_time=now_date, fields='symbol,close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)  
        # 平不在标的池的股票（注：日频市价单，backtest_match_mode=1，以当日收盘价进行交易）  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in to_buy:  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, new_prices))  
                if len(new_price)==0:continue  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,position_side=PositionSide_Long,price=new_price[0]['close']*0.99)  
                print('市价单平不在股票池的仓位', symbol)  
  
  
        # 计算每只股票买入比例  
        percent = context.ratio / len(to_buy)  
        new_prices = history(symbol=to_buy, frequency='1d', start_time=now_date, end_time=now_date, fields='symbol,close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)  
        # 将股票池中的股票持仓调整至percent  
        for symbol in to_buy:  
            new_price = list(filter(lambda x:x[r'symbol'] == symbol, new_prices))  
            if len(new_price)==0:continue  
            order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market,  
                                position_side=PositionSide_Long, price=new_price[0]['close']*1.01)  
            print('以市价单调仓至买入比例', symbol)  
  
  
def get_fundamentals_n_new(table, symbols, end_date, fields,count, df):  
    df_total = pd.DataFrame()  
    for i in range(len(symbols)//200+1):  
        security_ = symbols[i*200:(i+1)*200]  
        df_ = get_fundamentals_n(table=table, symbols=security_, end_date=end_date, fields=fields,count=count, df=df)  
        df_total = pd.concat([df_total,df_])  
    return df_total  
  
def on_backtest_finished(context, indicator):  
    print(indicator)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
    '''    run(strategy_id='859c3b0a-1765-11ec-acc4-d0509949eed8',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2018-12-28 08:00:00',  
        backtest_end_time='2023-10-08 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# 优化换手率二
**1、****策略说明：**

基于东吴金工《“技术分析拥抱选股因子”系列研究(十二):优加_换手率UTR选股因子2.0_》研报复现。

## coding=utf-8
from __future__ import print_function, absolute_import, unicode_literals
from gm.api import *

import numpy as np
import pandas as pd
import multiprocessing
from sklearn.linear_model import LinearRegression

'''
本策略计算优加换手率因子，通过10分组回测筛选有效因子；
10分组回测基本思想：设定所需优化的参数数值范围及步长，将参数数值循环输入进策略，进行遍历回测，
                 记录每次回测结果和参数，根据某种规则将回测结果排序，找到最好的参数。
1、定义策略函数
2、多进程循环输入参数数值
3、获取回测报告，生成DataFrame格式
4、排序
'''

def init(context):
    # 每月的第一个交易日的09:40:00执行策略algo
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:50:00')
    # 设置交易标的
    context.symbol = None
    # 设置买入股票资金比例
    context.ratio = 1
    # 持股数量
    context.holding_num = 30


def MAD(data, N):
    """
    ---N倍中位数去极值---
    1 求所有因子的中位数 median
    2 求每个因子与中位数的绝对偏差值，求得到绝对偏差值的中位数 new_median
    3 根据参数 N 确定合理的范围为 [median?N*new_median,median+N*new_median]，并针对超出合理范围的因子值做调整
    """
    median = data.quantile(0.5)
    new_median = abs(data - median).quantile(0.5)
    # 定义N倍的中位数上下限
    high = median + N * new_median
    low = median - N * new_median
    # 替换上下限
    data = np.where(data > high, high, data)
    data = np.where(data < low, low, data)
    return data

def Standardize(data):
    """
    ---数据标准化---
    标准化后的数值 = (原始值 - 原始值均值) / 原始值标准差
    """
    mean = data.mean()
    std = data.std()
    return (data - mean) / std

def LR(x, y):
    '''
    ---建立回归方程---
    '''
    lr = LinearRegression()
    # 拟合
    lr.fit(x, y)
    # 预测
    y_predict = lr.predict(x)
    # 求残差
    data = y - y_predict
    return data

def algo(context):
    # 当前时间
    now_date = context.now.strftime('%Y-%m-%d')

    # 获取上一个交易日日期，即为上个月月底的日期
    last_date = get_previous_trading_date(exchange='SHSE', date=now_date)
    # 下一交易日
    next_day = get_N_trading_date(context,now_date,counts=1,model='next',exchange='SHSE')
    # 判断是否为每个月最后一个交易日
    if context.now.month!=pd.Timestamp(next_day).month:
        # 获取指数成分股
        stocks = get_history_constituents(index='SHSE.000852', start_date=last_date, end_date=last_date)\
                                        [0]['constituents'].keys()

        # 获取沪深300成分股过去20个交易日的换手率
        fundamentals = get_fundamentals_n_new(table='trading_derivative_indicator', symbols=list(stocks),
                                        end_date=last_date, count=20, fields='TURNRATE, NEGOTIABLEMV',df=True)

        # 对股票进行分组，计算每只股票过去20个交易日的换手率的均值、市值均值
        fundamental = fundamentals.groupby('symbol')[['TURNRATE', 'NEGOTIABLEMV']].mean()
        fundamental.columns = ['turn_mean', 'pe_mean']

        # 对股票进行分组，计算每只股票过去20个交易日的换手率的标准差
        fundamental['turn_std'] = fundamentals.groupby('symbol')['TURNRATE'].std()

        # 计算量小换手率因子：对每只股票过去20个交易日的换手率的均值，做市值中性化处理
        # 提取回归数据 x1: 市值, y1: 换手率均值
        fundamental['turn_mean'] = MAD(fundamental['turn_mean'], 3)
        fundamental['turn_mean'] = Standardize(fundamental['turn_mean'])
        x1 = fundamental['pe_mean'].values.reshape(-1, 1)
        y1 = fundamental['turn_mean']
        # 市值中性化处理得出量小换手率因子
        fundamental['turn_20'] = LR(x1, y1)

        # 计算量稳换手率因子：对每只股票过去20个交易日的换手率的标准差，做市值中性化处理
        # 提取回归数据 x2: 市值, y2: 换手率标准差
        fundamental['turn_std'] = MAD(fundamental['turn_std'], 3)
        fundamental['turn_std'] = Standardize(fundamental['turn_std'])
        x2 = fundamental['pe_mean'].values.reshape(-1, 1)
        y2 = fundamental['turn_std']
        # 市值中性化处理得出量稳换手率因子
        fundamental['str'] = LR(x2, y2)

        # 计算优加换手率：最终得分，即为优加换手率因子的因子值
        # 先将所有样本按照量稳因子从小到大排序，打分 1,2,……,N-1,N，N 为当期样本数量，记为“得分 1”；
        fundamental.sort_values(by='str', inplace=True)
        fundamental['score_1'] = range(1, len(fundamental) + 1)

        # 取量稳因子排名靠前的50%样本，再将它们按照量小因子从大到小排序，打分 1,2,…,N/2，记为“得分 2”；
        # 这些股票的最终得分为 “得分 1”+“得分 2”
        fund_1 = fundamental.iloc[:int(len(fundamental) / 2)]
        fund1 = fund_1.copy()
        fund1.sort_values(by='turn_20', ascending=False, inplace=True)
        fund1['score_2'] = range(1, len(fund1) + 1)
        fund1['scores'] = fund1['score_1'] + fund1['score_2']

        # 量稳因子排名靠后的50%样本，则将它们按照量小因子从小到大排序，打分 1,2,…,N/2，记为“得分 3”；
        # 这些股票的最终得分为 “得分 1”+“得分 3”
        fund_2 = fundamental.iloc[int(len(fundamental) / 2):]
        fund2 = fund_2.copy()
        fund2.sort_values(by='turn_20', inplace=True)
        fund2['score_3'] = range(1, len(fund2) + 1)
        fund2['scores'] = fund2['score_1'] + fund2['score_3']

        # 合并fund1、fund2，按照最终得分从小到大排序
        fundamental = pd.concat([fund1, fund2], join='inner')
        fundamental.sort_values(by='scores', inplace=True)

        # 进行10分组回测：第N次回测，对第N组的优加换手率进行回测，其中，对第N组中10%优加换手率较小的股票进行买入
        data = fundamental.iloc[:context.holding_num]
        to_buy = data.index.to_list()

        # 获取持仓
        positions = context.account().positions()
        holding_symbols = [posi['symbol'] for posi in positions]
        new_prices = history(symbol=holding_symbols, frequency='1d', start_time=now_date, end_time=now_date, fields='symbol,close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)
        # 平不在标的池的股票（注：日频市价单，backtest_match_mode=1，以当日收盘价进行交易）
        for position in positions:
            symbol = position['symbol']
            if symbol not in to_buy:
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, new_prices))
                if len(new_price)==0:continue
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,position_side=PositionSide_Long,price=new_price[0]['close']*0.99)
                print('市价单平不在股票池的仓位', symbol)


        # 计算每只股票买入比例
        percent = context.ratio / len(to_buy)
        new_prices = history(symbol=to_buy, frequency='1d', start_time=now_date, end_time=now_date, fields='symbol,close', adjust=ADJUST_PREV, adjust_end_time=context.backtest_end_time, df=False)
        # 将股票池中的股票持仓调整至percent
        for symbol in to_buy:
            new_price = list(filter(lambda x:x[r'symbol'] == symbol, new_prices))
            if len(new_price)==0:continue
            order_target_percent(symbol=symbol, percent=percent, order_type=OrderType_Market,
                                position_side=PositionSide_Long, price=new_price[0]['close']*1.01)
            print('以市价单调仓至买入比例', symbol)


def get_fundamentals_n_new(table, symbols, end_date, fields,count, df):
    df_total = pd.DataFrame()
    for i in range(len(symbols)//200+1):
        security_ = symbols[i*200:(i+1)*200]
        df_ = get_fundamentals_n(table=table, symbols=security_, end_date=end_date, fields=fields,count=count, df=df)
        df_total = pd.concat([df_total,df_])
    return df_total

def on_backtest_finished(context, indicator):
    print(indicator)


if __name__ == '__main__':
    '''
        strategy_id策略ID,由系统生成
        filename文件名,请与本文件名保持一致
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID,可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
    '''
    run(strategy_id='859c3b0a-1765-11ec-acc4-d0509949eed8',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        backtest_start_time='2018-12-28 08:00:00',
        backtest_end_time='2023-10-08 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0007,
        backtest_slippage_ratio=0.00123,
        backtest_match_mode=1)

# 换手率策略
**1、****策略说明：**

传统换手率作为一个因子，效果怎么样呢？

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
import multiprocessing  
import statsmodels.api as sm  
  
  
def init(context):  
    # 目标指数  
    context.index_code = 'SHSE.000300'# 中证1000 'SHSE.000852'， 沪深300 'SHSE.000300'  
    # 因子计算长度  
    context.urt_length = 20# 20天交易日  
    # 持仓数量  
    context.holding_num = 30  
    # 定时任务，日频，月底换仓的逻辑在algo中进行判断  
    schedule(schedule_func=algo, date_rule='1d', time_rule='14:55:00')  
  
  
def algo(context):  
    # 当前时间  
    now_date = context.now.strftime('%Y-%m-%d')  
  
    next_day = get_next_n_trading_dates(exchange='SHSE', date=now_date, n=1)[0]  
    # 判断是否为每个月最后一个交易日  
    if context.now.month!=pd.Timestamp(next_day).month:  
        # 获取上一个交易日日期  
        last_date = get_previous_n_trading_dates(exchange='SHSE', date=now_date, n=1)[0]  
  
        # 获取指数成分股  
        stocks = list(stk_get_index_constituents(index=context.index_code, trade_date=last_date)['symbol'])  
  
        # 计算传统换手率并去极值、标准化、市值中性化  
        turnrate = stk_get_daily_basic_pt_new(symbols=stocks, fields='turnrate', date=last_date, counts=context.urt_length, df=True)  
        turnrate20 = turnrate.groupby('symbol')['turnrate'].mean()  
        turnrate20 = winsorize_med(turnrate20)  
        turnrate20 = standardlize(turnrate20)  
        turnrate20 = neutralize_MarketValue(context, data=turnrate20, date=last_date, counts=1)  
  
        turnrate20_ = turnrate20.sort_values()  
        to_buy = list(turnrate20_[:context.holding_num].index)  
        print('{}:待买入股票池{}只：{}'.format(context.now,len(to_buy),to_buy))  
  
        # 交易  
        positions = context.account().positions()  
        holding_symbol = [posi['symbol'] for posi in positions]  
        all_symbol = list(set(holding_symbol)|set(to_buy))  
        data_price = history(symbol=all_symbol, frequency='1d', start_time=now_date,  end_time=now_date, adjust=ADJUST_NONE, df=False)  
        data_info = get_symbols(sec_type1=1010, symbols=all_symbol, trade_date=now_date, skip_st=False)  
        # 卖出（跌停不卖出，回测时用限价单，实盘时用市价单）  
        for symbol in holding_symbol:  
            if symbol not in to_buy:  
                # 收盘价（日频数据）  
                new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
                new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
                if len(new_price)==0 or len(new_info)==0:continue  
                new_price = new_price[-1]['close']  
                if context.mode==MODE_BACKTEST:  
                    # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                    if new_info[0]['lower_limit']!=new_price:  
                        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
                else:  
                    # 实盘时下市价单，以涨停价作为保护限价  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['lower_limit'])  
  
        # 买入（涨停不买入，回测时用限价单，实盘时用市价单）  
        for symbol in to_buy:  
            # 收盘价（日频数据）  
            new_price = list(filter(lambda x:x[r'symbol'] == symbol, data_price))  
            new_info = list(filter(lambda x:x[r'symbol'] == symbol, data_info))  
            if len(new_price)==0 or len(new_info)==0:continue  
            new_price = new_price[-1]['close']  
            if context.mode==MODE_BACKTEST:  
                # 回测时下限价单，先判断是否跌停，以收盘价撮合  
                if new_info[0]['upper_limit']!=new_price:  
                    order_target_percent(symbol=symbol, percent=1/context.holding_num, order_type=OrderType_Limit, position_side=PositionSide_Long, price=new_price)  
            else:  
                # 实盘时下市价单，以涨停价作为保护限价  
                order_target_percent(symbol=symbol, percent=1/context.holding_num, order_type=OrderType_Market, position_side=PositionSide_Long,price=new_info[-1]['upper_limit'])  
  
  
def stk_get_daily_basic_pt_new(symbols, fields, date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_basic_pt函数，count为正整数  
    """    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=counts)  
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_basic_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def stk_get_daily_mktvalue_pt_new(symbols, fields, date=None, counts:int = None, df=False):  
    """  
    多日期调用stk_get_daily_mktvalue_pt函数，count为正整数  
    """    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=counts)  
    # 循环获取数据  
    df_total = pd.DataFrame()  
    for date in date_list:  
        df_new = stk_get_daily_mktvalue_pt(symbols=symbols, fields=fields, trade_date=date, df=df)  
        if df:  
            df_total = pd.concat([df_total,df_new])  
        else:  
            df_total = df_total+df_new  
    return df_total  
  
  
def winsorize_med(data, scale=3, inclusive=True, inf2nan=True):  
    """  
    去极值  
    :param data：待处理数据[Series]  
    :param scale：标准差倍数，默认为3  
    :param inclusive：True为将边界外的数值调整为边界值，False为将边界外的数值调整为NaN  
    :param inf2nan：True为将inf转化为nan，False不转化  
    """    data = data.astype('float')  
    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    std_ = data.std()  
    mean_ = data.mean()  
    if inclusive:  
        data[data>mean_+std_*scale]=mean_+std_*scale  
        data[data<mean_-std_*scale]=mean_-std_*scale  
    else:  
        data[data>mean_+std_*scale]=np.nan  
        data[data<mean_-std_*scale]=np.nan  
    return data  
  
  
def standardlize(data, inf2nan=True):  
    """  
    标准化  
    :param data:待处理数据  
    :param inf2nan：是否将inf转化为nan  
    """    if inf2nan:  
        data = data.replace([np.inf, -np.inf], np.nan)  
    return (data - data.mean()) / data.std()  
  
  
def neutralize_MarketValue(context,data,date,counts=1):  
    """  
    市值中性化  
    :param data:待处理数据  
    :param date:目标日期  
    :param counts：历史回溯天数  
    """    if isinstance(data,pd.Series):  
        data = data.to_frame()  
    security = data.index.to_list()  
    market_value = stk_get_daily_mktvalue_pt_new(symbols=security, fields='tot_mv', date=date, counts=counts, df=True)  
    market_value_mean = market_value.groupby('symbol')['tot_mv'].mean()  
    x = sm.add_constant(market_value_mean)  
    common_index = list(set(x.index) & set(data.index))  
    x = x.loc[common_index,:]  
    data = data.loc[common_index,:]  
    residual = sm.OLS(data, x).fit().resid# 此处使用最小二乘回归计算  
    return residual  
  
  
def on_backtest_finished(context, indicator):  
    print('回测结束。')  
    print(indicator)  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID,由系统生成  
        filename文件名,请与本文件名保持一致  
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID,可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='859c3b0a-1765-11ec-acc4-d0509949eed8',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2018-12-28 08:00:00',  
        backtest_end_time='2023-10-08 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# 国债逆回购策略
**1、****策略说明**

定时进行国债逆回购，并可以设置最低利率阈值，低于该阈值不进行国债逆回购，而进行券商的余额理财（需在券商中开通，具体咨询券商客户经理）。

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
  
"""  
示例策略仅供参考，不建议直接实盘使用。  
  
国债逆回购助手  
定时进行国债逆回购，并可以设置最低利率阈值，低于该阈值不进行国债逆回购，而进行券商的余额理财（需在券商中开通，具体咨询券商客户经理）。  
"""  

def init(context):  
    # 逆回购定时时间  
    context.brr_time = '15:18:00'  
    # 逆回购目标债券(沪市1天期逆回购代码为204001，深市1天期逆回购代码为131810，沪市和深市均为1000起，10张为1手，每张100元，一般沪市相对深市利率高一些)  
    context.symbol = 'SHSE.204001'  
    # 逆回购最低利率（低于该利率不进行逆回购，可开启券商的余额理财产品，2023年10月的某券商余额理财年化约为2.3%）  
    context.min_rate = 2.3  
  
    # 定时任务  
    algo_1(context)  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule=context.brr_time)  
  
  
def algo_1(context):  
    current_data = current(symbols=context.symbol)[0]  
    if current_data['price']<context.min_rate:  
        print('{}:当前逆回购价格低于{}，不进行逆回购，建议开启余额理财产品'.format(context.now,context.min_rate))  
    else:  
        trade_price = current_data['quotes'][4]['bid_p']# 以买五价格作为交易价格  
        account_cash = context.account().cash  
        volume = int(np.floor(account_cash['available']/1000)*10)  
        if volume>0:  
            bond_reverse_repurchase_agreement(symbol=context.symbol, volume=volume, price=trade_price, order_type=OrderType_Market)  
            print('{}:进行逆回购，卖出市值：{}，当前价格：{}'.format(context.now,volume*100,current_data['price']))  
        else:  
            print('{}:金额不足，未进行逆回购'.format(context.now))  
      
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='strategy_id',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='{{token}}',  
        backtest_start_time='2023-10-01 08:00:00',  
        backtest_end_time='2023-11-07 16:00:00',  
        backtest_adjust=ADJUST_PREV,  
        backtest_initial_cash=10000000,  
        backtest_commission_ratio=0.0001,  
        backtest_slippage_ratio=0.0001,  
        backtest_match_mode=1)

# 高股息成长股策略
**1、****策略说明：**

2024年高股息策略如火如荼，策略未来能否持续走红，又该如何进一步优化呢？

## coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *

import time
import datetime
import pandas as pd

"""
策略名称：高股息成长股策略
策略逻辑：在全市场中选股，剔除高风险股票（ST、次新股、停牌、短期涨幅过高、盈利下滑的股票），
平均买入剩余股票池中股息率最高的一部分股票，每个月月初进行调仓换股。
"""

def init(context):
    # 策略参数
    context.max_holding = 20  # 最大持股数量
    context.momentum_periods = 21  # 选股条件：短期涨幅的周期
    context.momentum_threshold = 0.2  # 选股条件：短期涨幅的阈值比例
    context.earning_threshold = 0  # 选股条件：盈利下滑的阈值
    context.to_buy = []  # 目标股票池

    # 定时任务：盘前运行
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:15:00')
    # 定时任务：盘中运行
    schedule(schedule_func=begin_market, date_rule='1d', time_rule='14:55:00')


def before_market(context):
    """
    盘前定时任务：筛选股票池
    """
    # 时间格式转换：将datetime转化为str
    date_str = context.now.strftime('%Y-%m-%d')
    # 判断是否是月初第一个交易日
    context.status_is_first_trading_day_of_month = is_first_trading_day_of_month(context, date_str)
    # 月初换股
    if context.status_is_first_trading_day_of_month:
        # 获取全A股票代码（剔除ST和停牌），采用get_symbols()函数
        context.symbols_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=True, skip_st=True,
                                           trade_date=date_str, df=True)
        context.symbols_info['delisted_date'] = context.symbols_info['delisted_date'].apply(
            lambda x: x.replace(tzinfo=None))
        # 过滤次新股（1年以内的次新股）和退市股(包括退市整理期）
        next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date_str, n=20)[-1])
        context.symbols_info = context.symbols_info[
            (context.symbols_info['listed_date'] <= context.now - datetime.timedelta(days=365)) & (
                        context.symbols_info['delisted_date'] > next_20date)]
        all_symbols = list(context.symbols_info['symbol'])

        # 剔除一字板（最低价==涨停价）
        if context.mode == MODE_BACKTEST:
            low_price = history(symbol=all_symbols, frequency='1d', start_time=date_str, end_time=date_str,
                                fields='low,symbol', adjust=ADJUST_NONE, df=True)
            context.symbols_info = context.symbols_info.merge(low_price, on=['symbol'])
            all_symbols = context.symbols_info[context.symbols_info['low'] != context.symbols_info['upper_limit']][
                'symbol'].tolist()
        # 过滤短期涨幅和跌幅过高（剔除最近21个交易日涨幅靠前的股票）
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date_str,
                                                 n=context.momentum_periods + 1)  # 多获取一天，以计算涨跌幅
        pre_n_date, pre_date = date_list[0], date_list[-1]
        # 获取收盘价，计算涨跌幅，采用history()函数
        close_price = history(symbol=all_symbols, frequency='1d', start_time=pre_n_date, end_time=pre_date,
                              fields='eob,symbol,close', fill_missing='Last', adjust=ADJUST_PREV, df=True)
        momentum = close_price.groupby('symbol')['close'].apply(
            lambda x: x.iloc[-1] / x.iloc[0] - 1)  # 通过groupby分组计算涨跌幅
        momentum.sort_values(ascending=True, inplace=True)  # 排序，根据涨跌幅从小到大排序
        momentum_threshold_num = int(len(momentum) * context.momentum_threshold)  # 剔除股票数的取整
        all_symbols = list(momentum.iloc[:int(len(momentum) - momentum_threshold_num)].index)
        # 过滤盈利下滑的股票（剔除利润总额同比增长率为负的股票），采用stk_get_finance_deriv_pt()函数
        ttl_prof_yoy = stk_get_finance_deriv_pt(symbols=all_symbols, fields='ttl_prof_yoy', date=pre_date,
                                                df=True)  # 获取利润总额同比增长率
        all_symbols = list(
            (ttl_prof_yoy[ttl_prof_yoy['ttl_prof_yoy'] > context.earning_threshold])['symbol'])  # 过滤增长率为负的股票
        # 选取剩余股票池中股息率最高的一部分股票，采用stk_get_daily_valuation_pt()函数
        dy_ttm = stk_get_daily_valuation_pt(symbols=all_symbols, fields='dy_ttm', trade_date=pre_date,
                                            df=True).dropna()  # 获取股息率TTM(滚动 12 月)，并删除缺失数据的股票
        dy_ttm.sort_values('dy_ttm', ascending=True, inplace=True)  # 排序，根据涨股息率TTM从小到大排序
        context.to_buy = list(dy_ttm.iloc[-context.max_holding:, :]['symbol'])  # 选股股息率最高的股票
        print('{}:目标股票池为：{}'.format(context.now, context.to_buy))


def begin_market(context):
    """
    盘中定时任务：买卖交易
    """
    # 月初换股
    if context.status_is_first_trading_day_of_month:
        positions = get_position()
        # 实盘时下单添加保护限价
        if context.mode == MODE_BACKTEST:
            # 卖出不在目标股票池的股票
            for position in positions:
                symbol = position['symbol']
                if symbol not in context.to_buy:
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
                                         position_side=PositionSide_Long)

            # 买入在目标股票池且无持仓的股票
            for symbol in context.to_buy:
                order_target_percent(symbol=symbol, percent=1 / context.max_holding, order_type=OrderType_Market,
                                     position_side=PositionSide_Long)
        else:
            # 卖出不在目标股票池的股票
            for position in positions:
                symbol = position['symbol']
                if symbol not in context.to_buy:
                    price = context.symbols_info.loc[context.symbols_info['symbol'] == symbol, 'lower_limit']
                    if len(price) == 0: continue
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market,
                                         position_side=PositionSide_Long, price=price)
            # 暂停5秒，有可能存在卖出委托资金还未回笼
            time.sleep(10)

            # 买入在目标股票池且无持仓的股票
            for symbol in context.to_buy:
                price = context.symbols_info.loc[context.symbols_info['symbol'] == symbol, 'upper_limit']
                order_target_percent(symbol=symbol, percent=1 / context.max_holding, order_type=OrderType_Market,
                                     position_side=PositionSide_Long, price=price)


def is_first_trading_day_of_month(context, date_str):
    """
    判断日期date_str是否是当月第一个交易日

    参数：
    date_str(str)：日期

    返回：
    True：日期date是当月的第一个交易日
    False：日期date不是当月的第一个交易日
    """
    # 获取前一个交易日，采用get_previous_n_trading_dates()函数，该函数要求输入的时间类型为str，返回日期（str）列表
    pre_date_str = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)[0]
    # 时间格式转化，将str格式的日期转换成datetime
    pre_date = datetime.datetime.strptime(pre_date_str, '%Y-%m-%d')
    date = datetime.datetime.strptime(date_str, '%Y-%m-%d')
    # 判断是否是当月第一个交易日
    return date.month != pre_date.month


if __name__ == '__main__':
    '''
        strategy_id策略ID, 由系统生成
        filename文件名, 请与本文件名保持一致
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID, 可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
        '''
    run(strategy_id='b8ca6192-fbb7-11ee-877c-f46b8c02346f',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        backtest_start_time='2014-01-01 08:00:00',
        backtest_end_time='2024-05-15 16:00:00',
        backtest_adjust=ADJUST_NONE,
        backtest_initial_cash=1000000,
        backtest_commission_ratio=0.0007,
        backtest_slippage_ratio=0.00123,
        backtest_match_mode=1)

# 量化打板工具
**1、****策略说明**

本策略是研学会员第一次直播课的策略（还有个打板工具），使用前先观看《如何实现量化打板——打二板策略》视频更佳。

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
致敬原作者：旋涡蓝蛙、子匀、天堂鸟汇信、十足的小市值迷等作者  
  
20240702：1、卖出时增加跌停不卖出的条件（当前价格>跌停价才能卖）；  
          2、剔除伪首板（过滤前天停牌的股票，精确的做法可以剔除停牌前涨停的股票）；  
          3、一些停牌数据造成的报错。  
"""  

def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:28:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str = get_normal_stocks(context, date,new_days=50)  
  
    # 2、过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]  
  
    # 2.1、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 3、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=3)  
    yesterday = pre_date_list[-1]  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)  
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)                          
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = pre_data[pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = pre_data[pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
  
    # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    # 9、剔除伪首板（多日涨停中间有停牌的，会误认为是首板；这边简单地剔除前天停牌的股票，精确的做法可以剔除停牌前涨停的股票）  
    if all_stocks:  
        before_yesterday = pre_date_list[-2]  
        symbols_info = get_symbols(sec_type1=1010, symbols=all_stocks, skip_suspended=False, skip_st=False, trade_date=before_yesterday, df=True)  
        all_stocks = symbols_info[symbols_info['is_suspended']==False]['symbol'].tolist()  
  
    # 10、订阅数据  
    context.buy_stocks = all_stocks  
    print('{} 买入监控股票：{}'.format(context.now, context.buy_stocks))  
      
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        pre_data_adjust = pre_data_adjust.merge(pd.DataFrame(dicts)[['symbol']], on=['symbol'])  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    to_buy = context.buy_stocks  
    for symbol in to_buy:  
        percent = 1/len(to_buy)  
        cash = context.account().cash  
        # 用市价单进行撮合，即下一个tick的开盘价  
        price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
        volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
        if volume>0:  
            order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)  
            unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
        if context.mode==MODE_LIVE:  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
            if call_rate>=context.max_call_rate or call_rate<=context.min_call_rate:  
                print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
                unsubscribe(symbols=symbol, frequency='tick')  
                context.buy_stocks.remove(symbol)  
                return  
  
  
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['open']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='ef13f522-10f9-11ef-9466-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-07-01 08:00:00',  
        backtest_end_time='2024-06-25 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)

# 回测自动备份
**1、****策略说明**

每次回测都能自动保存原代码，备份文件以时间命名，可以手动修改文件名称，或根据时间匹配对应的回测记录，防止混淆了多次修改记录！

## coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  

timestamp = datetime.now().strftime('%Y-%m-%d %H-%M-%S')  

backup_filename = f'回测_{timestamp}.py'  
 
with open(__file__, 'r',encoding='utf-8') as file:  
    original_code = file.read()  

with open(backup_filename, 'w',encoding='utf-8') as file:  
    file.write(original_code)  
  
def init(context):  
    pass  
      
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='ef13f522-10f9-11ef-9466-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-01-01 08:00:00',  
        backtest_end_time='2024-06-16 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# 低估盈利策略
**1、****策略说明**

本策略是研学会员第二次直播课的策略，使用前先观看《用两个指标写一个有效的基本面策略》视频更佳。

## 日频
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import time  
import datetime  
import pandas as pd  
  
  
"""  
策略名称：低估盈利策略  
策略逻辑：  
"""  
  
def init(context):  
    # 策略参数  
    context.to_buy = []                                      # 目标股票池  
  
    # 定时任务：盘前运行  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:15:00')  
    # 定时任务：盘中运行  
    schedule(schedule_func=begin_market, date_rule='1d', time_rule='14:55:00')  
  
  
def before_market(context):  
    """  
    盘前定时任务：筛选股票池  
    """# 时间格式转换：将datetime转化为str  
    date_str = context.now.strftime('%Y-%m-%d')  
    # # 判断是否是月初第一个交易日  
    # context.status_is_first_trading_day_of_month = is_first_trading_day_of_month(context,date_str)  
    # 月初换股/tatus_is_first_trading_day_of_month:  
    # # 获取全A股票代码（剔除ST、停牌、开盘涨跌停、新股等股票）  
    all_symbols,all_stocks_str = get_normal_stocks(context, date=date_str, new_days=365, skip_suspended=True, skip_st=True, skip_upper_limit=True)  
  
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)            # 多获取一天，以计算涨跌幅  
    pre_date = date_list[-1]           
  
    # 低估：PB【pb_mrq：最新报告期MRQ、pb_lyr：最新年报LYR、pb_lf：最新公告】  
    fields1 = 'pb_mrq'  
    pb_lf = stk_get_daily_valuation_pt(symbols=all_symbols, fields=fields1, trade_date=pre_date, df=True).sort_values(fields1)  
    all_symbols = list((pb_lf[(pb_lf[fields1]>0)&(pb_lf[fields1]<1)])['symbol'])              # 筛选PB在0到1之间的数据  
    # 盈利：ROE【roe：摊薄、roe_weight：加权、roe_avg：平均、roe_cut：扣除/摊薄、roe_weight_cut：扣除/加权、roe_cut_avg：扣除/平均、roe_add：增发条件、roe_ann：年化、roa、roa_ann：年化】  
    if len(all_symbols)>0:  
        fields2 = 'roe'  
        roe_weight = stk_get_finance_deriv_pt(symbols=all_symbols, fields=fields2, rpt_type=None, data_type=None, date=pre_date, df=True).sort_values(fields2)  
        roe_weight_new = roe_weight[(roe_weight[fields2]>1)]             # 筛选roe_weight大于1的数据  
        all_symbols = list(roe_weight_new['symbol'])    
  
    context.to_buy = all_symbols  
    print('{}:目标股票池为：{}'.format(context.now,context.to_buy))  
  
  
def begin_market(context):  
    """  
    盘中定时任务：买卖交易  
    """    # 月初换股  
    # if context.status_is_first_trading_day_of_month:  
    positions = get_position()  
    # 实盘时下单添加保护限价  
    if context.mode==MODE_BACKTEST:  
        # 卖出不在目标股票池的股票  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in context.to_buy:  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long)  
  
        # 买入在目标股票池且无持仓的股票  
        for symbol in context.to_buy:  
            order_target_percent(symbol=symbol, percent=1/len(context.to_buy), order_type=OrderType_Market, position_side=PositionSide_Long)  
    else:  
        # 卖出不在目标股票池的股票  
        for position in positions:  
            symbol = position['symbol']  
            if symbol not in context.to_buy:  
                price = context.symbols_info.loc[context.symbols_info['symbol']==symbol,'lower_limit']  
                if len(price)==0:continue  
                order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long, price=price)  
        # # 暂停5秒，有可能存在卖出委托资金还未回笼  
        # time.sleep(5)  
  
        # 买入在目标股票池且无持仓的股票  
        for symbol in context.to_buy:  
            price = context.symbols_info.loc[context.symbols_info['symbol']==symbol,'upper_limit']  
            order_target_percent(symbol=symbol, percent=1/len(context.to_buy), order_type=OrderType_Market, position_side=PositionSide_Long, price=price)  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_upper_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def is_first_trading_day_of_month(context, date_str):  
    """  
    判断日期date_str是否是当月第一个交易日  
        参数：  
    date_str(str)：日期  
        返回：  
    True：日期date是当月的第一个交易日  
    False：日期date不是当月的第一个交易日  
    """    # 获取前一个交易日，采用get_previous_n_trading_dates()函数，该函数要求输入的时间类型为str，返回日期（str）列表  
    pre_date_str = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)[0]  
    # 时间格式转化，将str格式的日期转换成datetime  
    pre_date = datetime.datetime.strptime(pre_date_str,'%Y-%m-%d')  
    date = datetime.datetime.strptime(date_str,'%Y-%m-%d')  
    # 判断是否是当月第一个交易日  
    return date.month!=pre_date.month  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='317cc45c-48de-11ef-94fb-68f728a52ea0',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        # backtest_start_time='2006-01-01 08:00:00',  
        # backtest_end_time='2015-12-31 16:00:00',        backtest_start_time='2016-01-01 08:00:00',  
        backtest_end_time='2024-06-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

## 月频
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import time  
import datetime  
import pandas as pd  
  
  
"""  
策略名称：低估盈利策略  
策略逻辑：  
"""  
  
def init(context):  
    # 策略参数  
    context.to_buy = []                                      # 目标股票池  
  
    # 定时任务：盘前运行  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:15:00')  
    # 定时任务：盘中运行  
    schedule(schedule_func=begin_market, date_rule='1d', time_rule='14:55:00')  
  
  
def before_market(context):  
    """  
    盘前定时任务：筛选股票池  
    """# 时间格式转换：将datetime转化为str  
    date_str = context.now.strftime('%Y-%m-%d')  
    # 判断是否是月初第一个交易日  
    context.status_is_first_trading_day_of_month = is_first_trading_day_of_month(context,date_str)  
    # 月初换股  
    if context.status_is_first_trading_day_of_month:  
        # # 获取全A股票代码（剔除ST、停牌、开盘涨跌停、新股等股票）  
        all_symbols,all_stocks_str = get_normal_stocks(context, date=date_str, new_days=365, skip_suspended=True, skip_st=True, skip_upper_limit=True)  
  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)            # 多获取一天，以计算涨跌幅  
        pre_date = date_list[-1]           
  
        # PB【pb_mrq：最新报告期MRQ、pb_lyr：最新年报LYR、pb_lf：最新公告】  
        fields1 = 'pb_mrq'  
        pb_lf = stk_get_daily_valuation_pt(symbols=all_symbols, fields=fields1, trade_date=pre_date, df=True).sort_values(fields1)  
        all_symbols = list((pb_lf[(pb_lf[fields1]>0)&(pb_lf[fields1]<1)])['symbol'])              # 筛选PB在0到1之间的数据  
        # ROE【roe：摊薄、roe_weight：加权、roe_avg：平均、roe_cut：扣除/摊薄、roe_weight_cut：扣除/加权、roe_cut_avg：扣除/平均、roe_add：增发条件、roe_ann：年化、roa、roa_ann：年化】  
        if len(all_symbols)>0:  
            fields2 = 'roe'  
            roe_weight = stk_get_finance_deriv_pt(symbols=all_symbols, fields=fields2, rpt_type=None, data_type=None, date=pre_date, df=True).sort_values(fields2)  
            roe_weight_new = roe_weight[(roe_weight[fields2]>1)]             # 筛选roe_weight大于1的数据  
            all_symbols = list(roe_weight_new['symbol'])    
  
        context.to_buy = all_symbols  
        print('{}:目标股票池为：{}'.format(context.now,context.to_buy))  
  
  
def begin_market(context):  
    """  
    盘中定时任务：买卖交易  
    """    # 月初换股  
    if context.status_is_first_trading_day_of_month:  
        positions = get_position()  
        # 实盘时下单添加保护限价  
        if context.mode==MODE_BACKTEST:  
            # 卖出不在目标股票池的股票  
            for position in positions:  
                symbol = position['symbol']  
                if symbol not in context.to_buy:  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long)  
  
            # 买入在目标股票池且无持仓的股票  
            for symbol in context.to_buy:  
                order_target_percent(symbol=symbol, percent=1/len(context.to_buy), order_type=OrderType_Market, position_side=PositionSide_Long)  
        else:  
            # 卖出不在目标股票池的股票  
            for position in positions:  
                symbol = position['symbol']  
                if symbol not in context.to_buy:  
                    price = context.symbols_info.loc[context.symbols_info['symbol']==symbol,'lower_limit']  
                    if len(price)==0:continue  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long, price=price)  
            # # 暂停5秒，有可能存在卖出委托资金还未回笼  
            # time.sleep(5)  
  
            # 买入在目标股票池且无持仓的股票  
            for symbol in context.to_buy:  
                price = context.symbols_info.loc[context.symbols_info['symbol']==symbol,'upper_limit']  
                order_target_percent(symbol=symbol, percent=1/len(context.to_buy), order_type=OrderType_Market, position_side=PositionSide_Long, price=price)  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_upper_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def is_first_trading_day_of_month(context, date_str):  
    """  
    判断日期date_str是否是当月第一个交易日  
        参数：  
    date_str(str)：日期  
        返回：  
    True：日期date是当月的第一个交易日  
    False：日期date不是当月的第一个交易日  
    """    # 获取前一个交易日，采用get_previous_n_trading_dates()函数，该函数要求输入的时间类型为str，返回日期（str）列表  
    pre_date_str = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)[0]  
    # 时间格式转化，将str格式的日期转换成datetime  
    pre_date = datetime.datetime.strptime(pre_date_str,'%Y-%m-%d')  
    date = datetime.datetime.strptime(date_str,'%Y-%m-%d')  
    # 判断是否是当月第一个交易日  
    return date.month!=pre_date.month  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='317cc45c-48de-11ef-94fb-68f728a52ea0',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        # backtest_start_time='2006-01-01 08:00:00',  
        # backtest_end_time='2015-12-31 16:00:00',        backtest_start_time='2016-01-01 08:00:00',  
        backtest_end_time='2024-06-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

## 月频-高ROE
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import time  
import datetime  
import pandas as pd  
  
  
"""  
策略名称：低估盈利策略  
策略逻辑：  
"""  
   
def init(context):  
    # 策略参数  
    context.max_holding = 20                                 # 最大持股数量  
    context.to_buy = []                                      # 目标股票池  
  
    # 定时任务：盘前运行  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:15:00')  
    # 定时任务：盘中运行  
    schedule(schedule_func=begin_market, date_rule='1d', time_rule='14:55:00')  
  
  
def before_market(context):  
    """  
    盘前定时任务：筛选股票池  
    """# 时间格式转换：将datetime转化为str  
    date_str = context.now.strftime('%Y-%m-%d')  
    # 判断是否是月初第一个交易日  
    context.status_is_first_trading_day_of_month = is_first_trading_day_of_month(context,date_str)  
    # 月初换股  
    if context.status_is_first_trading_day_of_month:  
        # # 获取全A股票代码（剔除ST、停牌、开盘涨跌停、新股等股票）  
        all_symbols,all_stocks_str = get_normal_stocks(context, date=date_str, new_days=365, skip_suspended=True, skip_st=True, skip_upper_limit=True)  
  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)            # 多获取一天，以计算涨跌幅  
        pre_date = date_list[-1]           
  
        # PB【pb_mrq：最新报告期MRQ、pb_lyr：最新年报LYR、pb_lf：最新公告】  
        fields1 = 'pb_mrq'  
        pb_lf = stk_get_daily_valuation_pt(symbols=all_symbols, fields=fields1, trade_date=pre_date, df=True).sort_values(fields1)  
        all_symbols = list((pb_lf[(pb_lf[fields1]>0)&(pb_lf[fields1]<1)])['symbol'])              # 筛选PB在0到1之间的数据  
        # ROE【roe：摊薄、roe_weight：加权、roe_avg：平均、roe_cut：扣除/摊薄、roe_weight_cut：扣除/加权、roe_cut_avg：扣除/平均、roe_add：增发条件、roe_ann：年化、roa、roa_ann：年化】  
        if len(all_symbols)>0:  
            fields2 = 'roe'  
            roe_weight = stk_get_finance_deriv_pt(symbols=all_symbols, fields=fields2, rpt_type=None, data_type=None, date=pre_date, df=True).sort_values(fields2)  
            roe_weight_new = roe_weight[(roe_weight[fields2]>1)]             # 筛选roe_weight大于1的数据  
            if len(roe_weight_new)>context.max_holding:  
                roe_weight_new.sort_values(by=fields2,ascending=False)  
                roe_weight_new = roe_weight_new.iloc[:context.max_holding,:]  
            all_symbols = list(roe_weight_new['symbol'])    
  
        context.to_buy = all_symbols  
        print('{}:目标股票池为：{}'.format(context.now,context.to_buy))  
  
  
def begin_market(context):  
    """  
    盘中定时任务：买卖交易  
    """    # 月初换股  
    if context.status_is_first_trading_day_of_month:  
        positions = get_position()  
        # 实盘时下单添加保护限价  
        if context.mode==MODE_BACKTEST:  
            # 卖出不在目标股票池的股票  
            for position in positions:  
                symbol = position['symbol']  
                if symbol not in context.to_buy:  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long)  
  
            # 买入在目标股票池且无持仓的股票  
            for symbol in context.to_buy:  
                order_target_percent(symbol=symbol, percent=1/len(context.to_buy), order_type=OrderType_Market, position_side=PositionSide_Long)  
        else:  
            # 卖出不在目标股票池的股票  
            for position in positions:  
                symbol = position['symbol']  
                if symbol not in context.to_buy:  
                    price = context.symbols_info.loc[context.symbols_info['symbol']==symbol,'lower_limit']  
                    if len(price)==0:continue  
                    order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Market, position_side=PositionSide_Long, price=price)  
            # 暂停5秒，有可能存在卖出委托资金还未回笼  
            time.sleep(5)  
  
            # 买入在目标股票池且无持仓的股票  
            for symbol in context.to_buy:  
                price = context.symbols_info.loc[context.symbols_info['symbol']==symbol,'upper_limit']  
                order_target_percent(symbol=symbol, percent=1/len(context.to_buy), order_type=OrderType_Market, position_side=PositionSide_Long, price=price)  
  
  
def get_normal_stocks(context, date, new_days=365, symbols=None, skip_suspended=True, skip_st=True, skip_upper_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除开盘涨停股票，默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    next_20date = pd.Timestamp(get_next_n_trading_dates(exchange='SHSE', date=date.strftime('%Y-%m-%d'), n=20)[-1])  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, symbols=symbols, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股(退市前20个交易日，以过滤退市整理期)  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-datetime.timedelta(days=new_days))&(stocks_info['delisted_date']>next_20date)]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def is_first_trading_day_of_month(context, date_str):  
    """  
    判断日期date_str是否是当月第一个交易日  
        参数：  
    date_str(str)：日期  
        返回：  
    True：日期date是当月的第一个交易日  
    False：日期date不是当月的第一个交易日  
    """    # 获取前一个交易日，采用get_previous_n_trading_dates()函数，该函数要求输入的时间类型为str，返回日期（str）列表  
    pre_date_str = get_previous_n_trading_dates(exchange='SHSE', date=date_str, n=1)[0]  
    # 时间格式转化，将str格式的日期转换成datetime  
    pre_date = datetime.datetime.strptime(pre_date_str,'%Y-%m-%d')  
    date = datetime.datetime.strptime(date_str,'%Y-%m-%d')  
    # 判断是否是当月第一个交易日  
    return date.month!=pre_date.month  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='317cc45c-48de-11ef-94fb-68f728a52ea0',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2006-01-01 08:00:00',  
        backtest_end_time='2015-12-31 16:00:00',  
        # backtest_start_time='2016-01-01 08:00:00',  
        # backtest_end_time='2024-06-30 16:00:00',        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=1000000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)


# 金谷战法
**1、****策略说明**

KDJ超跌定义：J<0，K-J>20

买点：J线金叉K线

卖点：10%开始止盈止损或者J线死叉K线

底背离:价格新低，但K值和D值均未创新低。

**2、****策略来源**

金谷战法之超跌必杀：

[https://vod.jtzyuan.com/su/0/TVRneU16Z3o](https://vod.jtzyuan.com/su/0/TVRneU16Z3o)

KDJ底背离：

[https://www.ixigua.com/6673762009995018755?logTag=c3a4dc18b1cc6d83c094](https://www.ixigua.com/6673762009995018755?logTag=c3a4dc18b1cc6d83c094)

## KDJ超跌
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import MyTT  
import talib  
import numpy as np  
import pandas as pd  
from functools import reduce  
from datetime import datetime, timedelta  
  
"""  
金谷战法-超跌必杀  
选股条件  
1、KDJ指标默认参数：9,3,3  
2、J<0,K-J>20  
3、买点：J线金叉K线  
   卖点：10%开始止盈止损或者J线死叉K线  
"""  
  
def init(context):  
    # 参数初始化  
    context.kdj_n1 = 9                         # KDJ指标第一个参数  
    context.kdj_n2 = 3                         # KDJ指标第二个参数  
    context.kdj_n3 = 3                         # KDJ指标第三个参数  
    context.j_threshold = 0                    # 超跌条件1：J<0  
    context.kj_threshold = 40                  # 超跌条件2：K-J>20  
    context.backtrack_periods = 10             # 超跌回溯周期，超过该周期还未发现超跌则停止回溯  
    context.mv_threshold = 300e8               # 只选中小市值的股票，总市值小于等于300亿  
    context.holding_num = 20                   # 持股数量  
    context.target_profit = 0.1                # 止盈百分比  
    context.target_loss = -0.1                 # 止损百分比  
  
    # 变量初始化  
    context.high_price = pd.DataFrame()  
    context.low_price = pd.DataFrame()  
    context.close_price = pd.DataFrame()  
      
    # 定时任务  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:25:00')  
  
  
def before_market(context):  
    """选股+买入"""  
    # 日期  
    context.today = context.now.strftime('%Y-%m-%d')  
    prev_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=1)[0]  
    # 获取全A股票池(剔除停牌股、ST股、次新股（365天）)  
    all_stocks,all_stocks_str = get_normal_stocks(context, date=context.today, skip_limit=True)  
    # 整理数据  
    if len(context.high_price)==0 or len(context.low_price)==0 or len(context.close_price)==0:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=120+context.backtrack_periods+1)[0]  
        context.high_price = history_new(all_stocks,frequency='1d',start_time=start_date,end_time=prev_date,fields='eob,symbol,high',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        context.low_price = history_new(all_stocks,frequency='1d',start_time=start_date,end_time=prev_date,fields='eob,symbol,low',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        context.close_price = history_new(all_stocks,frequency='1d',start_time=start_date,end_time=prev_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
    else:  
        pre_high_price = history_new(all_stocks,frequency='1d',start_time=prev_date,end_time=prev_date,fields='eob,symbol,high',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        pre_low_price = history_new(all_stocks,frequency='1d',start_time=prev_date,end_time=prev_date,fields='eob,symbol,low',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        pre_close_price = history_new(all_stocks,frequency='1d',start_time=prev_date,end_time=prev_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        context.high_price = pd.concat([context.high_price,pre_high_price])  
        context.low_price = pd.concat([context.low_price,pre_low_price])  
        context.close_price = pd.concat([context.close_price,pre_close_price])  
        context.high_price = context.high_price.iloc[1:,:]  
        context.low_price = context.low_price.iloc[1:,:]  
        context.close_price = context.close_price.iloc[1:,:]  
          
    # 计算 J线金叉K线 的股票  
    target_stocks = []  
    for code in all_stocks:  
        if code not in context.high_price.columns:  
            continue  
            close_price = context.close_price[code]  
        high_price = context.high_price[code]  
        low_price = context.low_price[code]  
        K_array,D_array,J_array = MyTT.KDJ(close_price,high_price,low_price,N=context.kdj_n1,M1=context.kdj_n2,M2=context.kdj_n3)  
  
        # 条件1：J线金叉K线  
        J_array = J_array[-context.backtrack_periods:]  
        K_array = K_array[-context.backtrack_periods:]  
        jc = J_array-K_array  
        if jc[-1]<=0 or jc[-2]>0:  
            continue  
            # 条件2：J<0,K-J>20  
        dead_cross_index = -context.backtrack_periods  
        # 查找最近一次死叉的位置，条件2的产生要在最近一次死叉以来  
        for i in range(-2,-len(jc),-1):      
            if jc[i] < 0 and jc[i-1] >= 0:    
                dead_cross_index = i    
                break  
        J_array_curr = J_array[dead_cross_index:]  
        K_array_curr = K_array[dead_cross_index:]  
        kj_array_curr = K_array_curr-J_array_curr  
        if len(J_array_curr[J_array_curr<context.j_threshold])==0 or len(kj_array_curr[kj_array_curr>context.kj_threshold])==0:  
            continue  
        target_stocks.append(code)  
    # 条件3：市值过滤  
    if len(target_stocks)>0:  
        fundamental = stk_get_daily_mktvalue_pt(symbols=target_stocks, fields='tot_mv', trade_date=prev_date, df=True)  
        fundamental = fundamental[fundamental['tot_mv']<=context.mv_threshold].sort_values('tot_mv')  
        target_stocks = fundamental['symbol'].tolist()  
    print('{}，股票池{}只：{}\n'.format(context.now,len(target_stocks),target_stocks))  
              
    # 买在标的池中的股票  
    # 所有持仓  
    positions = get_position()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    new_buy = 0  
    for symbol in target_stocks:  
        if len(holding_stocks)+new_buy<context.holding_num:  
            price = history(symbol=symbol, frequency='1d', start_time=context.today,  end_time=context.today, adjust=ADJUST_NONE, df= True)  
            upper_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
            if len(upper_limit)>0 and len(price)>0 and upper_limit[0]['upper_limit']!=price['open'].iloc[0]:  
                new_price = price['open'].iloc[0]  
                nav = context.account().cash['nav']  
                trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/context.holding_num,price=new_price)  
                order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
                new_buy += 1  
        else:  
            break  
  
    # 记录日内bar价格  
    context.today_price = {}  
    for code in holding_stocks:  
        context.today_price[code] = {'high':context.close_price[code].iloc[-1], 'low':context.close_price[code].iloc[-1]}  
  
    # 订阅行情（止盈止损）  
    subscribe(symbols=holding_stocks, frequency='3600s', count=2,unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    symbol = bars[0]['symbol']  
    close = bars[0]['close']  
    # 更新价格  
    context.today_price[symbol]['high'] = max(context.today_price[symbol]['high'],bars[0]['high'])  
    context.today_price[symbol]['low'] = max(context.today_price[symbol]['low'],bars[0]['low'])  
    # 指定持仓  
    position = context.account().position(symbol=symbol,side = PositionSide_Long)  
  
    if position:  
        # 卖点1：固定比例止盈止损  
        profit_rate = bars[0]['close']/position['vwap']-1  
        if profit_rate>=context.target_profit or profit_rate<=context.target_loss:  
            sell_stocks(context,symbol,context.today,close)  
  
        # 卖点2：J线死叉K线  
        close_price = context.close_price[symbol]  
        high_price = context.high_price[symbol]  
        low_price = context.low_price[symbol]  
        close_price[bars[0]['eob']] = close  
        high_price[bars[0]['eob']] = context.today_price[symbol]['high']  
        low_price[bars[0]['eob']] = context.today_price[symbol]['low']  
        K_array,D_array,J_array = MyTT.KDJ(close_price,high_price,low_price,N=context.kdj_n1,M1=context.kdj_n2,M2=context.kdj_n3)  
        if J_array[-1]<K_array[-1]:  
            sell_stocks(context,symbol,context.today,close)  
  
  
def sell_stocks(context,symbol,date,price):  
    lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=date, skip_st=False)  
    if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=price:  
        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=price)  
      
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:  
        pass  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
         
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='1d838471-59fd-11ef-ae7a-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2024-02-20 08:00:00',  
        backtest_end_time='2024-05-25 08:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

## KDJ超跌+底背离
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import MyTT  
import talib  
import numpy as np  
import pandas as pd  
from functools import reduce  
from datetime import datetime, timedelta  
  
"""  
金谷战法-超跌必杀  
选股条件  
1、KDJ指标默认参数：9,3,3  
2、J<0,K-J>20  
3、买点：J线金叉K线  
   卖点：10%开始止盈止损或者J线死叉K线  
4、J线金叉K线时，要形成底背离  
"""  
  
def init(context):  
    # 参数初始化  
    context.kdj_n1 = 9                         # KDJ指标第一个参数  
    context.kdj_n2 = 3                         # KDJ指标第二个参数  
    context.kdj_n3 = 3                         # KDJ指标第三个参数  
    context.j_threshold = 0                    # 超跌条件1：J<0  
    context.kj_threshold = 40                  # 超跌条件2：K-J>20  
    context.backtrack_periods = 30             # 超跌回溯周期，超过该周期还未发现超跌则停止回溯(底背离需要较长的周期)  
    context.mv_threshold = 300e8               # 只选中小市值的股票，总市值小于等于300亿  
    context.holding_num = 20                   # 持股数量  
    context.target_profit = 0.1                # 止盈百分比  
    context.target_loss = -0.1                 # 止损百分比  
  
    # 变量初始化  
    context.high_price = pd.DataFrame()  
    context.low_price = pd.DataFrame()  
    context.close_price = pd.DataFrame()  
      
    # 定时任务  
    schedule(schedule_func=before_market, date_rule='1d', time_rule='09:25:00')  
  
  
def before_market(context):  
    """选股+买入"""  
    # 日期  
    context.today = context.now.strftime('%Y-%m-%d')  
    prev_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=1)[0]  
    # 获取全A股票池(剔除停牌股、ST股、次新股（365天）)  
    all_stocks,all_stocks_str = get_normal_stocks(context, date=context.today, skip_limit=True)  
    # 整理数据  
    if len(context.high_price)==0 or len(context.low_price)==0 or len(context.close_price)==0:  
        start_date = get_previous_n_trading_dates(exchange='SHSE', date=context.today, n=120+context.backtrack_periods+1)[0]  
        context.high_price = history_new(all_stocks,frequency='1d',start_time=start_date,end_time=prev_date,fields='eob,symbol,high',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        context.low_price = history_new(all_stocks,frequency='1d',start_time=start_date,end_time=prev_date,fields='eob,symbol,low',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        context.close_price = history_new(all_stocks,frequency='1d',start_time=start_date,end_time=prev_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
    else:  
        pre_high_price = history_new(all_stocks,frequency='1d',start_time=prev_date,end_time=prev_date,fields='eob,symbol,high',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        pre_low_price = history_new(all_stocks,frequency='1d',start_time=prev_date,end_time=prev_date,fields='eob,symbol,low',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        pre_close_price = history_new(all_stocks,frequency='1d',start_time=prev_date,end_time=prev_date,fields='eob,symbol,close',adjust=ADJUST_PREV,adjust_end_time=prev_date, df=True, type=True)  
        context.high_price = pd.concat([context.high_price,pre_high_price])  
        context.low_price = pd.concat([context.low_price,pre_low_price])  
        context.close_price = pd.concat([context.close_price,pre_close_price])  
        context.high_price = context.high_price.iloc[1:,:]  
        context.low_price = context.low_price.iloc[1:,:]  
        context.close_price = context.close_price.iloc[1:,:]  
          
    # 计算 J线金叉K线 的股票  
    target_stocks = []  
    for code in all_stocks:  
        if code not in context.high_price.columns:  
            continue  
            close_price = context.close_price[code]  
        high_price = context.high_price[code]  
        low_price = context.low_price[code]  
        K_array,D_array,J_array = MyTT.KDJ(close_price,high_price,low_price,N=context.kdj_n1,M1=context.kdj_n2,M2=context.kdj_n3)  
  
        # 条件1：J线金叉K线  
        J_array = J_array[-context.backtrack_periods:]  
        K_array = K_array[-context.backtrack_periods:]  
        D_array = D_array[-context.backtrack_periods:]  
        jc = J_array-K_array  
        if jc[-1]<=0 or jc[-2]>0:  
            continue  
            # 条件2：J<0,K-J>20  
        dead_cross_index = -context.backtrack_periods  
        # 查找最近一次死叉的位置，条件2的产生要在最近一次死叉以来  
        for i in range(-2,-len(jc),-1):      
            if jc[i] < 0 and jc[i-1] >= 0:    
                dead_cross_index = i    
                break  
        J_array_curr = J_array[dead_cross_index:]  
        K_array_curr = K_array[dead_cross_index:]  
        kj_array_curr = K_array_curr-J_array_curr  
        if len(J_array_curr[J_array_curr<context.j_threshold])==0 or len(kj_array_curr[kj_array_curr>context.kj_threshold])==0:  
            continue  
  
        # 条件3：底背离（跟上一次金叉的前一天数据对比，价格新低，K值和D值未新低）  
        gold_cross_index = 0  
        for i in range(-2,-len(jc)+1,-1):      
            if jc[i] > 0 and jc[i-1] <= 0:    
                gold_cross_index = i-1  
                break  
        if gold_cross_index!=0 and D_array[-2]>D_array[gold_cross_index] and K_array[-2]>K_array[gold_cross_index] and close_price[-2]<close_price[gold_cross_index]:  
            target_stocks.append(code)  
        else:  
            continue  
  
    # 条件4：市值过滤  
    if len(target_stocks)>0:  
        fundamental = stk_get_daily_mktvalue_pt(symbols=target_stocks, fields='tot_mv', trade_date=prev_date, df=True)  
        fundamental = fundamental[fundamental['tot_mv']<=context.mv_threshold].sort_values('tot_mv')  
        target_stocks = fundamental['symbol'].tolist()  
    print('{}，股票池{}只：{}\n'.format(context.now,len(target_stocks),target_stocks))  
              
    # 买在标的池中的股票  
    # 所有持仓  
    positions = get_position()  
    holding_stocks = [posi['symbol'] for posi in positions]  
    new_buy = 0  
    for symbol in target_stocks:  
        if len(holding_stocks)+new_buy<context.holding_num:  
            price = history(symbol=symbol, frequency='1d', start_time=context.today,  end_time=context.today, adjust=ADJUST_NONE, df= True)  
            upper_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=context.now.strftime('%Y-%m-%d'), skip_st=False)  
            if len(upper_limit)>0 and len(price)>0 and upper_limit[0]['upper_limit']!=price['open'].iloc[0]:  
                new_price = price['open'].iloc[0]  
                nav = context.account().cash['nav']  
                trade_volume = cal_stock_buy_volume(context,code=symbol,amount=0.98*nav/context.holding_num,price=new_price)  
                order_volume(symbol=symbol, volume=trade_volume, side=OrderSide_Buy, order_type=OrderType_Limit, position_effect=PositionEffect_Open, price=new_price)  
                new_buy += 1  
        else:  
            break  
  
    # 记录日内bar价格  
    context.today_price = {}  
    for code in holding_stocks:  
        context.today_price[code] = {'high':context.close_price[code].iloc[-1], 'low':context.close_price[code].iloc[-1]}  
  
    # 订阅行情（止盈止损）  
    subscribe(symbols=holding_stocks, frequency='3600s', count=2,unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    symbol = bars[0]['symbol']  
    close = bars[0]['close']  
    # 更新价格  
    context.today_price[symbol]['high'] = max(context.today_price[symbol]['high'],bars[0]['high'])  
    context.today_price[symbol]['low'] = max(context.today_price[symbol]['low'],bars[0]['low'])  
    # 指定持仓  
    position = context.account().position(symbol=symbol,side = PositionSide_Long)  
  
    if position:  
        # 卖点1：固定比例止盈止损  
        profit_rate = bars[0]['close']/position['vwap']-1  
        if profit_rate>=context.target_profit or profit_rate<=context.target_loss:  
            sell_stocks(context,symbol,context.today,close)  
  
        # 卖点2：J线死叉K线  
        close_price = context.close_price[symbol]  
        high_price = context.high_price[symbol]  
        low_price = context.low_price[symbol]  
        close_price[bars[0]['eob']] = close  
        high_price[bars[0]['eob']] = context.today_price[symbol]['high']  
        low_price[bars[0]['eob']] = context.today_price[symbol]['low']  
        K_array,D_array,J_array = MyTT.KDJ(close_price,high_price,low_price,N=context.kdj_n1,M1=context.kdj_n2,M2=context.kdj_n3)  
        if J_array[-1]<K_array[-1]:  
            sell_stocks(context,symbol,context.today,close)  
  
  
def sell_stocks(context,symbol,date,price):  
    lower_limit = get_symbols(sec_type1=1010, symbols=symbol, trade_date=date, skip_st=False)  
    if len(lower_limit)>0 and lower_limit[0]['lower_limit']!=price:  
        order_target_percent(symbol=symbol, percent=0, order_type=OrderType_Limit, position_side=PositionSide_Long, price=price)  
      
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_limit=False, return_info=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除开盘涨停股  
        if skip_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='open,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[(stocks_info['open']!=stocks_info['upper_limit'])&(stocks_info['open']!=stocks_info['lower_limit'])]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    if return_info:  
        return all_stocks,all_stocks_str,stocks_info  
    else:  
        return all_stocks,all_stocks_str  
  
  
def cal_stock_buy_volume(context,code,amount,price):  
    """计算股票下单数量"""  
    Acc_cash = context.account().cash# 获取账户资金信息  
    available_amount = min(amount,Acc_cash['available'])       
    # if code.startswith('SHSE.68'):  
    #     trade_volume = int(np.floor(available_amount/price))    #     trade_volume = 0 if trade_volume<200 else trade_volume    # else:    #     trade_volume = int(np.floor(available_amount/price/100)*100)    trade_volume = max(0,int(np.floor(available_amount/price/100)*100))  
    return trade_volume  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    # 交易方向  
    if effect == 1:  
        if side == 1:  
            side_effect = '开多仓'  
        else:  
            side_effect = '开空仓'  
    else:  
        if side == 1:  
            side_effect = '平空仓'  
        else:  
            side_effect = '平多仓'  
    # 委托类型  
    order_type_word = '限价' if order_type==1 else '市价'  
    if status == 3:  
        pass  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:标的：{}，操作：以{}{}，委托数量:{}， 被拒原因：{}'.format(context.now,symbol,order_type_word,side_effect,volume,order['ord_rej_reason_detail']))  
         
         
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
      
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''    run(strategy_id='1d838471-59fd-11ef-ae7a-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-01-01 08:00:00',  
        backtest_end_time='2024-07-31 15:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.0007,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=1)

# 策略优化方法
**1、****策略说明**

本策略为2024年8月末研学第三次直播的案例策略，结合视频观看效果更佳。

1、检查买卖点是否符合预期

2、分析持仓特征

3、数据预处理

4、参数优化

5、风险管理

6、策略组合

7、模型/算法优化

## 过滤一字板
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
剔除首板开盘就涨停的股票  
"""  
    
def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:28:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str = get_normal_stocks(context, date,new_days=50)  
  
    # 2、过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]  
  
    # 2.1、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 3、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=3)  
    yesterday = pre_date_list[-1]  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)  
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)                          
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = pre_data[pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['open']!=pre_data_new['high'])&(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = pre_data[pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
  
    # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    context.buy_stocks = all_stocks  
    print('{} 买入监控股票：{}'.format(context.now, context.buy_stocks))  
      
    # 9、订阅数据  
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    to_buy = context.buy_stocks  
    for symbol in to_buy:  
        percent = 1/len(to_buy)  
        cash = context.account().cash  
        # 用市价单进行撮合，即下一个tick的开盘价  
        price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
        volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
        if volume>0:  
            order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)  
            unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
        if context.mode==MODE_LIVE:  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
            if call_rate>=context.max_call_rate or call_rate<=context.min_call_rate:  
                print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
                unsubscribe(symbols=symbol, frequency='tick')  
                context.buy_stocks.remove(symbol)  
                return  
  
  
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='ef13f522-10f9-11ef-9466-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-09-01 08:00:00',  
        backtest_end_time='2024-08-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)

## 记录数据特征
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
1、剔除首板开盘就涨停的股票  
2、持仓特征（分仓、信息记录）  
  
"""  
  
def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
    context.trade_info = pd.DataFrame()  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:28:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    context.infos = {'左压比例':{},'集合竞价成交额':{},'集合竞价量能':{}}  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str = get_normal_stocks(context, date,new_days=50)  
  
    # 2、过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]  
  
    # 2.1、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 3、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=3)  
    yesterday = pre_date_list[-1]  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,context.pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)  
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)                          
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['open']!=pre_data_new['high'])&(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
        context.infos['价格'] = pre_data_new.groupby('symbol')['close'].apply(list).to_dict()  
        context.infos['成交额'] = pre_data_new.groupby('symbol')['amount'].apply(list).to_dict()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
        context.infos['市值'] = mv.groupby('symbol')['tot_mv'].apply(list).to_dict()  
        context.infos['流通市值'] = mv.groupby('symbol')['a_mv_ex_ltd'].apply(list).to_dict()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
        context.infos['获利比例'] = pre_data_new.groupby('symbol')['get_profit_rate'].apply(list).to_dict()     
  
    # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
        context.infos['开盘涨跌幅'] = today_data.groupby('symbol')['open_rate'].apply(list).to_dict()           
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            context.infos['左压比例'][symbol] = volume_data.iloc[-1] / max(volume_data[:-1])  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    context.buy_stocks = all_stocks  
    print('{} 买入监控股票：{}'.format(context.now, context.buy_stocks))  
      
    # 9、订阅数据  
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    to_buy = context.buy_stocks  
    for symbol in to_buy:  
        percent = 1/len(to_buy)  
        cash = context.account().cash  
        # 用市价单进行撮合，即下一个tick的开盘价  
        price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
        volume = 100  
        # volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
        if volume>0:  
            order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)  
            unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        context.infos['集合竞价成交额'][symbol] = tick['cum_volume']*1e-8  
        context.infos['集合竞价量能'][symbol] = open_colume_rate  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
            if context.mode==MODE_LIVE:  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
            context.infos['开盘涨跌幅'][symbol] = [call_rate]  
            if call_rate>=context.max_call_rate or call_rate<=context.min_call_rate:  
                print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
                unsubscribe(symbols=symbol, frequency='tick')  
                context.buy_stocks.remove(symbol)  
                return  
  
  
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
        if side==1:  
            pre_data = context.pre_data[context.pre_data['symbol']==symbol]  
            if pre_data['low'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = '一字板'  
            elif pre_data['open'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = 'T字板'  
            else:  
                k_type = '非一字板'  
            context.trade_info = pd.concat([context.trade_info,pd.DataFrame([symbol,order['filled_amount'],0,context.now.strftime('%Y-%m-%d'),None,  
                                            context.infos['价格'][symbol][0],context.infos['成交额'][symbol][0]*1e-8,k_type,context.infos['流通市值'][symbol][0]*1e-8,  
                                            context.infos['市值'][symbol][0]*1e-8,context.infos['获利比例'][symbol][0],context.infos['开盘涨跌幅'][symbol][0],  
                                            context.infos['左压比例'][symbol],context.infos['集合竞价成交额'][symbol]*1e-8,context.infos['集合竞价量能'][symbol],  
                                            ],  
                                            index=['代码','买入金额','卖出金额','买入日期','卖出日期',  
                                            '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能'  
                                            ]).T])  
            context.trade_info.index = range(len(context.trade_info))  
        else:  
            index = context.trade_info[(context.trade_info['代码']==symbol) & (context.trade_info['卖出金额']==0)].index  
            context.trade_info.loc[index,'卖出金额'] = order['filled_amount']  
            context.trade_info.loc[index,'卖出日期'] = context.now.strftime('%Y-%m-%d')  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
  
         
def on_backtest_finished(context, indicator):  
    print('********************************************\n')  
    context.trade_info['平仓收益'] = (context.trade_info['卖出金额']-context.trade_info['买入金额'])/context.trade_info['买入金额']  
    context.trade_info = context.trade_info[context.trade_info['卖出金额']!=0]  
    context.trade_info.sort_values('平仓收益',inplace=True)  
    columns_save = ['买入日期','代码','买入金额','卖出金额','平仓收益',  
                    '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能']  
    context.trade_info[columns_save].to_csv(r'交易信息.csv')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='ef13f522-10f9-11ef-9466-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-09-01 08:00:00',  
        backtest_end_time='2024-08-29 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)

## 对数收益
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
1、剔除首板开盘就涨停的股票  
2、持仓特征（分仓、信息记录）  
  
"""  
  
def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
    context.trade_info = pd.DataFrame()  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:28:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    context.infos = {'左压比例':{},'集合竞价成交额':{},'集合竞价量能':{}}  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str = get_normal_stocks(context, date,new_days=50)  
  
    # 2、过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]  
  
    # 2.1、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 3、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=3)  
    yesterday = pre_date_list[-1]  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,context.pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)  
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)                          
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['open']!=pre_data_new['high'])&(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
        context.infos['价格'] = pre_data_new.groupby('symbol')['close'].apply(list).to_dict()  
        context.infos['成交额'] = pre_data_new.groupby('symbol')['amount'].apply(list).to_dict()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
        context.infos['市值'] = mv.groupby('symbol')['tot_mv'].apply(list).to_dict()  
        context.infos['流通市值'] = mv.groupby('symbol')['a_mv_ex_ltd'].apply(list).to_dict()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
        context.infos['获利比例'] = pre_data_new.groupby('symbol')['get_profit_rate'].apply(list).to_dict()     
  
    # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
        context.infos['开盘涨跌幅'] = today_data.groupby('symbol')['open_rate'].apply(list).to_dict()           
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            context.infos['左压比例'][symbol] = volume_data.iloc[-1] / max(volume_data[:-1])  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    context.buy_stocks = all_stocks  
    print('{} 买入监控股票：{}'.format(context.now, context.buy_stocks))  
      
    # 9、订阅数据  
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    to_buy = context.buy_stocks  
    for symbol in to_buy:  
        percent = 1/len(to_buy)  
        cash = context.account().cash  
        # 用市价单进行撮合，即下一个tick的开盘价  
        price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
        volume = 100  
        # volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
        if volume>0:  
            order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)  
            unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        context.infos['集合竞价成交额'][symbol] = tick['cum_volume']*1e-8  
        context.infos['集合竞价量能'][symbol] = open_colume_rate  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
            if context.mode==MODE_LIVE:  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
            context.infos['开盘涨跌幅'][symbol] = [call_rate]  
            if call_rate>=context.max_call_rate or call_rate<=context.min_call_rate:  
                print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
                unsubscribe(symbols=symbol, frequency='tick')  
                context.buy_stocks.remove(symbol)  
                return  
  
  
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
        if side==1:  
            pre_data = context.pre_data[context.pre_data['symbol']==symbol]  
            if pre_data['low'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = '一字板'  
            elif pre_data['open'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = 'T字板'  
            else:  
                k_type = '非一字板'  
            context.trade_info = pd.concat([context.trade_info,pd.DataFrame([symbol,order['filled_amount'],0,context.now.strftime('%Y-%m-%d'),None,  
                                            context.infos['价格'][symbol][0],context.infos['成交额'][symbol][0]*1e-8,k_type,context.infos['流通市值'][symbol][0]*1e-8,  
                                            context.infos['市值'][symbol][0]*1e-8,context.infos['获利比例'][symbol][0],context.infos['开盘涨跌幅'][symbol][0],  
                                            context.infos['左压比例'][symbol],context.infos['集合竞价成交额'][symbol]*1e-8,context.infos['集合竞价量能'][symbol],  
                                            ],  
                                            index=['代码','买入金额','卖出金额','买入日期','卖出日期',  
                                            '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能'  
                                            ]).T])  
            context.trade_info.index = range(len(context.trade_info))  
        else:  
            index = context.trade_info[(context.trade_info['代码']==symbol) & (context.trade_info['卖出金额']==0)].index  
            context.trade_info.loc[index,'卖出金额'] = order['filled_amount']  
            context.trade_info.loc[index,'卖出日期'] = context.now.strftime('%Y-%m-%d')  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
  
         
def on_backtest_finished(context, indicator):  
    print('********************************************\n')  
    context.trade_info['平仓收益'] = (context.trade_info['卖出金额']-context.trade_info['买入金额'])/context.trade_info['买入金额']  
    context.trade_info = context.trade_info[(context.trade_info['卖出金额']!=0)]  
    context.trade_info['卖出金额'] = pd.to_numeric(context.trade_info['卖出金额'], errors='coerce')  
    context.trade_info['买入金额'] = pd.to_numeric(context.trade_info['买入金额'], errors='coerce')  
    context.trade_info['对数收益'] = np.log(context.trade_info['卖出金额'])-np.log(context.trade_info['买入金额'])  
    context.trade_info.sort_values('平仓收益',inplace=True)  
    columns_save = ['买入日期','代码','买入金额','卖出金额','平仓收益','对数收益',  
                    '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能']  
    context.trade_info[columns_save].to_csv(r'交易信息.csv')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='ef13f522-10f9-11ef-9466-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-09-01 08:00:00',  
        backtest_end_time='2024-08-29 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)

## 择时
### 龙头开盘涨幅分析
#### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
1、剔除首板开盘就涨停的股票  
2、持仓特征（分仓、信息记录）  
3、择时  
"""  
  
def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
    context.trade_info = pd.DataFrame()  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:28:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    context.infos = {'左压比例':{},'集合竞价成交额':{},'集合竞价量能':{},'最大连板股票_开盘涨跌幅':{}}  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str,stocks_info = get_normal_stocks(context, date,new_days=50)  
  
    # 2、过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]  
  
    # 2.1、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 3、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=15)  
    yesterday = pre_date_list[-1]  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,context.pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 最大连板股及其开盘涨跌幅  
    cal_zt_stocks = all_stocks.copy()  
    cal_zt_stocks = list(set(cal_zt_stocks) - set(stocks_info[(stocks_info['is_st']==True)]['symbol']))  
    lb_stocks = cal_zt_stocks.copy()  
    for i in range(-2,-15,-1):  
        lb_stocks = set(lb_stocks)&set(context.n_days_limit_up_list[i])  
        if len(lb_stocks)==0:  
            context.infos['最大连板高度'] = abs(i)-1  
            break  
        else:  
            context.infos['最大连板股票'] = list(lb_stocks)    
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)   
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)                
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['open']!=pre_data_new['high'])&(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
        context.infos['价格'] = pre_data_new.groupby('symbol')['close'].apply(list).to_dict()  
        context.infos['成交额'] = pre_data_new.groupby('symbol')['amount'].apply(list).to_dict()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
        context.infos['市值'] = mv.groupby('symbol')['tot_mv'].apply(list).to_dict()  
        context.infos['流通市值'] = mv.groupby('symbol')['a_mv_ex_ltd'].apply(list).to_dict()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
        context.infos['获利比例'] = pre_data_new.groupby('symbol')['get_profit_rate'].apply(list).to_dict()     
  
    # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
        context.infos['开盘涨跌幅'] = today_data.groupby('symbol')['open_rate'].apply(list).to_dict()           
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            context.infos['左压比例'][symbol] = volume_data.iloc[-1] / max(volume_data[:-1])  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    context.buy_stocks = all_stocks  
    print('{} 买入监控股票：{}'.format(context.now, context.buy_stocks))  
      
    # 9、订阅数据  
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks)|set(context.infos['最大连板股票']))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    to_buy = context.buy_stocks  
    for symbol in to_buy:  
        percent = 1/len(to_buy)  
        cash = context.account().cash  
        # 用市价单进行撮合，即下一个tick的开盘价  
        price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
        volume = 100  
        # volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
        if volume>0:  
            order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)  
            unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        context.infos['集合竞价成交额'][symbol] = tick['cum_volume']*1e-8  
        context.infos['集合竞价量能'][symbol] = open_colume_rate  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
            if context.mode==MODE_LIVE:  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
            context.infos['开盘涨跌幅'][symbol] = [call_rate]  
            if symbol not in context.infos['最大连板股票'] and (call_rate>=context.max_call_rate or call_rate<=context.min_call_rate):  
                print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
                unsubscribe(symbols=symbol, frequency='tick')  
                context.buy_stocks.remove(symbol)  
                return  
                # 择时  
    if tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time() and symbol in context.infos['最大连板股票'] and symbol not in context.infos['最大连板股票_开盘涨跌幅'].keys():  
        call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
        context.infos['最大连板股票_开盘涨跌幅'][symbol] = call_rate  
        context.infos['最大连板股票_最大开盘涨跌幅'] = max(context.infos['最大连板股票_开盘涨跌幅'].values())  
          
  
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str,stocks_info  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
        if side==1:  
            pre_data = context.pre_data[context.pre_data['symbol']==symbol]  
            if pre_data['low'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = '一字板'  
            elif pre_data['open'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = 'T字板'  
            else:  
                k_type = '非一字板'  
            context.trade_info = pd.concat([context.trade_info,pd.DataFrame([symbol,order['filled_amount'],0,context.now.strftime('%Y-%m-%d'),None,  
                                            context.infos['价格'][symbol][0],context.infos['成交额'][symbol][0]*1e-8,k_type,context.infos['流通市值'][symbol][0]*1e-8,  
                                            context.infos['市值'][symbol][0]*1e-8,context.infos['获利比例'][symbol][0],context.infos['开盘涨跌幅'][symbol][0],  
                                            context.infos['左压比例'][symbol],context.infos['集合竞价成交额'][symbol]*1e-8,context.infos['集合竞价量能'][symbol],  
                                            context.infos['最大连板高度'],context.infos['最大连板股票'],context.infos['最大连板股票_最大开盘涨跌幅'],  
                                            ],  
                                            index=['代码','买入金额','卖出金额','买入日期','卖出日期',  
                                            '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能',  
                                            '最大连板高度','最大连板股票','最大连板股票_最大开盘涨跌幅'  
                                            ]).T])  
            context.trade_info.index = range(len(context.trade_info))  
        else:  
            index = context.trade_info[(context.trade_info['代码']==symbol) & (context.trade_info['卖出金额']==0)].index  
            context.trade_info.loc[index,'卖出金额'] = order['filled_amount']  
            context.trade_info.loc[index,'卖出日期'] = context.now.strftime('%Y-%m-%d')  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
  
         
def on_backtest_finished(context, indicator):  
    print('********************************************\n')  
    context.trade_info['平仓收益'] = (context.trade_info['卖出金额']-context.trade_info['买入金额'])/context.trade_info['买入金额']  
    context.trade_info = context.trade_info[(context.trade_info['卖出金额']!=0)]  
    context.trade_info['卖出金额'] = pd.to_numeric(context.trade_info['卖出金额'], errors='coerce')  
    context.trade_info['买入金额'] = pd.to_numeric(context.trade_info['买入金额'], errors='coerce')  
    context.trade_info['对数收益'] = np.log(context.trade_info['卖出金额'])-np.log(context.trade_info['买入金额'])  
    context.trade_info.sort_values('平仓收益',inplace=True)  
    columns_save = ['买入日期','代码','买入金额','卖出金额','平仓收益','对数收益',  
                    '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能',  
                    '最大连板高度','最大连板股票','最大连板股票_最大开盘涨跌幅']  
    context.trade_info[columns_save].to_csv(r'交易信息.csv')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='3ee7a74c-66dc-11ef-8b83-bc6ee2b72c72',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-09-01 08:00:00',  
        backtest_end_time='2024-08-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)

### 择时
#### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
1、剔除首板开盘就涨停的股票  
2、持仓特征（分仓、信息记录）  
3、择时  
"""  
  
def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
    context.hhl_open_rate = -0.08            # 最大连板股票_最大开盘涨跌幅的最小值  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
    context.trade_info = pd.DataFrame()  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:28:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    context.infos = {'左压比例':{},'集合竞价成交额':{},'集合竞价量能':{},'最大连板股票_开盘涨跌幅':{}}  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str,stocks_info = get_normal_stocks(context, date,new_days=50)  
  
    # 2、过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]  
  
    # 2.1、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 3、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=15)  
    yesterday = pre_date_list[-1]  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,context.pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 最大连板股及其开盘涨跌幅  
    cal_zt_stocks = all_stocks.copy()  
    cal_zt_stocks = list(set(cal_zt_stocks) - set(stocks_info[(stocks_info['is_st']==True)]['symbol']))  
    lb_stocks = cal_zt_stocks.copy()  
    for i in range(-2,-15,-1):  
        lb_stocks = set(lb_stocks)&set(context.n_days_limit_up_list[i])  
        if len(lb_stocks)==0:  
            context.infos['最大连板高度'] = abs(i)-1  
            break  
        else:  
            context.infos['最大连板股票'] = list(lb_stocks)    
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)   
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)                
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['open']!=pre_data_new['high'])&(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
        context.infos['价格'] = pre_data_new.groupby('symbol')['close'].apply(list).to_dict()  
        context.infos['成交额'] = pre_data_new.groupby('symbol')['amount'].apply(list).to_dict()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
        context.infos['市值'] = mv.groupby('symbol')['tot_mv'].apply(list).to_dict()  
        context.infos['流通市值'] = mv.groupby('symbol')['a_mv_ex_ltd'].apply(list).to_dict()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = context.pre_data[context.pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
        context.infos['获利比例'] = pre_data_new.groupby('symbol')['get_profit_rate'].apply(list).to_dict()     
  
    # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
        context.infos['开盘涨跌幅'] = today_data.groupby('symbol')['open_rate'].apply(list).to_dict()           
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            context.infos['左压比例'][symbol] = volume_data.iloc[-1] / max(volume_data[:-1])  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    context.buy_stocks = all_stocks  
    print('{} 买入监控股票：{}'.format(context.now, context.buy_stocks))  
      
    # 9、订阅数据  
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks)|set(context.infos['最大连板股票']))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    to_buy = context.buy_stocks  
    for symbol in to_buy:  
        percent = 1/len(to_buy)  
        cash = context.account().cash  
        # 用市价单进行撮合，即下一个tick的开盘价  
        price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
        # volume = 100  
        volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
        if volume>0:  
            order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)  
            unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        context.infos['集合竞价成交额'][symbol] = tick['cum_volume']*1e-8  
        context.infos['集合竞价量能'][symbol] = open_colume_rate  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
            if context.mode==MODE_LIVE:  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
            context.infos['开盘涨跌幅'][symbol] = [call_rate]  
            if symbol not in context.infos['最大连板股票'] and (call_rate>=context.max_call_rate or call_rate<=context.min_call_rate):  
                print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
                unsubscribe(symbols=symbol, frequency='tick')  
                context.buy_stocks.remove(symbol)  
                return  
                # 择时  
    if tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time() and symbol in context.infos['最大连板股票'] and symbol not in context.infos['最大连板股票_开盘涨跌幅'].keys():  
        call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
        context.infos['最大连板股票_开盘涨跌幅'][symbol] = call_rate  
        context.infos['最大连板股票_最大开盘涨跌幅'] = max(context.infos['最大连板股票_开盘涨跌幅'].values())  
          
        if len(context.infos['最大连板股票'])==len(context.infos['最大连板股票_开盘涨跌幅']):  
            if context.infos['最大连板股票_最大开盘涨跌幅']<context.hhl_open_rate:########################################################################  
                print('{}:最大连板股票{}的涨幅为{}，超过预期范围（>={}），空仓。'.format(context.now,context.infos['最大连板股票'],context.infos['最大连板股票_最大开盘涨跌幅'],context.hhl_open_rate))  
                context.buy_stocks = []  
  
  
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str,stocks_info  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        # print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
        if side==1:  
            pre_data = context.pre_data[context.pre_data['symbol']==symbol]  
            if pre_data['low'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = '一字板'  
            elif pre_data['open'].iloc[-1]==pre_data['upper_limit'].iloc[-1]:  
                k_type = 'T字板'  
            else:  
                k_type = '非一字板'  
            context.trade_info = pd.concat([context.trade_info,pd.DataFrame([symbol,order['filled_amount'],0,context.now.strftime('%Y-%m-%d'),None,  
                                            context.infos['价格'][symbol][0],context.infos['成交额'][symbol][0]*1e-8,k_type,context.infos['流通市值'][symbol][0]*1e-8,  
                                            context.infos['市值'][symbol][0]*1e-8,context.infos['获利比例'][symbol][0],context.infos['开盘涨跌幅'][symbol][0],  
                                            context.infos['左压比例'][symbol],context.infos['集合竞价成交额'][symbol]*1e-8,context.infos['集合竞价量能'][symbol],  
                                            context.infos['最大连板高度'],context.infos['最大连板股票'],context.infos['最大连板股票_最大开盘涨跌幅'],  
                                            ],  
                                            index=['代码','买入金额','卖出金额','买入日期','卖出日期',  
                                            '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能',  
                                            '最大连板高度','最大连板股票','最大连板股票_最大开盘涨跌幅'  
                                            ]).T])  
            context.trade_info.index = range(len(context.trade_info))  
        else:  
            index = context.trade_info[(context.trade_info['代码']==symbol) & (context.trade_info['卖出金额']==0)].index  
            context.trade_info.loc[index,'卖出金额'] = order['filled_amount']  
            context.trade_info.loc[index,'卖出日期'] = context.now.strftime('%Y-%m-%d')  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
  
         
def on_backtest_finished(context, indicator):  
    print('********************************************\n')  
    context.trade_info['平仓收益'] = (context.trade_info['卖出金额']-context.trade_info['买入金额'])/context.trade_info['买入金额']  
    context.trade_info = context.trade_info[(context.trade_info['卖出金额']!=0)]  
    context.trade_info['卖出金额'] = pd.to_numeric(context.trade_info['卖出金额'], errors='coerce')  
    context.trade_info['买入金额'] = pd.to_numeric(context.trade_info['买入金额'], errors='coerce')  
    context.trade_info['对数收益'] = np.log(context.trade_info['卖出金额'])-np.log(context.trade_info['买入金额'])  
    context.trade_info.sort_values('平仓收益',inplace=True)  
    columns_save = ['买入日期','代码','买入金额','卖出金额','平仓收益','对数收益',  
                    '价格','成交额/亿','K线类型','流通市值/亿','市值/亿','获利比例','开盘涨跌幅','左压比例','集合竞价成交额/亿','集合竞价量能',  
                    '最大连板高度','最大连板股票','最大连板股票_最大开盘涨跌幅']  
    context.trade_info[columns_save].to_csv(r'交易信息.csv')  
  
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='3ee7a74c-66dc-11ef-8b83-bc6ee2b72c72',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-09-01 08:00:00',  
        backtest_end_time='2024-08-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)

## 策略组合
### coding=utf-8  
from __future__ import print_function, absolute_import  
from gm.api import *  
  
import numpy as np  
import pandas as pd  
from datetime import datetime,timedelta  
  
"""  
剔除首板开盘就涨停的股票  
增加双低股票（致敬原作者：wywy1995、天山灵兔）  
"""  
  
def init(context):  
    # 1、参数设置  
    context.max_mv = 520e8                   # 最大市值  
    context.min_mv = 70e8                    # 最小市值  
    context.min_amount = 7e8                 # 最小成交额  
    context.max_amount = 19e8                # 最大成交额  
    context.min_call_rate = 0.00             # 最小开盘涨跌幅  
    context.max_call_rate = 0.0602           # 最大开盘涨跌幅  
    context.max_get_profit_rate = 0.04       # 最大获利比例  
    context.zy_min_rate = 0.9                # 左压最小比例  
    context.dk_max_rp_periods = 60           # 双低：低开形态的周期  
    context.dk_max_rp = 0.5                  # 双低：日线低位的比例  
    context.dk_min_call_rate = -0.04         # 双低：低开的最小涨跌幅  
    context.dk_max_call_rate = -0.03         # 双低：低开的最大涨跌幅  
  
    # 2、变量初始化  
    context.n_days_limit_up_list = []        # 涨停股列表  
  
    # 3、设置定时任务  
    context.sell_time1 = '11:28:00'  
    context.sell_time2 = '14:50:00'  
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:22:00')  
    schedule(schedule_func=buy_algo, date_rule='1d', time_rule='09:30:00')  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time1)  
    schedule(schedule_func=sell_algo, date_rule='1d', time_rule=context.sell_time2)  
      
  
def algo_1(context):  
    print('*'*88)  
    unsubscribe(symbols='*', frequency='tick')            # 取消所有订阅  
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str  
    pre_date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=3)  
    yesterday = pre_date_list[-1]  
  
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）  
    all_stocks,all_stocks_str = get_normal_stocks(context, date,new_days=50)  
  
    # 2、计算昨日涨停股、并剔除前2日涨停股  
    # 首次运行，先添加前2天的数据  
    if not context.n_days_limit_up_list:  
        for the_date in pre_date_list[::-1][1:]:  
            context.n_days_limit_up_list.append(get_zt_stock(all_stocks, the_date))  
    # 计算昨日涨停股票  
    all_stocks,pre_data = get_zt_stock(all_stocks, yesterday, return_data=True)  
    context.n_days_limit_up_list.append(all_stocks)   
    # 过滤前2日涨停股票  
    zt2_list = set(context.n_days_limit_up_list[-2] + context.n_days_limit_up_list[-3])  
    all_stocks = list(set(all_stocks)-zt2_list)  
    # 移除无用的数据  
    context.n_days_limit_up_list.pop(0)    
  
    # 3、重分组：低开 高开  
    # 低开股票：创业板，非低开股没有开仓时，就买低开股  
    context.dk_stocks = []  
    all_stocks_dk = [code for code in all_stocks if code.startswith('SZSE.3')]  
    context.dk_stocks = []  
    if all_stocks_dk:  
        dk_start_date = get_previous_n_trading_dates(exchange='SHSE', date=date, n=context.dk_max_rp_periods)[0]  
        dk_all_data = history_new(security=all_stocks_dk,frequency='1d',start_time=dk_start_date,end_time=yesterday,fields='symbol,eob,close,high,low',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=date, df=True, type=False)  
        for code in all_stocks_dk:  
            close = dk_all_data[dk_all_data['symbol']==code]['close'].iloc[-1]  
            high = dk_all_data[dk_all_data['symbol']==code]['high'].max()  
            low = dk_all_data[dk_all_data['symbol']==code]['low'].min()  
            rp = (close-low) / (high-low)  
            if rp<= context.dk_max_rp:  
                context.dk_stocks.append(code)   
    # 高开：过滤北交所、科创板、创业板  
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]     
    # context.dk_stocks = []                    
    # 3、过滤转债股  
    if all_stocks:  
        zz = get_symbols(sec_type1=1030, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        underlying_symbols = [data['underlying_symbol'] for data in zz]  
        all_stocks = list(set(all_stocks)-set(underlying_symbols))  
  
    # 4、过滤成交额大于19亿、小于7亿  
    if all_stocks:  
        pre_data_new = pre_data[pre_data['symbol'].isin(all_stocks)].copy()  
        all_stocks = pre_data_new[(pre_data_new['open']!=pre_data_new['high'])&(pre_data_new['amount']>context.min_amount)&(pre_data_new['amount']<context.max_amount)&(pre_data_new['symbol'].isin(all_stocks))]['symbol'].tolist()  
  
    # 5、过滤市值小于70亿，流通市值大于520亿  
    if all_stocks:          
        # 流通市值  
        mv = stk_get_daily_mktvalue_pt(symbols=all_stocks, fields='tot_mv,a_mv_ex_ltd', trade_date=yesterday, df=True)  
        all_stocks = mv[(mv['tot_mv']>context.min_mv)&(mv['a_mv_ex_ltd']<context.max_mv)]['symbol'].tolist()  
  
    # 6、过滤收盘获利比例大于4%  
    if all_stocks:  
        pre_data_new = pre_data[pre_data['symbol'].isin(all_stocks)].copy()  
        pre_data_new['get_profit_rate'] = pre_data_new['close']/(pre_data_new['amount']/pre_data_new['volume'])-1  
        all_stocks = pre_data_new[pre_data_new['get_profit_rate']<context.max_get_profit_rate]['symbol'].tolist()  
  
    # # 7、过滤开盘涨跌幅（回测中才执行这个步骤，实盘用订阅tick数据的方式过滤)  
    if all_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        all_stocks = today_data[(today_data['open_rate']<context.max_call_rate)&(today_data['open_rate']>context.min_call_rate)]['symbol'].tolist()  
    if context.dk_stocks and context.mode==MODE_BACKTEST:  
        today_data = history(symbol=context.dk_stocks, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        today_data['open_rate'] = today_data['open']/today_data['pre_close']-1  
        context.dk_stocks = today_data[(today_data['open_rate']<context.dk_max_call_rate)&(today_data['open_rate']>context.dk_min_call_rate)]['symbol'].tolist()  
  
    # 8、过滤左侧压力位缩量的  
    if all_stocks:  
        new_stocks = []  
        date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=101)  
        all_data = history_new(security=all_stocks,frequency='1d',start_time=date_list[0],end_time=date_list[-1],fields='symbol,eob,high,volume',skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=False)  
        for symbol in all_stocks:  
            the_all_data = all_data[all_data['symbol'].isin([symbol])]  
            prev_high = the_all_data['high'].iloc[-1]  # 计算前一天的高点  
            zyts_0 = next((i-1 for i, high in enumerate(the_all_data['high'][-3::-1], 2) if high >= prev_high), 100)  # 计算zyts_0  
            zyts = zyts_0+5  
            volume_data = the_all_data['volume'][-zyts:]   # 获取高点以来的成交量数据  
            # 检查今天的成交量是否同步放大  
            if len(volume_data) < 2 or volume_data.iloc[-1] < max(volume_data[:-1])*context.zy_min_rate:  
                continue  
            new_stocks.append(symbol)  
        all_stocks = list(set(all_stocks)&set(new_stocks))  
  
    context.buy_stocks = all_stocks  
      
    # 9、订阅数据  
    # 查询持仓  
    context.holding_stocks =  [posi['symbol'] for posi in get_position()]   
    # 合并监控的股票和持仓的股票  
    subscribe_stocks = list(set(context.buy_stocks)|set(context.holding_stocks)|set(context.dk_stocks))  
    # 记录数据  
    if subscribe_stocks:  
        dicts = get_symbols(sec_type1=1010, symbols=subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)  
        pre_data_adjust = history(symbol=subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)  
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],  
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],  
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}  
    else:  
        context.monitor_data = {}  
    # 订阅tick数据  
    subscribe(symbols=subscribe_stocks, frequency='tick', count=2, unsubscribe_previous=True)  
  
  
def sell_algo(context):# 所有持仓  
    nor_str = context.now.strftime('%H:%M:%S')  
    Account_positions = context.account().positions()  
    all_symbols = [posi['symbol'] for posi in Account_positions]  
    current_data_all = current(symbols=all_symbols)  
  
    # 两种卖出之一：早盘卖出  
    if nor_str==context.sell_time1:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停且有利润(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = available_now>0 and current_data['price']<context.monitor_data[symbol]['upper_limit'] and current_data['price']>posi['vwap']  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
    # 两种卖出之二：尾盘卖出  
    elif nor_str==context.sell_time2:  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            current_data = list(filter(lambda x:x['symbol']==symbol,current_data_all))[0]  
            # 卖出条件，未涨停(跌停不卖出)  
            available_now = posi['volume']-posi['volume_today'] if context.mode==MODE_BACKTEST else posi['available_now']  
            sell_cond = current_data['price']<context.monitor_data[symbol]['upper_limit'] and available_now>0  
            if sell_cond and  current_data['price']>context.monitor_data[symbol]['lower_limit']:  
                order_volume(symbol=symbol, volume=available_now, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)  
                  
                  
def buy_algo(context):  
    if len(context.buy_stocks)==0:  
        print('{} 买入监控股票(低开)：{}'.format(context.now, context.dk_stocks))  
        to_buy = context.dk_stocks   
    else:  
        print('{} 买入监控股票(高开)：{}'.format(context.now, context.buy_stocks))  
        to_buy = context.buy_stocks  
      
    for symbol in to_buy:  
        percent = 0.99/len(to_buy)  
        cash = context.account().cash  
          
        # 回测模式下，用市价单进行撮合，即下一个tick的开盘价  
        if context.mode==MODE_BACKTEST:  
            price = context.data(symbol=symbol, frequency='tick', count=1)['open'].iloc[-1]  
            volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
            if volume>0:  
                order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Limit, price=price, position_effect=PositionEffect_Open)  
                unsubscribe(symbols=symbol, frequency='tick')  
        # 实盘模式下，用限价单进行报价，因为9:25-30不接受市价单  
        else:  
            price = context.monitor_data[symbol]['upper_limit']  
            volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)  
            if volume>0:  
                order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=price, position_effect=PositionEffect_Open)  
                unsubscribe(symbols=symbol, frequency='tick')  
  
  
def on_tick(context,tick):  
    symbol = tick['symbol']  
    if symbol in context.buy_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        open_colume_rate = tick['cum_volume']/context.monitor_data[symbol]['pre_volume']  
        if open_colume_rate<0.03:  
            print('{}:{}集合竞价量能为{},不符合条件，取消订阅'.format(tick['created_at'],symbol,open_colume_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
            call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
        if call_rate>=context.max_call_rate or call_rate<=context.min_call_rate:  
            print('{}:{}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.buy_stocks.remove(symbol)  
            return  
            if symbol in context.dk_stocks and symbol not in context.holding_stocks and tick['open']>0 and tick['created_at'].time()<datetime.strptime('09:30:00', '%H:%M:%S').time():  
        # 低开股票判断  
        call_rate = tick['open']/context.monitor_data[symbol]['pre_close']-1  
        if call_rate>=context.dk_max_call_rate or call_rate<=context.dk_min_call_rate:  
            print('{}:(双低){}开盘涨跌幅为{:.2%}，不符合条件,取消订阅'.format(tick['created_at'],symbol,call_rate))  
            unsubscribe(symbols=symbol, frequency='tick')  
            context.dk_stocks.remove(symbol)  
            return  
          
def get_zt_stock(stock_list, date, return_data=False):  
    """筛选出某一日涨停的股票"""  
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)  
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)  
    history_data = history_data.merge(symbols_info, on=['symbol'])  
    zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()  
  
    if return_data:  
        return zt_stock,history_data  
    else:  
        return zt_stock  
  
  
def cal_stock_buy_volume(code,amount,price):  
    """计算股票下单数量"""  
    Account_cash = get_cash()# 获取账户资金信息  
    available_amount = min(amount,Account_cash['available'])                   
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)  
    return trade_volume  
  
  
def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):  
    """  
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））  
    :param date：目标日期  
    :param new_days:新股上市天数，默认为365天  
    :param skip_suspended:是否剔除停牌股，默认为True  
    :param skip_st:是否剔除ST股，默认为True  
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效  
    """    date = pd.Timestamp(date).replace(tzinfo=None)  
    # A股，剔除停牌和ST股票  
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)  
    if len(stocks_info)>0:  
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))  
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))  
        # 剔除次新股和退市股  
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]  
        all_stocks = list(stocks_info['symbol'])  
        # 剔除当日涨停股（收盘价==涨停价）  
        if skip_upper_limit and context.mode==MODE_BACKTEST:  
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)  
            stocks_info = stocks_info.merge(low_price,on=['symbol'])  
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()  
    else:  
        all_stocks = []  
    all_stocks_str = ','.join(all_stocks)  
    return all_stocks,all_stocks_str  
  
  
def history_new(security,frequency,start_time,end_time,fields,skip_suspended=True,fill_missing=None,adjust=ADJUST_PREV,adjust_end_time=None, df=True, type=True, benchmark='SHSE.000300'):  
    """  
    分区间获取数据（以避免超出数据限制）(start_time和end_date为字符串,fields需包含eob和symbol,单字段)  
    :param ：参数同history()参数一致，adjust_end_time默认为回测结束时间：None,注意需要根据不同场景使用end_time或context.backtest_end_time  
    :param type：默认为True，输出2维DataFrame（日期*股票）,否则输出1维DataFrame  
    """    Data = pd.DataFrame()  
    if frequency=='1d':  
        trading_date = pd.Series(get_trading_dates(exchange='SZSE', start_date=start_time, end_date=end_time))  
    elif frequency=='tick':  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='created_at', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        trading_date = history(benchmark, frequency=frequency, start_time=start_time, end_time=end_time, fields='bob,eob', skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    # 计算合理间隔  
    if isinstance(security,str):  
        security = security.split(',')  
    space = 30000//len(security)  
    # 获取数据  
    if len(trading_date)<=space:  
        Data = history(security, frequency=frequency, start_time=start_time, end_time=end_time, fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
    else:  
        for n in range(int(np.ceil(len(trading_date)/space))):  
            start = n*space  
            end = start+space  
            if end>=len(trading_date):  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[-1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                elif frequency=='tick':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[-1][1], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            else:  
                if frequency=='1d':  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start], end_time=trading_date.iloc[end], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
                else:  
                    data = history(security, frequency=frequency, start_time=trading_date.iloc[start][0], end_time=trading_date.iloc[end][0], fields=fields, skip_suspended=skip_suspended, fill_missing=fill_missing, adjust=adjust, adjust_end_time=adjust_end_time, df=df)  
            if len(data)==33000:  
                print('请检查返回数据量，可能超过系统限制，缺少数据！！！！！！！！！！')  
            Data = pd.concat([Data,data])  
    if df and len(Data)>0:  
        if frequency=='tick':   
            Data.sort_values(['symbol','created_at'],inplace=True)  
            Data.drop_duplicates(subset=['created_at','symbol'],keep='first',inplace=True)  
        else:  
            Data.sort_values(['symbol','eob'],inplace=True)  
            Data.drop_duplicates(subset=['eob','symbol'],keep='first',inplace=True)  
        if type:  
            if len(Data)>0:  
                if frequency=='tick':  
                    Data = Data.set_index(['created_at','symbol'])  
                else:  
                    Data = Data.set_index(['eob','symbol'])  
                Data = Data.unstack()  
                Data.columns = Data.columns.droplevel(level=0)  
    return Data  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print('{}:拒绝委托：{}'.format(context.now,order))  
         
  
if __name__ == '__main__':  
    '''  
        strategy_id策略ID, 由系统生成  
        filename文件名, 请与本文件名保持一致  
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
        token绑定计算机的ID, 可在系统设置-密钥管理中生成  
        backtest_start_time回测开始时间  
        backtest_end_time回测结束时间  
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
        backtest_initial_cash回测初始资金  
        backtest_commission_ratio回测佣金比例  
        backtest_slippage_ratio回测滑点比例  
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
        '''    run(strategy_id='96710f63-66db-11ef-8b83-bc6ee2b72c72',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2023-09-01 08:00:00',  
        backtest_end_time='2024-08-30 16:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=100000,  
        backtest_commission_ratio=0.0008,  
        backtest_slippage_ratio=0.00123,  
        backtest_match_mode=0)


# 跨期套利策略(期货)(日内)
**策略说明：**

基于前期的日频跨期套利策略修改为日内实时的跨期套利策略，并

改进3（实时1.0）、将日频改为日内实时：bar频率。

改进4（实时1.1）、优化平仓条件：止盈止损点锚定开仓点价格。

改进5（实时1.2）、在1.1的基础上，优化开仓条件：价差突破时才开仓（往价差均值方向突破）。

改进5（实时1.2.1）、在1.1的基础上，优化开仓条件：有价差回归的趋势时才开仓，趋势以双均线为例。

改进6（实时1.3）、在1.1的基础上，多品种组合。

## 实时1.0
### coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。  
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差处于上轨时做空价差组合,处于下轨时做多价差组合。  
  
改进1（日频）、202307，焦炭的开仓手续费是万一，平今手续费是万一点四，可以取均值设置为万一点二。  
改进2（日频）、取消order_close_all和市价委托，这两种方式在回测且没有订阅数据时，是以次日开盘价或收盘价作为成交价，不够准确，改为以实时价格的限价单进行委托  
改进3（实时1.0）、将日频改为日内实时：bar频率  
'''  
  
  
def init(context):  
    # 交易参数  
    context.frequency = '60s'            # 期货实时行情支持15s, 30s, 60s, 300s, 900s, 1800s  
    context.contract_A = 'DCE.J'         # 主力合约  
    context.contract_B = 'DCE.J22'       # 次主力合约  
    context.main_contract = None         # 具体的主力合约  
    context.minor_contract = None        # 具体的次主力合约  
    context.trade_num = 1                # 开仓数量  
  
    # 指标参数  
    context.periods_time = 105           # 设置回溯周期  
    context.boll_multiple = 1.5          # 布林带上下轨倍数  
    context.stoppoint_multiple = 3       # 止盈止损倍数     
context.close_all = False            # 清仓信号  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        minor_contract_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {}  
            for dic in main_contract_list:  
                context.main_contract_list[dic['trade_date']] = dic['symbol']  
        if len(minor_contract_list)>0:  
            context.minor_contract_list = {}  
            for dic in minor_contract_list:  
                context.minor_contract_list[dic['trade_date']] = dic['symbol']  
    # 其他  
    context.exchanges = context.contract_A.split('.')[0]            # 交易所代码  
    context.virt_contract_list = set([context.contract_A,context.contract_A+'22',context.contract_A+'00',context.contract_A+'01',context.contract_A+'02',context.contract_A+'03',context.contract_A+'99'])  
    # 设置定时任务：夜盘21点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):      
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约和次主力合约  
    date_list = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)  
    if context.now.hour>15:  
        date = date_list[-1]  
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    pre_date = date_list[-1]  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list and date in context.minor_contract_list:  
        main_contract = context.main_contract_list[date]  
        minor_contract = context.minor_contract_list[date]  
    else:  
        main_contract = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)[0]['symbol']  
        minor_contract = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)[0]['symbol']  
          
    # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约  
    Account_positions = context.account().positions()   
    if main_contract!=context.main_contract or minor_contract!=context.minor_contract:  
        if Account_positions:  
            for posi in Account_positions:  
                if posi['symbol']==context.main_contract and main_contract!=context.main_contract:  
                    print('{}：主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract))  
                    context.close_all = True  
                if posi['symbol']==context.minor_contract and minor_contract!=context.minor_contract:  
                    print('{}：次主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract))  
                    context.close_all = True  
        # 更新主力合约  
        context.main_contract = main_contract  
        context.minor_contract = minor_contract  
          
    # 当context.close_all为True时，清仓  
    if context.close_all:  
        context.close_all = False  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            side = posi['side']  
            trade_side = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
            price_new = history(symbol=symbol, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
            order_volume(symbol=symbol, side=trade_side, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
        print('{}:平仓'.format(context.now))  
  
  
    # 获取历史数据  
    close_main = history_n(symbol=context.main_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    close_minor = history_n(symbol=context.minor_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    # 计算布林带  
    spread_history = close_main - close_minor# 历史价差  
    context.spread_history_mean = np.mean(spread_history)# 历史价差的均值  
    spread_history_std = np.std(spread_history)# 历史价差的标准差  
    context.open_price_diff = context.boll_multiple * spread_history_std# 开仓的价差  
    context.upper = context.spread_history_mean + context.open_price_diff# 布林带上轨  
    context.lower = context.spread_history_mean - context.open_price_diff# 布林带下轨  
    context.stop_price_diff = context.stoppoint_multiple * spread_history_std# 止损的价差  
    context.upper_stoppoint = context.spread_history_mean + context.stop_price_diff# 上轨止损线  
    context.lower_stoppoint = context.spread_history_mean - context.stop_price_diff# 下轨止损线  
    # 行情订阅  
    subscribe(symbols=[context.main_contract,context.minor_contract], frequency=context.frequency, count=2, wait_group=True, unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    # 获取仓位# 指定持仓  
    position_long = context.account().position(symbol=context.main_contract,side = PositionSide_Long)        # 多头仓位  
    position_short = context.account().position(symbol=context.main_contract,side = PositionSide_Short)      # 空头仓位  
    if len(bars)<2:return  
    main_price,minor_price = 0,0  
    if bars[0]['symbol']==context.main_contract:  
        main_price = bars[0]['close']  
    elif bars[1]['symbol']==context.main_contract:  
        main_price = bars[1]['close']  
    if bars[0]['symbol']==context.minor_contract:  
        minor_price = bars[0]['close']  
    elif bars[1]['symbol']==context.minor_contract:  
        minor_price = bars[1]['close']  
    if main_price==0 or minor_price==0:return  
    spread_new = main_price-minor_price  
  
    # 没有仓位时  
    if not position_short and not position_long:  
        if spread_new > context.upper:  
            print('{}:做空价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.minor_contract,context.main_contract,spread_new))  
            order_volume(symbol=context.main_contract,side=OrderSide_Sell,volume=context.trade_num,order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
        if spread_new < context.lower:  
            print('{}:做多价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.main_contract,context.minor_contract,spread_new))  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
    # 设计平仓信号  
    # 做多价差组合时  
    if position_long:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new >= context.spread_history_mean:  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（升至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new <= context.lower_stoppoint:  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（跌至止损线）:{:.1f}'.format(context.now,spread_new))  
  
    # 做空价差组合时  
    if position_short:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new <= context.spread_history_mean:  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（跌至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new >= context.upper_stoppoint:  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（升至止损线）:{:.1f}'.format(context.now,spread_new))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''run(strategy_id='86390bd2-6b52-11ef-a083-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 21:00:00',  
        backtest_end_time='2024-09-15 23:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.00012,  
        backtest_slippage_ratio=0.0000,  
        backtest_match_mode=1)

## 实时1.1：优化平仓
### coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。  
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差处于上轨时做空价差组合,处于下轨时做多价差组合。  
  
改进1（日频）、202307，焦炭的开仓手续费是万一，平今手续费是万一点四，可以取均值设置为万一点二。  
改进2（日频）、取消order_close_all和市价委托，这两种方式在回测且没有订阅数据时，是以次日开盘价或收盘价作为成交价，不够准确，改为以实时价格的限价单进行委托。  
改进3（实时1.0）、将日频改为日内实时：bar频率。  
改进4（实时1.1）、优化平仓条件：止盈止损点锚定开仓点价格。  
'''  
  
  
def init(context):  
    # 交易参数  
    context.frequency = '60s'            # 期货实时行情支持15s, 30s, 60s, 300s, 900s, 1800s  
    context.contract_A = 'DCE.J'         # 主力合约  
    context.contract_B = 'DCE.J22'       # 次主力合约  
    context.main_contract = None         # 具体的主力合约  
    context.minor_contract = None        # 具体的次主力合约  
    context.trade_price_spread = 0       # 交易时的价差  
    context.trade_num = 1                # 开仓数量  
  
    # 指标参数  
    context.periods_time = 105           # 设置回溯周期  
    context.boll_multiple = 1.5          # 布林带上下轨倍数  
    context.stoppoint_multiple = 3       # 止盈止损倍数     
context.close_all = False            # 清仓信号  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        minor_contract_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {}  
            for dic in main_contract_list:  
                context.main_contract_list[dic['trade_date']] = dic['symbol']  
        if len(minor_contract_list)>0:  
            context.minor_contract_list = {}  
            for dic in minor_contract_list:  
                context.minor_contract_list[dic['trade_date']] = dic['symbol']  
    # 其他  
    context.exchanges = context.contract_A.split('.')[0]            # 交易所代码  
    context.virt_contract_list = set([context.contract_A,context.contract_A+'22',context.contract_A+'00',context.contract_A+'01',context.contract_A+'02',context.contract_A+'03',context.contract_A+'99'])  
    # 设置定时任务：夜盘21点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):      
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约和次主力合约  
    date_list = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)  
    if context.now.hour>15:  
        date = date_list[-1]  
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    pre_date = date_list[-1]  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list and date in context.minor_contract_list:  
        main_contract = context.main_contract_list[date]  
        minor_contract = context.minor_contract_list[date]  
    else:  
        main_contract = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)[0]['symbol']  
        minor_contract = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)[0]['symbol']  
    # # 次主力合约不用旧的合约（比主力合约新，比原本的次主力合约新）  
    # if minor_contract<main_contract:  
    #     infos = get_symbols(sec_type1=1040, exchanges=context.exchanges, trade_date=date, df=True)    #     contract_list = list(set(set(infos[infos['underlying_symbol']==context.contract_A]['symbol'].tolist())-context.virt_contract_list))    #     data = history(symbol=contract_list, frequency='1d', start_time=pre_date, end_time=pre_date, adjust=ADJUST_NONE, adjust_end_time=pre_date, df=True)    #     data = data[data['volume']>0].sort_values('volume',ascending=False)    #     contract_list = data['symbol'].tolist()    #     for contract in contract_list:    #         if contract>main_contract:    #             minor_contract = contract    #             break    # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约  
    Account_positions = context.account().positions()   
    if main_contract!=context.main_contract or minor_contract!=context.minor_contract:  
        if Account_positions:  
            for posi in Account_positions:  
                if posi['symbol']==context.main_contract and main_contract!=context.main_contract:  
                    print('{}：主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract))  
                    context.close_all = True  
                if posi['symbol']==context.minor_contract and minor_contract!=context.minor_contract:  
                    print('{}：次主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract))  
                    context.close_all = True  
        # 更新主力合约  
        context.main_contract = main_contract  
        context.minor_contract = minor_contract  
    # 当context.close_all为True时，清仓  
    if context.close_all:  
        context.close_all = False  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            side = posi['side']  
            trade_side = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
            price_new = history(symbol=symbol, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
            order_volume(symbol=symbol, side=trade_side, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
        print('{}:平仓'.format(context.now))  
  
  
    # 获取历史数据  
    close_main = history_n(symbol=context.main_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    close_minor = history_n(symbol=context.minor_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    # 计算布林带  
    spread_history = close_main - close_minor# 历史价差  
    context.spread_history_mean = np.mean(spread_history)# 历史价差的均值  
    spread_history_std = np.std(spread_history)# 历史价差的标准差  
    context.open_price_diff = context.boll_multiple * spread_history_std# 开仓的价差  
    context.upper = context.spread_history_mean + context.open_price_diff# 布林带上轨  
    context.lower = context.spread_history_mean - context.open_price_diff# 布林带下轨  
    context.stop_price_diff = context.stoppoint_multiple * spread_history_std# 止损的价差  
    context.upper_stoppoint = context.spread_history_mean + context.stop_price_diff# 上轨止损线  
    context.lower_stoppoint = context.spread_history_mean - context.stop_price_diff# 下轨止损线  
    # 行情订阅  
    subscribe(symbols=[context.main_contract,context.minor_contract], frequency=context.frequency, count=2, wait_group=True, unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    # 获取仓位# 指定持仓  
    position_long = context.account().position(symbol=context.main_contract,side = PositionSide_Long)        # 多头仓位  
    position_short = context.account().position(symbol=context.main_contract,side = PositionSide_Short)      # 空头仓位  
    if len(bars)<2:return  
    main_price,minor_price = 0,0  
    if bars[0]['symbol']==context.main_contract:  
        main_price = bars[0]['close']  
    elif bars[1]['symbol']==context.main_contract:  
        main_price = bars[1]['close']  
    if bars[0]['symbol']==context.minor_contract:  
        minor_price = bars[0]['close']  
    elif bars[1]['symbol']==context.minor_contract:  
        minor_price = bars[1]['close']  
    if main_price==0 or minor_price==0:return  
    spread_new = main_price-minor_price  
  
    # 没有仓位时  
    if not position_short and not position_long:  
        if spread_new > context.upper:  
            context.trade_price_spread =spread_new  
            print('{}:做空价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.minor_contract,context.main_contract,spread_new))  
            order_volume(symbol=context.main_contract,side=OrderSide_Sell,volume=context.trade_num,order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
        if spread_new < context.lower:  
            context.trade_price_spread =spread_new  
            print('{}:做多价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.main_contract,context.minor_contract,spread_new))  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
    # 设计平仓信号  
    # 做多价差组合时  
    if position_long:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new >= context.spread_history_mean and spread_new>context.trade_price_spread+context.open_price_diff:  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（升至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new <= context.lower_stoppoint and spread_new<context.trade_price_spread-(context.stop_price_diff-context.open_price_diff):  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（跌至止损线）:{:.1f}'.format(context.now,spread_new))  
  
    # 做空价差组合时  
    if position_short:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new <= context.spread_history_mean and spread_new<context.trade_price_spread-context.open_price_diff:  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（跌至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new >= context.upper_stoppoint and spread_new>context.trade_price_spread+(context.stop_price_diff-context.open_price_diff):  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（升至止损线）:{:.1f}'.format(context.now,spread_new))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''run(strategy_id='86390bd2-6b52-11ef-a083-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 21:00:00',  
        backtest_end_time='2024-09-24 23:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.00012,  
        backtest_slippage_ratio=0.0000,  
        backtest_match_mode=1)

## 实时1.2.1：优化开仓+趋势
### coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。  
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差处于上轨时做空价差组合,处于下轨时做多价差组合。  
  
改进1（日频）、202307，焦炭的开仓手续费是万一，平今手续费是万一点四，可以取均值设置为万一点二。  
改进2（日频）、取消order_close_all和市价委托，这两种方式在回测且没有订阅数据时，是以次日开盘价或收盘价作为成交价，不够准确，改为以实时价格的限价单进行委托。  
改进3（实时1.0）、将日频改为日内实时：bar频率。  
改进4（实时1.1）、优化平仓条件：止盈止损点锚定开仓点价格。  
'''  
  
  
def init(context):  
    # 交易参数  
    context.frequency = '60s'            # 期货实时行情支持15s, 30s, 60s, 300s, 900s, 1800s  
    context.contract_A = 'DCE.J'         # 主力合约  
    context.contract_B = 'DCE.J22'       # 次主力合约  
    context.main_contract = None         # 具体的主力合约  
    context.minor_contract = None        # 具体的次主力合约  
    context.trade_price_spread = 0       # 交易时的价差  
    context.trade_num = 1                # 开仓数量  
  
    # 指标参数  
    context.periods_time = 105           # 设置回溯周期  
    context.boll_multiple = 1.5          # 布林带上下轨倍数  
    context.stoppoint_multiple = 3       # 止盈止损倍数     
context.close_all = False            # 清仓信号  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        minor_contract_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            context.main_contract_list = {}  
            for dic in main_contract_list:  
                context.main_contract_list[dic['trade_date']] = dic['symbol']  
        if len(minor_contract_list)>0:  
            context.minor_contract_list = {}  
            for dic in minor_contract_list:  
                context.minor_contract_list[dic['trade_date']] = dic['symbol']  
    # 其他  
    context.exchanges = context.contract_A.split('.')[0]            # 交易所代码  
    context.virt_contract_list = set([context.contract_A,context.contract_A+'22',context.contract_A+'00',context.contract_A+'01',context.contract_A+'02',context.contract_A+'03',context.contract_A+'99'])  
    # 设置定时任务：夜盘21点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):      
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约和次主力合约  
    date_list = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)  
    if context.now.hour>15:  
        date = date_list[-1]  
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    pre_date = date_list[-1]  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list and date in context.minor_contract_list:  
        main_contract = context.main_contract_list[date]  
        minor_contract = context.minor_contract_list[date]  
    else:  
        main_contract = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)[0]['symbol']  
        minor_contract = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)[0]['symbol']  
    # # 次主力合约不用旧的合约（比主力合约新，比原本的次主力合约新）  
    # if minor_contract<main_contract:  
    #     infos = get_symbols(sec_type1=1040, exchanges=context.exchanges, trade_date=date, df=True)    #     contract_list = list(set(set(infos[infos['underlying_symbol']==context.contract_A]['symbol'].tolist())-context.virt_contract_list))    #     data = history(symbol=contract_list, frequency='1d', start_time=pre_date, end_time=pre_date, adjust=ADJUST_NONE, adjust_end_time=pre_date, df=True)    #     data = data[data['volume']>0].sort_values('volume',ascending=False)    #     contract_list = data['symbol'].tolist()    #     for contract in contract_list:    #         if contract>main_contract:    #             minor_contract = contract    #             break    # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约  
    Account_positions = context.account().positions()   
    if main_contract!=context.main_contract or minor_contract!=context.minor_contract:  
        if Account_positions:  
            for posi in Account_positions:  
                if posi['symbol']==context.main_contract and main_contract!=context.main_contract:  
                    print('{}：主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract))  
                    context.close_all = True  
                if posi['symbol']==context.minor_contract and minor_contract!=context.minor_contract:  
                    print('{}：次主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract))  
                    context.close_all = True  
        # 更新主力合约  
        context.main_contract = main_contract  
        context.minor_contract = minor_contract  
    # 当context.close_all为True时，清仓  
    if context.close_all:  
        context.close_all = False  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            side = posi['side']  
            trade_side = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
            price_new = history(symbol=symbol, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
            order_volume(symbol=symbol, side=trade_side, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
        print('{}:平仓'.format(context.now))  
  
  
    # 获取历史数据  
    close_main = history_n(symbol=context.main_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    close_minor = history_n(symbol=context.minor_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    # 计算布林带  
    spread_history = close_main - close_minor# 历史价差  
    context.spread_history_mean = np.mean(spread_history)# 历史价差的均值  
    spread_history_std = np.std(spread_history)# 历史价差的标准差  
    context.open_price_diff = context.boll_multiple * spread_history_std# 开仓的价差  
    context.upper = context.spread_history_mean + context.open_price_diff# 布林带上轨  
    context.lower = context.spread_history_mean - context.open_price_diff# 布林带下轨  
    context.stop_price_diff = context.stoppoint_multiple * spread_history_std# 止损的价差  
    context.upper_stoppoint = context.spread_history_mean + context.stop_price_diff# 上轨止损线  
    context.lower_stoppoint = context.spread_history_mean - context.stop_price_diff# 下轨止损线  
    # 行情订阅  
    subscribe(symbols=[context.main_contract,context.minor_contract], frequency=context.frequency, count=2, wait_group=True, unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    # 获取仓位# 指定持仓  
    position_long = context.account().position(symbol=context.main_contract,side = PositionSide_Long)        # 多头仓位  
    position_short = context.account().position(symbol=context.main_contract,side = PositionSide_Short)      # 空头仓位  
    if len(bars)<2:return  
    main_price,minor_price = 0,0  
    if bars[0]['symbol']==context.main_contract:  
        main_price = bars[0]['close']  
    elif bars[1]['symbol']==context.main_contract:  
        main_price = bars[1]['close']  
    if bars[0]['symbol']==context.minor_contract:  
        minor_price = bars[0]['close']  
    elif bars[1]['symbol']==context.minor_contract:  
        minor_price = bars[1]['close']  
    if main_price==0 or minor_price==0:return  
    spread_new = main_price-minor_price  
  
    # 没有仓位时  
    if not position_short and not position_long:  
        if spread_new > context.upper:  
            context.trade_price_spread =spread_new  
            print('{}:做空价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.minor_contract,context.main_contract,spread_new))  
            order_volume(symbol=context.main_contract,side=OrderSide_Sell,volume=context.trade_num,order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
        if spread_new < context.lower:  
            context.trade_price_spread =spread_new  
            print('{}:做多价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.main_contract,context.minor_contract,spread_new))  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
    # 设计平仓信号  
    # 做多价差组合时  
    if position_long:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new >= context.spread_history_mean and spread_new>context.trade_price_spread+context.open_price_diff:  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（升至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new <= context.lower_stoppoint and spread_new<context.trade_price_spread-(context.stop_price_diff-context.open_price_diff):  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（跌至止损线）:{:.1f}'.format(context.now,spread_new))  
  
    # 做空价差组合时  
    if position_short:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new <= context.spread_history_mean and spread_new<context.trade_price_spread-context.open_price_diff:  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（跌至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new >= context.upper_stoppoint and spread_new>context.trade_price_spread+(context.stop_price_diff-context.open_price_diff):  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（升至止损线）:{:.1f}'.format(context.now,spread_new))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''run(strategy_id='86390bd2-6b52-11ef-a083-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 21:00:00',  
        backtest_end_time='2024-09-24 23:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.00012,  
        backtest_slippage_ratio=0.0000,  
        backtest_match_mode=1)

## 实时1.2：优化开仓
### coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。  
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差处于上轨时做空价差组合,处于下轨时做多价差组合。  
  
改进1（日频）、202307，焦炭的开仓手续费是万一，平今手续费是万一点四，可以取均值设置为万一点二。  
改进2（日频）、取消order_close_all和市价委托，这两种方式在回测且没有订阅数据时，是以次日开盘价或收盘价作为成交价，不够准确，改为以实时价格的限价单进行委托。  
改进3（实时1.0）、将日频改为日内实时：bar频率。  
改进4（实时1.1）、优化平仓条件：止盈止损点锚定开仓点价格。  
改进5（实时1.2）、优化开仓条件：价差突破时才开仓（往价差均值方向突破）  
'''  
  
  
def init(context):  
    # 交易参数  
    context.frequency = '60s'            # 期货实时行情支持15s, 30s, 60s, 300s, 900s, 1800s  
    context.contract_A = 'DCE.J'         # 主力合约  
    context.contract_B = 'DCE.J22'       # 次主力合约  
    context.main_contract = None         # 具体的主力合约  
    context.minor_contract = None        # 具体的次主力合约  
    context.trade_price_spread = 0       # 交易时的价差  
    context.trade_num = 1                # 开仓数量  
  
    # 指标参数  
    context.periods_time = 105           # 设置回溯周期  
    context.boll_multiple = 1.5          # 布林带上下轨倍数  
    context.stoppoint_multiple = 3       # 止盈止损倍数     
context.close_all = False            # 清仓信号  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        context.main_contract_list = {}  
        context.minor_contract_list = {}  
        main_contract_list = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        minor_contract_list = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
        if len(main_contract_list)>0:  
            for dic in main_contract_list:  
                context.main_contract_list[dic['trade_date']] = dic['symbol']  
        if len(minor_contract_list)>0:  
            for dic in minor_contract_list:  
                context.minor_contract_list[dic['trade_date']] = dic['symbol']  
    # 其他  
    context.exchanges = context.contract_A.split('.')[0]            # 交易所代码  
    context.virt_contract_list = set([context.contract_A,context.contract_A+'22',context.contract_A+'00',context.contract_A+'01',context.contract_A+'02',context.contract_A+'03',context.contract_A+'99'])  
    # 设置定时任务：夜盘21点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):      
    now_str = context.now.strftime('%Y-%m-%d')  
    # 主力合约和次主力合约  
    date_list = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)  
    if context.now.hour>15:  
        date = date_list[-1]  
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    pre_date = date_list[-1]  
    if context.mode==MODE_BACKTEST and date in context.main_contract_list and date in context.minor_contract_list:  
        main_contract = context.main_contract_list[date]  
        minor_contract = context.minor_contract_list[date]  
    else:  
        main_contract = fut_get_continuous_contracts(csymbol=context.contract_A, start_date=date, end_date=date)  
        if len(main_contract)>0:  
            main_contract = main_contract[0]['symbol']  
        else:  
            return  
        minor_contract = fut_get_continuous_contracts(csymbol=context.contract_B, start_date=date, end_date=date)  
        if len(minor_contract)>0:  
            minor_contract = minor_contract[0]['symbol']  
        else:  
            return  
    # # 次主力合约不用旧的合约（比主力合约新，比原本的次主力合约新）  
    # if minor_contract<main_contract:  
    #     infos = get_symbols(sec_type1=1040, exchanges=context.exchanges, trade_date=date, df=True)    #     contract_list = list(set(set(infos[infos['underlying_symbol']==context.contract_A]['symbol'].tolist())-context.virt_contract_list))    #     data = history(symbol=contract_list, frequency='1d', start_time=pre_date, end_time=pre_date, adjust=ADJUST_NONE, adjust_end_time=pre_date, df=True)    #     data = data[data['volume']>0].sort_values('volume',ascending=False)    #     contract_list = data['symbol'].tolist()    #     for contract in contract_list:    #         if contract>main_contract:    #             minor_contract = contract    #             break    # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约  
    Account_positions = context.account().positions()   
    if main_contract!=context.main_contract or minor_contract!=context.minor_contract:  
        if Account_positions:  
            for posi in Account_positions:  
                if posi['symbol']==context.main_contract and main_contract!=context.main_contract:  
                    print('{}：主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract))  
                    context.close_all = True  
                if posi['symbol']==context.minor_contract and minor_contract!=context.minor_contract:  
                    print('{}：次主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract))  
                    context.close_all = True  
        # 更新主力合约  
        context.main_contract = main_contract  
        context.minor_contract = minor_contract  
    # 当context.close_all为True时，清仓  
    if context.close_all:  
        context.close_all = False  
        for posi in Account_positions:  
            symbol = posi['symbol']  
            side = posi['side']  
            trade_side = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
            price_new = history(symbol=symbol, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
            order_volume(symbol=symbol, side=trade_side, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
        print('{}:平仓'.format(context.now))  
  
  
    # 获取历史数据  
    close_main = history_n(symbol=context.main_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    close_minor = history_n(symbol=context.minor_contract,frequency='1d', count=context.periods_time+1, end_time=context.now, df=True)['close']  
    # 计算布林带  
    spread_history = close_main - close_minor# 历史价差  
    context.spread_history_mean = np.mean(spread_history)# 历史价差的均值  
    spread_history_std = np.std(spread_history)# 历史价差的标准差  
    context.open_price_diff = context.boll_multiple * spread_history_std# 开仓的价差  
    context.upper = context.spread_history_mean + context.open_price_diff# 布林带上轨  
    context.lower = context.spread_history_mean - context.open_price_diff# 布林带下轨  
    context.stop_price_diff = context.stoppoint_multiple * spread_history_std# 止损的价差  
    context.upper_stoppoint = context.spread_history_mean + context.stop_price_diff# 上轨止损线  
    context.lower_stoppoint = context.spread_history_mean - context.stop_price_diff# 下轨止损线  
    # 行情订阅  
    subscribe(symbols=[context.main_contract,context.minor_contract], frequency=context.frequency, count=2, wait_group=True, unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
    # 获取仓位# 指定持仓  
    position_long = context.account().position(symbol=context.main_contract,side = PositionSide_Long)        # 多头仓位  
    position_short = context.account().position(symbol=context.main_contract,side = PositionSide_Short)      # 空头仓位  
    if len(bars)<2:return  
    main_price,minor_price = 0,0  
    for bar in bars:  
        if bar['symbol']==context.main_contract:  
            main_price = bar['close']  
        if bar['symbol']==context.minor_contract:  
            minor_price = bar['close']  
    if main_price==0 or minor_price==0:return  
    spread_new = main_price-minor_price  
    # 没有仓位时  
    if not position_short and not position_long:  
        # 获取上一个bar价格  
        pre_main_price = context.data(symbol=context.main_contract, frequency=context.frequency, count=2, fields='eob,close')['close'].iloc[0]  
        pre_minor_price = context.data(symbol=context.minor_contract, frequency=context.frequency, count=2, fields='eob,close')['close'].iloc[0]  
        pre_spread_new = pre_main_price-pre_minor_price  
        if spread_new < context.upper and pre_spread_new>=context.upper:  
            context.trade_price_spread =spread_new  
            print('{}:做空价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.minor_contract,context.main_contract,spread_new))  
            order_volume(symbol=context.main_contract,side=OrderSide_Sell,volume=context.trade_num,order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
        if spread_new > context.lower and pre_spread_new <= context.lower:  
            context.trade_price_spread =spread_new  
            print('{}:做多价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,context.main_contract,context.minor_contract,spread_new))  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
    # 设计平仓信号  
    # 做多价差组合时  
    if position_long:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new >= context.spread_history_mean and spread_new>context.trade_price_spread+context.open_price_diff:  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（升至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new <= context.lower_stoppoint and spread_new<context.trade_price_spread-(context.stop_price_diff-context.open_price_diff):  
            order_volume(symbol=context.main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（跌至止损线）:{:.1f}'.format(context.now,spread_new))  
  
    # 做空价差组合时  
    if position_short:  
        # 价差回归到均值水平时或偏离达到止损位时，平仓  
        if spread_new <= context.spread_history_mean and spread_new<context.trade_price_spread-context.open_price_diff:  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=1,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止盈平仓（跌至平均水平）:{:.1f}'.format(context.now,spread_new))  
        elif spread_new >= context.upper_stoppoint and spread_new>context.trade_price_spread+(context.stop_price_diff-context.open_price_diff):  
            order_volume(symbol=context.main_contract, side=OrderSide_Buy,volume=1,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
            order_volume(symbol=context.minor_contract, side=OrderSide_Sell, volume=1, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
            print('{}:止损平仓（升至止损线）:{:.1f}'.format(context.now,spread_new))  
  
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''run(strategy_id='86390bd2-6b52-11ef-a083-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 21:00:00',  
        backtest_end_time='2024-09-24 23:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.00012,  
        backtest_slippage_ratio=0.0000,  
        backtest_match_mode=1)

## 实时1.3：多品种组合
### coding=utf-8  
from __future__ import print_function, absolute_import, unicode_literals  
from gm.api import *  
  
import re  
import datetime  
import numpy as np  
import pandas as pd  
  
'''  
示例策略仅供参考，不建议直接实盘使用。  
  
跨期套利是在同一期货品种的不同月份合约上建立数量相等、方向相反的交易头寸，最后以对冲或交割方式结束交易、获得收益的方式。  
本策略基于主力合约和次主力合约的价格序列，并构建价差的布林带,在价差处于上轨时做空价差组合,处于下轨时做多价差组合。  
  
改进1（日频）、202307，焦炭的开仓手续费是万一，平今手续费是万一点四，可以取均值设置为万一点二。  
改进2（日频）、取消order_close_all和市价委托，这两种方式在回测且没有订阅数据时，是以次日开盘价或收盘价作为成交价，不够准确，改为以实时价格的限价单进行委托。  
改进3（实时1.0）、将日频改为日内实时：bar频率。  
改进4（实时1.1）、优化平仓条件：止盈止损点锚定开仓点价格。  
改进5（实时1.2）、优化开仓条件：价差突破时才开仓（往价差均值方向突破）。  
改进5（实时1.2.1）、优化开仓条件：有价差回归的趋势时才开仓，趋势以双均线为例。  
改进6（实时1.3）、多品种组合。  
'''  
  
  
def init(context):  
    # 交易参数  
    context.frequency = '60s'                                                      # 期货实时行情支持15s, 30s, 60s, 300s, 900s, 1800s  
    context.continue_contracts = {'DCE.J':'DCE.J22','SHFE.RB':'SHFE.RB22'}         # 连续合约对，主力连续合约：次主力连续合约  
    context.main_minor_contract = {main:{'main_contract':None,'minor_contract':None} for main in context.continue_contracts}             # 具体的合约对，主力合约：次主力合约  
    context.trade_num = 1                     # 开仓数量  
  
    # 指标参数,{主力连续合约：{},}，默认为回溯周期105，上下轨倍数1.5，止盈止损倍数3  
    context.trade_parm = {main:{'periods_time':105,'boll_multiple':1.5,'stoppoint_multiple':3} for main in context.continue_contracts}     
    context.trade_parm['SHFE.HC'] = {'periods_time':21,'boll_multiple':2,'stoppoint_multiple':3}        # 单独设置   
context.trade_parm['SHFE.RB'] = {'periods_time':21,'boll_multiple':1,'stoppoint_multiple':2.5}      # 单独设置  
    # 其他参数  
    context.trade_price_spread = {}           # 交易时的价差  
  
    # 数据一次性获取  
    if context.mode==MODE_BACKTEST:  
        context.main_contract_dict = {}  
        context.minor_contract_dict = {}  
        for main_continue in context.continue_contracts.keys():  
            # 主力合约及其日期  
            main_contract_dict = fut_get_continuous_contracts(csymbol=main_continue, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
            context.main_contract_dict[main_continue] = {dic['trade_date']:dic['symbol'] for dic in main_contract_dict}  
  
            # 次主力合约及其日期  
            minor_continue = context.continue_contracts[main_continue]  
            minor_contract_dict = fut_get_continuous_contracts(csymbol=minor_continue, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])  
            context.minor_contract_dict[minor_continue] = {dic['trade_date']:dic['symbol'] for dic in minor_contract_dict}  
      
    # 设置定时任务：夜盘21点开始  
    schedule(schedule_func=algo, date_rule='1d', time_rule='21:00:00')  
  
  
def algo(context):      
    # 日期  
    now_str = context.now.strftime('%Y-%m-%d')  
    date_list = get_next_n_trading_dates(exchange='SHSE', date=now_str, n=1)  
    if context.now.hour>15:  
        date = date_list[-1]  
    else:  
        date = context.now.strftime('%Y-%m-%d')  
    pre_date = date_list[-1]  
  
    # 主力合约和次主力合约  
    for main_continue,minor_continue in context.continue_contracts.items():  
              
        if context.mode==MODE_BACKTEST and date in context.main_contract_dict and date in context.minor_contract_dict:  
            main_contract = context.main_contract_dict[main_continue][date]  
            minor_contract = context.minor_contract_dict[minor_continue][date]  
        else:  
            main_contract = fut_get_continuous_contracts(csymbol=main_continue, start_date=date, end_date=date)[0]['symbol']  
            minor_contract = fut_get_continuous_contracts(csymbol=minor_continue, start_date=date, end_date=date)[0]['symbol']  
              
        # 具体合约首次赋值  
        if context.main_minor_contract[main_continue]['main_contract'] is None or context.main_minor_contract[main_continue]['minor_contract'] is None:  
            context.main_minor_contract[main_continue]['main_contract'] = main_contract  
            context.main_minor_contract[main_continue]['minor_contract'] = minor_contract  
            continue  
  
        # 有持仓时，检查持仓的合约是否为当前的主力合约和次主力合约  
        Account_positions = context.account().positions()   
        for posi in Account_positions:  
            symbol_latter = re.findall('\D+',posi['symbol'])[0].upper()  
            if symbol_latter==main_continue:# 相同品种，主力或次主力  
                if posi['symbol']==context.main_minor_contract[main_continue]['main_contract'] and main_contract!=context.main_minor_contract[main_continue]['main_contract']:  
                    print('{}：主力合约由{}替换为{}, 次主力合约由{}替换为{}'.format(context.now,posi['symbol'],main_contract,context.main_minor_contract[main_continue]['minor_contract'],minor_contract))  
                    # 先平主力合约  
                    symbol1 = posi['symbol']  
                    side = posi['side']  
                    trade_side1 = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
                    price_new = history(symbol=symbol1, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
                    order_volume(symbol=symbol1, side=trade_side1, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
                    # 再平次主力合约  
                    symbol2 = context.main_minor_contract[main_continue]['minor_contract']  
                    trade_side2 = OrderSide_Sell if trade_side1==OrderSide_Buy else OrderSide_Buy  
                    try:  
                        price_new = history(symbol=symbol2, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
                    except:  
                        print(symbol2,date)  
                        print(history(symbol=symbol2, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True))  
                        price_new = history(symbol=symbol2, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
                    order_volume(symbol=symbol2, side=trade_side2, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
                    break  
                if posi['symbol']==context.main_minor_contract[main_continue]['minor_contract'] and minor_contract!=context.main_minor_contract[main_continue]['minor_contract']:  
                    print('{}：次主力合约由{}替换为{}, 主力合约由{}替换为{}'.format(context.now,posi['symbol'],minor_contract,context.main_minor_contract[main_continue]['main_contract'],main_contract))  
                    # 先平次主力合约  
                    symbol2 = posi['symbol']  
                    side = posi['side']  
                    trade_side2 = OrderSide_Sell if posi['side']==PositionSide_Long else OrderSide_Buy  
                    price_new = history(symbol=symbol2, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
                    order_volume(symbol=symbol2, side=trade_side2, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
                    # 再平主力合约  
                    symbol1 = context.main_minor_contract[main_continue]['main_contract']  
                    trade_side1 = OrderSide_Sell if trade_side2==OrderSide_Buy else OrderSide_Buy  
                    price_new = history(symbol=symbol1, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)['open'].iloc[0]  
                    order_volume(symbol=symbol1, side=trade_side1, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=price_new)  
                    break  
        # 更新主力合约  
        context.main_minor_contract[main_continue] = {'main_contract':main_contract,'minor_contract':minor_contract}  
  
    # 获取历史数据  
    context.spread_history_mean = {}  
    context.open_price_diff = {}  
    context.upper = {}  
    context.lower = {}  
    context.stop_price_diff = {}  
    context.upper_stoppoint = {}  
    context.lower_stoppoint = {}  
    subs_list = []  
    for main_continue,contracts in context.main_minor_contract.items():  
        main_contract = contracts['main_contract']  
        minor_contract = contracts['minor_contract']  
        subs_list += [main_contract,minor_contract]  
        close_main = history_n(symbol=main_contract,frequency='1d', count=context.trade_parm[main_continue]['periods_time']+1, end_time=context.now, df=True)['close']  
        close_minor = history_n(symbol=minor_contract,frequency='1d', count=context.trade_parm[main_continue]['periods_time']+1, end_time=context.now, df=True)['close']  
        # 计算布林带  
        spread_history = close_main - close_minor# 历史价差  
        context.spread_history_mean[main_contract] = np.mean(spread_history)# 历史价差的均值  
        spread_history_std = np.std(spread_history)# 历史价差的标准差  
        context.open_price_diff[main_contract] = context.trade_parm[main_continue]['boll_multiple'] * spread_history_std# 开仓的价差  
        context.upper[main_contract] = context.spread_history_mean[main_contract] + context.open_price_diff[main_contract]# 布林带上轨  
        context.lower[main_contract] = context.spread_history_mean[main_contract] - context.open_price_diff[main_contract]# 布林带下轨  
  
        context.stop_price_diff[main_contract] = context.trade_parm[main_continue]['stoppoint_multiple'] * spread_history_std# 止损的价差  
        context.upper_stoppoint[main_contract] = context.spread_history_mean[main_contract] + context.stop_price_diff[main_contract]# 上轨止损线  
        context.lower_stoppoint[main_contract] = context.spread_history_mean[main_contract] - context.stop_price_diff[main_contract]# 下轨止损线  
    # 行情订阅  
    subscribe(symbols=subs_list, frequency=context.frequency, count=2, wait_group=True, unsubscribe_previous=True)  
  
  
def on_bar(context,bars):  
      
    for main_continue,contracts in context.main_minor_contract.items():  
        main_contract = contracts['main_contract']  
        minor_contract = contracts['minor_contract']  
        # 获取仓位# 指定持仓  
        position_long = context.account().position(symbol=main_contract,side = PositionSide_Long)        # 多头仓位  
        position_short = context.account().position(symbol=main_contract,side = PositionSide_Short)      # 空头仓位  
        if len(bars)<2:return  
        main_price,minor_price = 0,0  
        for bar in bars:  
            if bar['symbol']==main_contract:  
                main_price = bar['close']  
            if bar['symbol']==minor_contract:  
                minor_price = bar['close']  
        if main_price==0 or minor_price==0:return  
        spread_new = main_price-minor_price  
  
        # 没有仓位时  
        if not position_short and not position_long:  
            if spread_new>=context.upper[main_contract]:  
                context.trade_price_spread[main_contract] =spread_new  
                print('{}:做空价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,minor_contract,main_contract,spread_new))  
                order_volume(symbol=main_contract,side=OrderSide_Sell,volume=context.trade_num,order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
                order_volume(symbol=minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
            if spread_new<=context.lower[main_contract]:  
                context.trade_price_spread[main_contract] =spread_new  
                print('{}:做多价差组合(买入{}，卖出{}):{:.1f}'.format(context.now,main_contract,minor_contract,spread_new))  
                order_volume(symbol=main_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=main_price)  
                order_volume(symbol=minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Market, position_effect=PositionEffect_Open,price=minor_price)  
  
        # 设计平仓信号  
        # 做多价差组合时  
        if position_long:  
            # 价差回归到均值水平时或偏离达到止损位时，平仓  
            if spread_new >= context.spread_history_mean[main_contract] and spread_new>context.trade_price_spread[main_contract]+context.open_price_diff[main_contract]:  
                order_volume(symbol=main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
                order_volume(symbol=minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
                print('{}:止盈平仓（升至平均水平，买入{}，卖出{}）:{:.1f}'.format(context.now,minor_contract,main_contract,spread_new))  
            elif spread_new <= context.lower_stoppoint[main_contract] and spread_new<context.trade_price_spread[main_contract]-(context.stop_price_diff[main_contract]-context.open_price_diff[main_contract]):  
                order_volume(symbol=main_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
                order_volume(symbol=minor_contract, side=OrderSide_Buy, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
                print('{}:止损平仓（跌至止损线，买入{}，卖出{}）:{:.1f}'.format(context.now,minor_contract,main_contract,spread_new))  
  
        # 做空价差组合时  
        if position_short:  
            # 价差回归到均值水平时或偏离达到止损位时，平仓  
            try:  
                if spread_new <= context.spread_history_mean[main_contract] and spread_new<context.trade_price_spread[main_contract]-context.open_price_diff[main_contract]:  
                    order_volume(symbol=main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
                    order_volume(symbol=minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
                    print('{}:止盈平仓（跌至平均水平，买入{}，卖出{}）:{:.1f}'.format(context.now,main_contract,minor_contract,spread_new))  
                elif spread_new >= context.upper_stoppoint[main_contract] and spread_new>context.trade_price_spread[main_contract]+(context.stop_price_diff[main_contract]-context.open_price_diff[main_contract]):  
                    order_volume(symbol=main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
                    order_volume(symbol=minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
                    print('{}:止损平仓（升至止损线，买入{}，卖出{}）:{:.1f}'.format(context.now,main_contract,minor_contract,spread_new))  
            except:  
                print(context.main_minor_contract)  
                print(context.spread_history_mean)  
                print(context.spread_history_mean[main_contract])  
                print(context.trade_price_spread)  
                print(context.trade_price_spread[main_contract])  
                print(context.open_price_diff)  
                print(context.open_price_diff[main_contract])  
                if spread_new <= context.spread_history_mean[main_contract] and spread_new<context.trade_price_spread[main_contract]-context.open_price_diff[main_contract]:  
                    order_volume(symbol=main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
                    order_volume(symbol=minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
                    print('{}:止盈平仓（跌至平均水平，买入{}，卖出{}）:{:.1f}'.format(context.now,main_contract,minor_contract,spread_new))  
                elif spread_new >= context.upper_stoppoint[main_contract] and spread_new>context.trade_price_spread[main_contract]+(context.stop_price_diff[main_contract]-context.open_price_diff[main_contract]):  
                    order_volume(symbol=main_contract, side=OrderSide_Buy,volume=context.trade_num,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=main_price)  
                    order_volume(symbol=minor_contract, side=OrderSide_Sell, volume=context.trade_num, order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=minor_price)  
                    print('{}:止损平仓（升至止损线，买入{}，卖出{}）:{:.1f}'.format(context.now,main_contract,minor_contract,spread_new))  
  
  
def on_order_status(context, order):  
    # 标的代码  
    symbol = order['symbol']  
    # 委托价格  
    price = order['price']  
    # 委托数量  
    volume = order['volume']  
    # 目标仓位  
    target_percent = order['target_percent']  
    # 查看下单后的委托状态，等于3代表委托全部成交  
    status = order['status']  
    # 买卖方向，1为买入，2为卖出  
    side = order['side']  
    # 开平仓类型，1为开仓，2为平仓  
    effect = order['position_effect']  
    # 委托类型，1为限价委托，2为市价委托  
    order_type = order['order_type']  
    if status == 3:  
        if effect == 1:  
            if side == 1:  
                side_effect = '开多仓'  
            else:  
                side_effect = '开空仓'  
        else:  
            if side == 1:  
                side_effect = '平空仓'  
            else:  
                side_effect = '平多仓'  
        order_type_word = '限价' if order_type==1 else '市价'  
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))  
    elif status == 8:  
        print(order)  
         
  
def on_backtest_finished(context, indicator):  
    print('*'*50)  
    print('回测已完成，请通过右上角“回测历史”功能查询详情。')  
  
  
if __name__ == '__main__':  
    '''  
    strategy_id策略ID,由系统生成  
    filename文件名,请与本文件名保持一致  
    mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST  
    token绑定计算机的ID,可在系统设置-密钥管理中生成  
    backtest_start_time回测开始时间  
    backtest_end_time回测结束时间  
    backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST  
    backtest_initial_cash回测初始资金  
    backtest_commission_ratio回测佣金比例  
    backtest_slippage_ratio回测滑点比例  
    backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1  
    '''run(strategy_id='86390bd2-6b52-11ef-a083-f46b8c02346f',  
        filename='main.py',  
        mode=MODE_BACKTEST,  
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',  
        backtest_start_time='2021-01-01 21:00:00',  
        backtest_end_time='2024-09-24 15:00:00',  
        backtest_adjust=ADJUST_NONE,  
        backtest_initial_cash=500000,  
        backtest_commission_ratio=0.00012,  
        backtest_slippage_ratio=0.0000,  
        backtest_match_mode=1)

# Dual Thrust趋势跟踪策略
1、**策略说明**

Dual Thrust是一个趋势跟踪策略，当现价突破上轨时做多，当现价跌穿下轨时做空。

上轨：开盘价+K*波动

下轨：开盘价-K*波动

波动：max(HH - LC, HC - LL)

其中HH为N天最高价的最大值，LC为N天收盘价的最小值，HC为N天收盘价的最大值，LL为N天最低价的最小值


## coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *

import datetime
import numpy as np
import pandas as pd
import multiprocessing

"""
示例策略仅供参考，不建议直接实盘使用。

Dual Thrust是一个趋势跟踪策略，当现价突破上轨时做多，当现价跌穿下轨时做空。
上轨：开盘价+K*波动
下轨：开盘价-K*波动
波动：max(HH - LC, HC - LL)
其中HH为N天最高价的最大值，LC为N天收盘价的最小值，HC为N天收盘价的最大值，LL为N天最低价的最小值
"""


def init(context):
    # 设置要进行回测的合约（可以在掘金终端的仿真交易中查询标的代码）
    context.exchange = 'CFFEX'
    # context.symbol = 'SHFE.RB'  # 订阅&交易标的

    # 设置参数
    # context.N = 5
    # context.k1 = 0.2
    # context.k2 = 0.2
    # 数据一次性获取
    if context.mode==MODE_BACKTEST:
        contract_list = fut_get_continuous_contracts(csymbol=context.symbol, start_date=context.backtest_start_time[:10], end_date=context.backtest_end_time[:10])
        if len(contract_list)>0:
            context.contract_list = {}
            for dic in contract_list:
                context.contract_list[dic['trade_date']] = dic['symbol']
    # 设置定时任务：夜盘21点开始，日盘9点开始
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:30:00')


def algo(context):
    # 更换主力合约
    now_str = context.now.strftime('%Y-%m-%d')
    last_date = get_previous_n_trading_dates(exchange=context.exchange, date=now_str, n=1)[0]
    # 主力合约
    if context.mode==MODE_BACKTEST and now_str in context.contract_list:
        context.main_contract = context.contract_list[now_str]
    else:
        context.main_contract = fut_get_continuous_contracts(csymbol=context.symbol, start_date=now_str, end_date=now_str)[0]['symbol']
    # 有持仓时，检查持仓的合约是否为主力合约,非主力合约则卖出
    Account_positions = get_position()
    if Account_positions:
        for posi in Account_positions:
            if context.main_contract!=posi['symbol']:
                # 更换合约
                print('{}：持仓合约由{}替换为主力合约{}'.format(context.now,posi['symbol'],context.main_contract))
                new_price = history(symbol=posi['symbol'], frequency='1d', start_time=now_str,  end_time=now_str, adjust=ADJUST_NONE, df= True)['open'].iloc[0]
                order_target_volume(symbol=posi['symbol'], 
                                    volume=0, 
                                    position_side=posi['side'], 
                                    order_type=OrderType_Limit,
                                    price=new_price)

    # 订阅行情
    subscribe(context.main_contract, '60s', count=1, fields='symbol,eob,high,low,close', unsubscribe_previous=True,format='row')  
    # 取历史数据
    data = history_n(symbol=context.main_contract, frequency='1d', end_time=context.now,
                    fields='symbol,open,high,low,close', count=context.N + 1, df=True)
    # 取开盘价
    # 回测模式下，开盘价可以直接用history_n取到
    if context.mode == 2:
        current_open = data.open.iloc[-1]
    else:# 如果是实时模式，开盘价需要用current取到
        current_open = current(context.main_contract)[0]['open']

    # 先去掉当天的实时数据，再计算其他指标
    if len(data)>0:
        data = data.iloc[:-1,:]
    # 计算Dual Thrust 的上下轨
    HH = data['high'].max()
    HC = data['close'].max()
    LC = data['close'].min()
    LL = data['low'].min()
    range = max(HH - LC, HC - LL)
    context.buy_line = current_open + range * context.k1  # 上轨
    context.sell_line = current_open - range * context.k2  # 下轨


def on_bar(context, bars):
    # 取出订阅的这一分钟的bar
    bar = bars[0]

    # 获取现有持仓
    positions = get_position()
    position_long = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Long,positions))        # 多头仓位
    position_short = list(filter(lambda x:x['symbol']==context.main_contract and x['side']==PositionSide_Short,positions))      # 空头仓位

    # 交易逻辑部分
    # 如果最新价突破上轨
    if bar.close > context.buy_line:
        # 情况1：已经持有多仓，直接返回
        if position_long:  
            return
        # 情况2：已经持有空仓，先平空仓再开多仓（再开多仓的操作再on_order_status()中实现）
        elif position_short:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=bar.close)
        # 情况3：没有持仓时，直接开多仓            
        else:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Buy,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=bar.close)
    # 如果最新价跌破下轨
    elif bar.close < context.sell_line:
        # 情况1：已经持有空仓，直接返回:
        if position_short:  
            return
        # 情况2：已经持有多仓，先平多仓再开空仓（再开空仓的操作再on_order_status()中实现）
        elif position_long:  
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell,order_type=OrderType_Limit, position_effect=PositionEffect_Close,price=bar.close)
        # 情况3：没有持仓，直接开空仓
        else: 
            order_volume(symbol=context.main_contract, volume=1, side=OrderSide_Sell,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=bar.close)


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            else:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            else:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type==1 else '市价'
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))
        # 平仓后，接着开相反方向的仓位
        if effect==2:
            order_volume(symbol=context.main_contract, volume=1, side=side,order_type=OrderType_Limit, position_effect=PositionEffect_Open,price=price)


def on_backtest_finished(context, indicator):
    # 回测业绩指标数据
    data = [
        indicator['pnl_ratio'], indicator['pnl_ratio_annual'],indicator['sharp_ratio'],
        indicator['max_drawdown'], context.symbol,context.N,context.k1
    ]
    # 将超参加入context.result
    context.result.append(data)


def run_strategy(param1, param2, param3):
    # 导入上下文
    from gm.model.storage import context
    # 用context传入参数
    context.symbol = param1
    context.N = param2
    context.k1 = context.k2 = param3
    # context.result用以存储超参
    context.result = []
    '''
        strategy_id策略ID,由系统生成
        filename文件名,请与本文件名保持一致
        mode实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID,可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
    '''
    run(strategy_id='13b36d59-c11c-11ec-8bbc-f46b8c02346f',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        backtest_start_time='2022-01-01 08:00:00',
        # backtest_end_time='2024-08-31 23:59:59',
        backtest_end_time='2024-10-14 23:59:59',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0003,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)
    return context.result


if __name__ == '__main__':
    # 参数组合列表
    paras_list = []
    # 循环输入参数数值回测
    params1 = ['CFFEX.IF','CFFEX.IC','CFFEX.IH']
    params2 = range(4,5)# K值
    params3 = range(2,3)# K值
    a_list = []
    pool = multiprocessing.Pool(processes=len(params1), maxtasksperchild=1)  # create processes
    for x in params1:
        for y in params2:
            for z in params3:
                a_list.append(pool.apply_async(func=run_strategy, args=(x,y,z*0.1,)))
    pool.close()
    pool.join()
    print('运行结束！')

    info = []
    for pro in a_list:
        print('pro', pro.get()[0])
        info.append(pro.get()[0])
    print(info)
    info = pd.DataFrame(np.array(info), columns=['pnl_ratio', 'pnl_ratio_annual', 'sharp_ratio', 'max_drawdown', 'symbol','n','k'])
    info.to_csv('多进程参数记录2.csv', index=False)


# 低开高走翻红(选股)
from gm.api import *

import pandas as pd
import numpy as np
from datetime import datetime,timedelta


def init(context):
    # 设置参数
    context.N1 = -0.0  # 跌幅超过1%
    context.N2 = 0.005  # 涨幅超过2%
    # 定时任务，每分钟执行一次
    schedule(schedule_func=algo, date_rule='1d', time_rule='09:20:00')
    schedule(schedule_func=get_open_price, date_rule='1d', time_rule='09:29:00')


def get_open_price(context):
    """
    fun:自由竞价前定时获取开盘价
    """
    context.target_stocks = []
    if len(context.subscribe_stocks)>0:        
        ## 获取当日开盘价
        if context.mode == 2:# 如果是回测模式
            # 开盘价直接在data最后一个数据里取到,前一交易日的最高和最低价为history_data里面的倒数第二条中取到
            price_data = history(symbol=context.subscribe_stocks, frequency='1d', start_time=context.now,  end_time=context.now, fields='symbol,open', adjust=ADJUST_PREV, adjust_end_time=context.now,df= False)
        else:# 如果是实时模式
            # 开盘价通过current取到
            price_data = current(symbols=context.subscribe_stocks,fields='symbol,open')
        print(price_data)
        for data in price_data:
            context.monitor_data[data['symbol']]['today_open'] = data['open']
            # 过滤：开盘涨幅>=-2%
            open_rate = context.monitor_data[data['symbol']]['today_open']/context.monitor_data[data['symbol']]['pre_close']-1
            print(data,open_rate)
            if open_rate<context.N1:
                context.target_stocks.append(data['symbol'])
        
    # 订阅tick数据
    subscribe(symbols=context.target_stocks, frequency='60s', count=2, unsubscribe_previous=True)


def algo(context):
    date = context.now.strftime('%Y-%m-%d')               # 当天日期str
    yesterday = get_previous_n_trading_dates(exchange='SHSE', date=date, n=1)[0]
    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）
    all_stocks,all_stocks_str = get_normal_stocks(context, date,new_days=50)
    # 获取昨天收盘价
    # 合并监控的股票和持仓的股票
    context.subscribe_stocks = all_stocks[:5]###########
    # 记录数据
    if context.subscribe_stocks:
        dicts = get_symbols(sec_type1=1010, symbols=context.subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=date, df=False)
        pre_data_adjust = history(symbol=context.subscribe_stocks, frequency='1d', start_time=yesterday,  end_time=yesterday, adjust=ADJUST_PREV, adjust_end_time=date, df= True)
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1]} for dic in dicts}
    else:
        context.monitor_data = {}
    print(context.monitor_data)


def on_bar(context,bars):
    symbol = bars[0]['symbol']
    close_price = bars[0]['close']
    current_rate = close_price/context.monitor_data[symbol]['pre_close']-1
    if current_rate>context.N2:
        print('{}:{}满足全部条件'.format(context.now,symbol))


def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):
    """
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））
    :param date：目标日期
    :param new_days:新股上市天数，默认为365天
    :param skip_suspended:是否剔除停牌股，默认为True
    :param skip_st:是否剔除ST股，默认为True
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效
    """
    date = pd.Timestamp(date).replace(tzinfo=None)
    # A股，剔除停牌和ST股票
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)
    if len(stocks_info)>0:
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))
        # 剔除次新股和退市股
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]
        all_stocks = list(stocks_info['symbol'])
        # 剔除当日涨停股（收盘价==涨停价）
        if skip_upper_limit and context.mode==MODE_BACKTEST:
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)
            stocks_info = stocks_info.merge(low_price,on=['symbol'])
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()
    else:
        all_stocks = []
    all_stocks_str = ','.join(all_stocks)
    return all_stocks,all_stocks_str


if __name__ == '__main__':
    '''
        strategy_id策略ID, 由系统生成
        filename文件名, 请与本文件名保持一致
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID, 可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
        '''
    run(strategy_id='f808f9b9-c689-11ef-a11d-f46b8c02346f',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        backtest_start_time='2024-12-30 08:00:00',
        backtest_end_time='2024-12-30 16:00:00',
        backtest_adjust=ADJUST_PREV,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0001,
        backtest_slippage_ratio=0.0001,
        backtest_match_mode=1)

# 龙头首阴战法

## coding=utf-8
from __future__ import print_function, absolute_import
from gm.api import *

import numpy as np
import pandas as pd
from datetime import datetime,timedelta

"""
克隆自聚宽文章：https://www.joinquant.com/post/36382
感谢原作者：游资小码哥
"""

def init(context):
    # 变量初始化
    context.holding_data = {}            # 建仓时间和持仓以来的最高价
    
    # 定时任务
    schedule(schedule_func=algo_1, date_rule='1d', time_rule='09:20:00')
    schedule(schedule_func=get_open_price, date_rule='1d', time_rule='09:29:00')


def algo_1(context):    
    context.date = context.now.strftime('%Y-%m-%d')               # 当天日期str

    # 1、获取基础股票池（过滤ST、新股、停牌、退市整理期的股票）
    all_stocks,all_stocks_str = get_normal_stocks(context, context.date,new_days=1080)

    # 2、过滤北交所、科创板、创业板
    all_stocks = [code for code in all_stocks if code.startswith('SHSE.60') or code.startswith('SZSE.0')]

    # 前天涨停
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=context.date, n=35)
    pre2_date = date_list[-2]
    pre2_zt_stocks = get_zt_stock(all_stocks, pre2_date)
    # 筛选股票（昨天形态）
    pre1_date = date_list[-1]
    pre_price = history(symbol=pre2_zt_stocks, frequency='1d', start_time=pre1_date, end_time=pre1_date, fields=None, skip_suspended=True,
                    fill_missing=None, adjust=ADJUST_PREV, adjust_end_time=context.date, df=True)
    context.target_stocks = []
    # 情况1
    stocks1 = pre_price[(pre_price['close']<pre_price['high']*0.976)&(pre_price['close']>=pre_price['pre_close'])&(pre_price['open']==pre_price['high'])]['symbol'].tolist()
    # print(000,stocks1)
    if len(stocks1)>0:
        pre8_date = date_list[-8]
        close_price = history(symbol=stocks1, frequency='1d', start_time=pre8_date, end_time=pre1_date, fields=None, skip_suspended=True, fill_missing=None, adjust=ADJUST_NONE, adjust_end_time='', df=True)
        for code in stocks1:
            infos = get_history_symbol(symbol=code, start_date=pre8_date, end_date=pre1_date, df=True)
            code_price = close_price[close_price['symbol']==code]
            code_price = code_price.merge(infos,left_on=['eob'],right_on=['trade_date'])
            code_price['zt'] = code_price['close']==code_price['upper_limit']
            limit_times = continute_upper_limit_times(context,code,date_list[-3])# 连扳天数
            # print(111,code,limit_times,sum(code_price['zt']))###################################################################
            # print(code_price[['trade_date','close','upper_limit','zt']])
            if limit_times>=2 and sum(code_price['zt'])<=6 :
                context.target_stocks.append(code)
                # print(222,code,limit_times,sum(code_price['zt']), )###################################################################
                # print(code['sdf'])
            
    # 情况2
    context.pre2_pre1_zt_stocks = get_zt_stock(pre2_zt_stocks, pre1_date)
    stocks2 = pre_price[(pre_price['open']<pre_price['high']*0.95)&(pre_price['open']>pre_price['pre_close']*0.986)&(pre_price['low']<pre_price['pre_close']*0.92)]['symbol'].tolist()
    stocks2 = list(set(stocks2)&set(context.pre2_pre1_zt_stocks)-set(stocks1))
    if len(stocks2)>0:
        pre35_date = date_list[-35]
        close_price = history(symbol=stocks2, frequency='1d', start_time=pre35_date, end_time=pre1_date, fields=None, skip_suspended=True, fill_missing=None, adjust=ADJUST_NONE, adjust_end_time='', df=True)
        for code in stocks2:
            infos = get_history_symbol(symbol=code, start_date=pre35_date, end_date=pre1_date, df=True)
            code_price = close_price[close_price['symbol']==code]
            code_price = code_price.merge(infos,left_on=['eob'],right_on=['trade_date'])
            code_price['zt'] = code_price['close']==code_price['upper_limit']
            if sum(code_price['zt'].iloc[-8:])<=5 and sum(code_price['zt'].iloc[-30:-5])<=4:
                context.target_stocks.append(code)
                # print(222,code,sum(code_price['zt'].iloc[-8:]), sum(code_price['zt'].iloc[-30:-5]))################################################################
    # 汇总
    print('{}:今日选出股票池：{}'.format(context.now,context.target_stocks))

    # 记录数据
    context.holding_stocks =  [posi['symbol'] for posi in get_position()] 
    context.subscribe_stocks = list(set(context.target_stocks)|set(context.holding_stocks))
    if context.subscribe_stocks:
        dicts = get_symbols(sec_type1=1010, symbols=context.subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=context.date, df=False)
        pre_dicts = get_symbols(sec_type1=1010, symbols=context.subscribe_stocks, skip_suspended=False, skip_st=False, trade_date=pre1_date, df=False)
        pre_data_adjust = history(symbol=context.subscribe_stocks, frequency='1d', start_time=pre1_date,  end_time=pre1_date, adjust=ADJUST_PREV, adjust_end_time=context.date, df= True)
        context.monitor_data = {dic['symbol']:{'upper_limit':dic['upper_limit'], 'lower_limit':dic['lower_limit'],
                                'pre_close':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['close'].iloc[-1],
                                'pre_volume':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['volume'].iloc[-1],
                                'pre_low':pre_data_adjust[pre_data_adjust['symbol'].isin([dic['symbol']])]['low'].iloc[-1]} for dic in dicts}
        for dic in pre_dicts:
            context.monitor_data[dic['symbol']]['pre_upper_limit'] = dic['upper_limit']
    else:
        context.monitor_data = {}

    # 持仓股的30天涨停数
    if context.holding_stocks:
        context.zt_in_30days = {code:0 for code in context.holding_stocks}
        start_date =  (context.now - timedelta(days = 30)).strftime("%Y-%m-%d")
        new_date_list = [date for date in date_list if date>start_date]
        for date in new_date_list:
            zt_stocks_ = get_zt_stock(context.holding_stocks, date)
            for code in zt_stocks_:
                context.zt_in_30days[code] += 1

        # 连扳天数
        context.lb_times = {code:continute_upper_limit_times(context,code,date_list[-3]) for code in context.holding_stocks}

    # 订阅tick数据
    subscribe(symbols=context.subscribe_stocks, frequency='60s', count=2, unsubscribe_previous=True)


def get_open_price(context):
    """
    fun:自由竞价前定时获取开盘价和昨收价
    """
    context.open_price = {}
    if len(context.subscribe_stocks)>0:        
        ## 获取当日开盘价
        if context.mode == 2:# 如果是回测模式
            # 开盘价直接在data最后一个数据里取到,前一交易日的最高和最低价为history_data里面的倒数第二条中取到
            price_data = history(symbol=context.subscribe_stocks, frequency='1d', start_time=context.now,  end_time=context.now, fields='symbol,open', adjust=ADJUST_PREV, adjust_end_time=context.now,df= False)
        else:# 如果是实时模式
            # 开盘价通过current取到
            price_data = current(symbols=context.subscribe_stocks,fields='symbol,open')
        for data in price_data:
            context.monitor_data[data['symbol']]['today_open'] = data['open']
            context.monitor_data[data['symbol']]['today_high'] = data['open']
            context.monitor_data[data['symbol']]['today_low'] = data['open']


def continute_upper_limit_times(context,stock,date):
    date_list = get_previous_n_trading_dates(exchange='SHSE', date=date, n=30)
    date_list.append(date)
    limit_times = 0
    for date_ in date_list[::-1]:
        zt_stocks_ = get_zt_stock(stock, date_)
        if stock in zt_stocks_:
            limit_times += 1
        else:
            break
    
    # 250个交易日内新高
    yesterday = get_previous_n_trading_dates(exchange='SHSE', date=context.now.strftime('%Y-%m-%d'), n=1)[-1]
    close_price_all = history_n(symbol=stock, frequency='1d', count=250+limit_times, end_time=yesterday, fields=None, skip_suspended=True,
          fill_missing=None, adjust=ADJUST_PREV, adjust_end_time=date, df=True)
    if close_price_all['high'].iloc[-1]<max(close_price_all['high'].iloc[:-(2+limit_times)]):
        # print(11,close_price_all['high'].iloc[-1],max(close_price_all['high'].iloc[:-(2+limit_times)]))
        limit_times = 0
    # 30天内涨停数不超过4个
    end_time = get_previous_n_trading_dates(exchange='SHSE', date=date, n=limit_times)[0] if limit_times!=0 else date
    close_price = history_n(symbol=stock, frequency='1d', count=30, end_time=end_time, fields=None, skip_suspended=True,
          fill_missing=None, adjust=ADJUST_NONE, adjust_end_time='', df=True)
    start_date = close_price['eob'].iloc[0].strftime('%Y-%m-%d')
    symbol_info = get_history_symbol(symbol=stock, start_date=start_date, end_date=date, df=True)
    close_price  = close_price.merge(symbol_info,left_on=['eob'],right_on=['trade_date'])
    close_price['zt'] = close_price['upper_limit']==close_price['close']
    # print(22,date,sum(close_price['zt']))
    # print(close_price[['trade_date','close','upper_limit','zt']])
    if sum(close_price['zt'])>4:
        # print(22,date,sum(close_price['zt']))
        # print(close_price[['trade_date','close','upper_limit','zt']])
        limit_times = 0
    # 上一个交易日不涨停
    pre_end_time = get_previous_n_trading_dates(exchange='SHSE', date=end_time, n=1)[0]
    pre_close_price = history_n(symbol=stock, frequency='1d', count=1, end_time=pre_end_time, fields=None, skip_suspended=True,
          fill_missing=None, adjust=ADJUST_NONE, adjust_end_time='', df=False)
    pre_symbol_info = get_history_symbol(symbol=stock, start_date=pre_end_time, end_date=pre_end_time, df=False)
    if pre_close_price[0]['close']==pre_symbol_info[0]['upper_limit']:
        limit_times = 0
    return limit_times    


def on_bar(context,bars):
    symbol = bars[0]['symbol']
    open_price = bars[0]['open']
    close_price = bars[0]['close']
    eob = bars[0]['eob']
    today_open = context.monitor_data[symbol]['today_open']
    pre_close = context.monitor_data[symbol]['pre_close']
    upper_limit = context.monitor_data[symbol]['upper_limit']
    lower_limit = context.monitor_data[symbol]['lower_limit']
    context.monitor_data[symbol]['today_high'] = max(context.monitor_data[symbol]['today_high'],bars[0]['high'])
    context.monitor_data[symbol]['today_low'] = min(context.monitor_data[symbol]['today_low'],bars[0]['low'])
    
    # # 指定持仓
    Account_position = context.account().position(symbol=symbol,side = PositionSide_Long)
    # 所有持仓
    Account_positions = context.account().positions()
    if Account_position:    
        available_now = Account_position['volume']-Account_position['volume_today'] if context.mode==MODE_BACKTEST else Account_position['available_now']
        if available_now>0:
            # 更新持仓最高价
            context.holding_data[symbol]['持仓最高价'] = max(context.holding_data[symbol]['持仓最高价'],close_price)
            # 更新持仓最低价
            context.holding_data[symbol]['持仓最低价'] = min(context.holding_data[symbol]['持仓最低价'],close_price)
            # 尾盘
            if eob.time()>datetime.strptime('14:40:00', '%H:%M:%S').time():
                # 卖出情况1
                if close_price<context.holding_data[symbol]['持仓最高价']*0.869 and today_open>pre_close and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:1.卖出股票{}：小于最高价0.869倍".format(context.now,symbol))
                # 卖出情况2
                elif close_price<Account_position['vwap']*0.92 and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:2.卖出股票{}：亏8个点".format(context.now,symbol))
                # 卖出情况3
                elif close_price<Account_position['vwap']*1.2 and close_price<context.holding_data[symbol]['持仓最高价']*0.92 and today_open<context.holding_data[symbol]['持仓最高价']*0.968 and close_price<today_open and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:3.卖出股票{}：1.2以下".format(context.now,symbol))
                # 卖出情况4
                elif context.holding_data[symbol]['持仓最高价']/context.holding_data[symbol]['持仓最低价']<0.86 and close_price<Account_position['vwap']*1.2 and context.holding_data[symbol]['持仓最低价']<pre_close*0.95 and close_price<today_open and close_price<pre_close and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:4.炸板卖出股票{}".format(context.now,symbol))
                # 卖出情况4
                elif close_price>Account_position['vwap']*1.3 and close_price<context.holding_data[symbol]['持仓最高价']*0.95 and close_price>context.holding_data[symbol]['持仓最低价']*1.05 and symbol in context.pre2_pre1_zt_stocks and close_price>pre_close and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:4.·炸板卖出股票{}".format(context.now,symbol)) 
            else:
                # 卖出情况5
                if context.zt_in_30days[symbol]>=12 and close_price<context.holding_data[symbol]['持仓最高价']*0.95 and today_open>pre_close and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:5.卖出股票{}：12个板以上,{},{},{},{},{},{},{},{}".format(context.now,symbol,context.zt_in_30days[symbol], close_price<context.holding_data[symbol]['持仓最高价']*0.95, today_open>pre_close,context.zt_in_30days[symbol], close_price, context.holding_data[symbol]['持仓最高价'], today_open, pre_close)) 
                # 卖出情况6
                elif close_price<Account_position['vwap']*0.92 and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:6.卖出股票{}：亏8个点".format(context.now,symbol)) 
                # 卖出情况7
                elif context.lb_times[symbol]>5 and today_open<close_price*1.01 and close_price>1.3*Account_position['vwap'] and today_open<close_price*0.95 and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:7.卖出股票{}：涨停超过5次，只有后面低于前一交易日就卖".format(context.now,symbol)) 
                # 卖出情况8
                elif today_open==context.monitor_data[symbol]['upper_limit'] and close_price<pre_close and close_price>lower_limit:
                    sell_algo(context,symbol,available_now)
                    print("{}:8.卖出股票{}：高位放量，请走{},{},{},{},{},{},".format(context.now,symbol,today_open==context.monitor_data[symbol]['today_high'], close_price<pre_close,today_open,context.monitor_data[symbol]['today_high'], close_price,pre_close)) 
            
    
    # 买入情况1
    elif symbol in context.target_stocks and len(Account_positions)==0 and close_price>context.monitor_data[symbol]['pre_upper_limit']*0.99 and close_price>pre_close*1.03 and \
        today_open>pre_close*1.07 and pre_close<context.monitor_data[symbol]['pre_upper_limit']:
        # 聚宽多了个09：30的数据
        if (eob.time()==datetime.strptime('09:31:00', '%H:%M:%S').time() and (open_price<upper_limit or close_price<upper_limit)) or close_price<upper_limit:
            buy_algo(context,symbol,close_price)
            print("{}：1.买入股票{}".format(context.now,symbol))

    # 买入情况2
    elif symbol in context.target_stocks and len(Account_positions)==0 and close_price>context.monitor_data[symbol]['pre_upper_limit']*0.99 and eob.time()>datetime.strptime('09:40:00', '%H:%M:%S').time() and \
        eob.time()<datetime.strptime('11:20:00', '%H:%M:%S').time() and close_price>today_open and \
            close_price>pre_close*1.03 and today_open>context.monitor_data[symbol]['pre_low'] and pre_close<context.monitor_data[symbol]['pre_upper_limit']:
        # 聚宽多了个09：30的数据
        if (eob.time()==datetime.strptime('09:31:00', '%H:%M:%S').time() and (open_price<upper_limit or close_price<upper_limit)) or close_price<upper_limit:
            buy_algo(context,symbol,close_price)
            print("{}：2.买入股票{}".format(context.now,symbol))

    # 买入情况3
    elif symbol in context.target_stocks and len(Account_positions)==0 and pre_close==context.monitor_data[symbol]['pre_upper_limit'] and close_price>context.monitor_data[symbol]['pre_upper_limit']*1.06 and \
        today_open>context.monitor_data[symbol]['pre_upper_limit']*0.95 and eob.time()<datetime.strptime('11:20:00', '%H:%M:%S').time():
        # 聚宽多了个09：30的数据
        if (eob.time()==datetime.strptime('09:31:00', '%H:%M:%S').time() and (open_price<upper_limit or close_price<upper_limit)) or close_price<upper_limit:
            buy_algo(context,symbol,close_price)
            print("{}：3.买入股票{}".format(context.now,symbol))


def sell_algo(context,symbol,volume):
    order_volume(symbol=symbol, volume=volume, side=OrderSide_Sell, order_type=OrderType_Market, price=context.monitor_data[symbol]['lower_limit'], position_effect=PositionEffect_Close)
        

def buy_algo(context,symbol,price):
    percent = 1
    cash = context.account().cash
    volume = cal_stock_buy_volume(symbol,cash['nav']*percent,price)
    if volume>0:
        order_volume(symbol=symbol, volume=volume, side=OrderSide_Buy, order_type=OrderType_Market, price=context.monitor_data[symbol]['upper_limit'], position_effect=PositionEffect_Open)
        context.holding_data[symbol] = {'建仓时间':context.now,'持仓最高价':price,'持仓最低价':price}


def cal_stock_buy_volume(code,amount,price):
    """计算股票下单数量"""
    Account_cash = get_cash()# 获取账户资金信息
    available_amount = min(amount,Account_cash['available'])                 
    trade_volume = max(int(np.floor(available_amount/price/100)*100),200) if code.startswith('SHSE.68') else max(int(np.floor(available_amount/price/100)*100),100)
    return trade_volume


def get_zt_stock(stock_list, date, return_data=False):
    """筛选出某一日涨停的股票"""
    history_data = history(symbol=stock_list, frequency='1d', start_time=date,  end_time=date, adjust=ADJUST_NONE, df= True)
    symbols_info = get_symbols(sec_type1=1010, symbols=stock_list, skip_suspended=False, skip_st=False, trade_date=date, df=True)
    if len(history_data)==0 or len(symbols_info)==0:
        zt_stock = []
    else:
        history_data = history_data.merge(symbols_info, on=['symbol'])
        zt_stock = history_data[(history_data['close']==history_data['upper_limit'])]['symbol'].tolist()

    if return_data:
        return zt_stock,history_data
    else:
        return zt_stock
        

def get_normal_stocks(context, date,new_days=365,skip_suspended=True, skip_st=True, skip_upper_limit=False):
    """
    获取目标日期date的A股代码（剔除停牌股、ST股、次新股（365天））
    :param date：目标日期
    :param new_days:新股上市天数，默认为365天
    :param skip_suspended:是否剔除停牌股，默认为True
    :param skip_st:是否剔除ST股，默认为True
    :param skip_upper_limit:是否剔除一字板股票（最低价==涨停价），默认为True,仅在回测中生效
    """
    date = pd.Timestamp(date).replace(tzinfo=None)
    # A股，剔除停牌和ST股票
    stocks_info = get_symbols(sec_type1=1010, sec_type2=101001, skip_suspended=skip_suspended, skip_st=skip_st, trade_date=date.strftime('%Y-%m-%d'), df=True)
    if len(stocks_info)>0:
        stocks_info['listed_date'] = stocks_info['listed_date'].apply(lambda x:x.replace(tzinfo=None))
        stocks_info['delisted_date'] = stocks_info['delisted_date'].apply(lambda x:x.replace(tzinfo=None))
        # 剔除次新股和退市股
        stocks_info = stocks_info[(stocks_info['listed_date']<=date-timedelta(days=new_days))&(stocks_info['delisted_date']>date)&(~stocks_info['sec_name'].str.startswith('退市'))&(~stocks_info['sec_name'].str.endswith('退'))]
        all_stocks = list(stocks_info['symbol'])
        # 剔除当日涨停股（收盘价==涨停价）
        if skip_upper_limit and context.mode==MODE_BACKTEST:
            low_price = history(symbol=all_stocks, frequency='1d', start_time=date,  end_time=date, fields='low,symbol', adjust=ADJUST_NONE, df= True)
            stocks_info = stocks_info.merge(low_price,on=['symbol'])
            all_stocks = stocks_info[stocks_info['low']!=stocks_info['upper_limit']]['symbol'].tolist()
    else:
        all_stocks = []
    all_stocks_str = ','.join(all_stocks)
    return all_stocks,all_stocks_str


def on_order_status(context, order):
    # 标的代码
    symbol = order['symbol']
    # 委托价格
    price = order['price']
    # 委托数量
    volume = order['volume']
    # 目标仓位
    target_percent = order['target_percent']
    # 查看下单后的委托状态，等于3代表委托全部成交
    status = order['status']
    # 买卖方向，1为买入，2为卖出
    side = order['side']
    # 开平仓类型，1为开仓，2为平仓
    effect = order['position_effect']
    # 委托类型，1为限价委托，2为市价委托
    order_type = order['order_type']
    if status == 3:
        if effect == 1:
            if side == 1:
                side_effect = '开多仓'
            else:
                side_effect = '开空仓'
        else:
            if side == 1:
                side_effect = '平空仓'
            else:
                side_effect = '平多仓'
        order_type_word = '限价' if order_type==1 else '市价'
        print('{}:标的：{}，操作：以{}{}，委托价格：{}，委托数量：{}'.format(context.now,symbol,order_type_word,side_effect,price,volume))
    elif status == 8:
        print('{}:拒绝委托：{}'.format(context.now,order))
       

if __name__ == '__main__':
    '''
        strategy_id策略ID, 由系统生成
        filename文件名, 请与本文件名保持一致
        mode运行模式, 实时模式:MODE_LIVE回测模式:MODE_BACKTEST
        token绑定计算机的ID, 可在系统设置-密钥管理中生成
        backtest_start_time回测开始时间
        backtest_end_time回测结束时间
        backtest_adjust股票复权方式, 不复权:ADJUST_NONE前复权:ADJUST_PREV后复权:ADJUST_POST
        backtest_initial_cash回测初始资金
        backtest_commission_ratio回测佣金比例
        backtest_slippage_ratio回测滑点比例
        backtest_match_mode市价撮合模式，以下一tick/bar开盘价撮合:0，以当前tick/bar收盘价撮合：1
        '''
    run(strategy_id='38c012cf-c658-11ef-a11d-f46b8c02346f',
        filename='main.py',
        mode=MODE_BACKTEST,
        token='47ca47f849b3a0f66ec0f7013bb56bb667d63a70',
        # backtest_start_time='2019-01-01 08:00:00',
        backtest_start_time='2020-09-01 08:00:00',
        backtest_end_time='2022-01-14 16:00:00',
        backtest_adjust=ADJUST_NONE,
        backtest_initial_cash=10000000,
        backtest_commission_ratio=0.0008,
        backtest_slippage_ratio=0.00123,
        backtest_match_mode=1)

