Warning: file_exists(): open_basedir restriction in effect. File(/www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/wp-content/db.php) is not within the allowed path(s): (/www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/:/tmp/:/proc/:/var/log/nginx/:/www/wwwroot/com.xiximiao.oa/com.cmstop/public/:/www/wwwroot/com.xiximiao.oa/com.cmstop/vendor/:/www/wwwroot/com.xiximiao.oa/com.cmstop/ppk/) in /www/wwwroot/com.xiximiao.oa/com.cmstop/public/www/wp-includes/load.php on line 707
2412 多因子复合策略组合 子账户实现策略分层管理 用子账户模拟多策略分仓 47344 量化自动化交易策略 » 轻知量化 QMT、PTrade、聚宽策略分享交流平台

2412 多因子复合策略组合 子账户实现策略分层管理 用子账户模拟多策略分仓 47344 量化自动化交易策略

策略为多因子复合策略组合,通过四个独立子账户实现策略分层管理,核心架构如下:
  1. 策略分层结构
  • ETF 轮动策略(20% 仓位):动量模型选取上证 180 / 创业板 / 纳指 / 黄金 ETF,结合 RSRS 择时模型进行趋势跟踪
  • PB 策略(20% 仓位):筛选市净率 <0.98、ROA>15% 的低估值个股,侧重财务健康度
  • 小市值策略(30% 仓位):多因子模型(ARBR 情绪 + SGAI 效率 + 未分配利润)选取超小盘股,市值排序前 0.1%
  • 菜场大妈策略(30% 仓位):股息率前 25%+ 股价 < 9 元的低估值组合,侧重分红能力
  1. 动态风控体系
  • 分层止损:个股回撤超 20% 触发子账户止损,静默期 20 天
  • 特殊时期规避:小市值策略 4 月强制空仓(规避财报风险)
  • 涨停板管理:昨日涨停股破板即触发卖出
  • 解禁股过滤:排除未来 120 天解禁超 20% 的个股
  1. 交易执行优化
  • 差异化调仓频率:ETF 日频调仓,PB / 小市值周频,大妈策略月频
  • 订单智能路由:买入使用溢价 0.99% 市价单,卖出使用折价 1.01% 保护单
  • 组合再平衡:每月初根据 25% 波动率调整子账户资金配比
  1. 数据处理创新
  • 小市值因子正交:通过三组因子正交处理消除共线性
  • 股息率动态计算:滚动 365 天分红总额 / 市值,实时更新股息排名
  • RSRS 择时优化:引入长周期动量反转因子降低误判率

# 导入函数库
from jqdata import *
from jqfactor import get_factor_values
import datetime


# 初始化函数,设定基准等等
def initialize(context):
    # 设定沪深300作为基准
    set_benchmark('000300.XSHG')
    # 开启动态复权模式(真实价格)
    set_option('use_real_price', True)
    # 输出内容到日志 log.info()
    log.info('初始函数开始运行且全局只运行一次')
    # 过滤掉order系列API产生的比error级别低的log
    log.set_level('order', 'error')
    
    set_order_cost(OrderCost(close_tax=0.001, open_commission=0.0003, close_commission=0.0003, min_commission=5), type='stock')
    set_slippage(PriceRelatedSlippage(0.01), type='stock')

    # 临时变量
    
    # 持久变量
    g.strategys = {}
    g.portfolio_value_proportion = [0.2,0.2,0.3,0.3]
    
    # 创建策略实例
    set_subportfolios([
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[0], 'stock'), 
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[1], 'stock'),
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[2], 'stock'),
        SubPortfolioConfig(context.portfolio.starting_cash*g.portfolio_value_proportion[3], 'stock'),
    ])
    
    params = {
        'max_hold_count': 1,    # 最大持股数
        'max_select_count': 1,  # 最大输出选股数
    }
    etf_strategy = ETF_Strategy(context, subportfolio_index=0, name='ETF轮动策略', params=params)
    g.strategys[etf_strategy.name] = etf_strategy
    
    params = {
        'max_hold_count': 1,        # 最大持股数
        'max_select_count': 3,      # 最大输出选股数
    }
    pb_strategy = PB_Strategy(context, subportfolio_index=1, name='PB策略', params=params)
    g.strategys[pb_strategy.name] = pb_strategy
    
    params = {
        'max_hold_count': 3,        # 最大持股数
        'max_select_count': 5,      # 最大输出选股数
        'use_empty_month': True,    # 是否在指定月份空仓
        'empty_month': [4],         # 指定空仓的月份列表
        'use_stoplost': True,       # 是否使用止损
    }
    xsz_strategy = XSZ_Strategy(context, subportfolio_index=2, name='小市值策略', params=params)
    g.strategys[xsz_strategy.name] = xsz_strategy

    params = {
        'max_hold_count': 1,        # 最大持股数
        'max_select_count': 3,      # 最大输出选股数
        # 'use_empty_month': True,    # 是否在指定月份空仓
        # 'empty_month': [4],         # 指定空仓的月份列表
        'use_stoplost': True,       # 是否使用止损
    }
    dama_strategy = DaMa_Strategy(context, subportfolio_index=3, name='菜场大妈策略', params=params)
    g.strategys[dama_strategy.name] = dama_strategy

    # 执行计划
    if g.portfolio_value_proportion[0] > 0:
        run_daily(etf_select, '7:40') 
        run_daily(etf_adjust, '10:00')
    if g.portfolio_value_proportion[1] > 0:
        run_daily(pb_day_prepare, time='7:30')
        run_monthly(pb_select, 1, time='7:40')
        run_daily(pb_open_market, time='9:30')
        run_monthly(pb_adjust, 1, time='9:35')
        run_daily(pb_sell_when_highlimit_open, time='14:00')
        run_daily(pb_sell_when_highlimit_open, time='14:50')
    if g.portfolio_value_proportion[2] > 0:
        run_daily(xsz_day_prepare, time='7:30')
        run_weekly(xsz_select, 1, time='7:40')
        run_daily(xsz_open_market, time='9:30')
        run_weekly(xsz_adjust, 1, time='9:35')
        run_daily(xsz_sell_when_highlimit_open, time='14:00')
        run_daily(xsz_sell_when_highlimit_open, time='14:50')
    if g.portfolio_value_proportion[3] > 0:
        run_daily(dama_day_prepare, time='7:30')
        run_monthly(dama_select, 15, time='7:40')
        run_daily(dama_open_market, time='9:30')
        run_monthly(dama_adjust, 15, time='10:30')
        run_daily(dama_sell_when_highlimit_open, time='14:00')
        run_daily(dama_sell_when_highlimit_open, time='14:50')
        
    # run_daily(print_trade_info, time='15:01')


def etf_select(context):
    g.strategys['ETF轮动策略'].select(context)

def etf_adjust(context):
    g.strategys['ETF轮动策略'].adjust(context)


def pb_day_prepare(context):
    g.strategys['PB策略'].day_prepare(context)

def pb_select(context):
    g.strategys['PB策略'].select(context)
        
def pb_adjust(context):
    g.strategys['PB策略'].adjust(context)

def pb_open_market(context):
    g.strategys['PB策略'].close_for_stoplost(context)

def pb_sell_when_highlimit_open(context):
    g.strategys['PB策略'].sell_when_highlimit_open(context)


def xsz_day_prepare(context):
    g.strategys['小市值策略'].day_prepare(context)

def xsz_select(context):
    g.strategys['小市值策略'].select(context)

def xsz_adjust(context):
    g.strategys['小市值策略'].adjust(context)

def xsz_open_market(context):
    g.strategys['小市值策略'].close_for_empty_month(context)
    g.strategys['小市值策略'].close_for_stoplost(context)

def xsz_sell_when_highlimit_open(context):
    g.strategys['小市值策略'].sell_when_highlimit_open(context)


def dama_day_prepare(context):
    g.strategys['菜场大妈策略'].day_prepare(context)

def dama_select(context):
    g.strategys['菜场大妈策略'].select(context)

def dama_adjust(context):
    g.strategys['菜场大妈策略'].adjust(context)

def dama_open_market(context):
    g.strategys['菜场大妈策略'].close_for_empty_month(context)
    g.strategys['菜场大妈策略'].close_for_stoplost(context)

def dama_sell_when_highlimit_open(context):
    g.strategys['菜场大妈策略'].sell_when_highlimit_open(context)
    
    
# 打印交易记录
def print_trade_info(context):
    orders = get_orders()
    for _order in orders.values():
        print('成交记录:'+str(_order))
        

# 策略基类
# 同一只股票只买入1次,卖出时全部卖出
class Strategy:
    def __init__(self, context, subportfolio_index, name, params):
        self.subportfolio_index = subportfolio_index
        # self.subportfolio = context.subportfolios[subportfolio_index]
        self.name = name
        self.params = params
        self.max_hold_count = self.params['max_hold_count'] if 'max_hold_count' in self.params else 1                       # 最大持股数
        self.max_select_count = self.params['max_select_count'] if 'max_select_count' in self.params else 5                 # 最大输出选股数
        self.hold_limit_days = self.params['hold_limit_days'] if 'hold_limit_days' in self.params else 20                   # 计算最近持有列表的天数
        self.use_empty_month = self.params['use_empty_month'] if 'use_empty_month' in self.params else False                # 是否有空仓期
        self.empty_month = self.params['empty_month'] if 'empty_month' in self.params else []                               # 空仓月份
        self.use_stoplost = self.params['use_stoplost'] if 'use_stoplost' in self.params else False                         # 是否使用止损
        self.stoplost_silent_days = self.params['stoplost_silent_days'] if 'stoplost_silent_days' in self.params else 20    # 止损后不交易的天数
        self.stoplost_level = self.params['stoplost_level'] if 'stoplost_level' in self.params else 0.2                     # 止损的下跌幅度(按买入价)

        self.select_list = []
        self.hold_list = []                 # 昨收持仓
        self.history_hold_list = []         # 最近持有列表
        self.not_buy_again_list = []        # 最近持有不再购买列表
        self.yestoday_high_limit_list = []  # 昨日涨停列表
        self.stoplost_date = None           # 止损日期,为None是表示未进入止损


    def day_prepare(self, context):
        subportfolio = context.subportfolios[self.subportfolio_index]
        
        # 获取昨日持股列表
        self.hold_list = list(subportfolio.long_positions)
        
        # 获取最近一段时间持有过的股票列表
        self.history_hold_list.append(self.hold_list)
        if len(self.history_hold_list) >= self.hold_limit_days:
            self.history_hold_list = self.history_hold_list[-self.hold_limit_days:]
        temp_set = set()
        for lists in self.history_hold_list:
            for stock in lists:
                temp_set.add(stock)
        self.not_buy_again_list = list(temp_set)
        
        # 获取昨日持股涨停列表
        if self.hold_list != []:
            df = get_price(self.hold_list, end_date=context.previous_date, frequency='daily', fields=['close','high_limit'], count=1, panel=False, fill_paused=False)
            df = df[df['close'] == df['high_limit']]
            self.yestoday_high_limit_list = list(df.code)
        else:
            self.yestoday_high_limit_list = []
        
        # 检查空仓期
        self.check_empty_month(context)
        # 检查止损
        self.check_stoplost(context)
        
    
    # 基础股票池
    def stockpool(self, context, pool_id=1):
        lists = list(get_all_securities(types=['stock'], date=context.previous_date).index)
        if pool_id ==0:
            pass
        elif pool_id == 1:
            lists = self.filter_kcbj_stock(lists)
            lists = self.filter_st_stock(lists)
            lists = self.filter_paused_stock(lists)
            lists = self.filter_highlimit_stock(context, lists)
            lists = self.filter_lowlimit_stock(context, lists)
            
        return lists
        
    
    # 选股
    def select(self, context):
        # 空仓期控制
        if self.use_empty_month and context.current_dt.month in (self.empty_month):
            return
        # 止损期控制
        if self.stoplost_date is not None:
            return
        select.select_list = []
    
    
    # 打印交易计划
    def print_trade_plan(self, context, select_list):
        subportfolio = context.subportfolios[self.subportfolio_index]
        current_data = get_current_data()   # 取股票名称
    
        content = context.current_dt.date().strftime("%Y-%m-%d") + ' ' + self.name + " 交易计划:" + "\n"

        for stock in subportfolio.long_positions:
            if stock not in select_list[:self.max_hold_count]:
                content = content + stock + ' ' + current_data[stock].name + ' 卖出\n'

        for stock in select_list:
            if stock not in subportfolio.long_positions and stock in select_list[:self.max_hold_count]:
                content = content + stock + ' ' + current_data[stock].name + ' 买入\n'
            elif stock in subportfolio.long_positions and stock in select_list[:self.max_hold_count]:
                content = content + stock + ' ' + current_data[stock].name + ' 继续持有\n'
            else:
                content = content + stock + ' ' + current_data[stock].name + '\n'

        if ('买' in content) or ('卖' in content):
            print(content)


    # 调仓
    def adjust(self, context):
        # 空仓期控制
        if self.use_empty_month and context.current_dt.month in (self.empty_month):
            return
        # 止损期控制
        if self.stoplost_date is not None:
            return
        
        # 先卖后买
        hold_list = list(context.subportfolios[self.subportfolio_index].long_positions)
        sell_stocks = []
        for stock in hold_list:
            if stock not in self.select_list[:self.max_hold_count]:
                sell_stocks.append(stock)
        self.sell(context, sell_stocks)
        self.buy(context, self.select_list)


    # 涨停打开卖出
    def sell_when_highlimit_open(self, context):
        if self.yestoday_high_limit_list != []:
            for stock in self.yestoday_high_limit_list:
                if stock in context.subportfolios[self.subportfolio_index].long_positions:
                    current_data = get_price(stock, end_date=context.current_dt, frequency='1m', fields=['close','high_limit'], 
                        skip_paused=False, fq='pre', count=1, panel=False, fill_paused=True)
                    if current_data.iloc[0,0] < current_data.iloc[0,1]:
                        self.sell(context, [stock])
                        content = context.current_dt.date().strftime("%Y-%m-%d") + ' ' + self.name + ': {}涨停打开,卖出'.format(stock) + "\n"
                        print(content)


    
    # 空仓期检查
    def check_empty_month(self, context):
        subportfolio = context.subportfolios[self.subportfolio_index]
        if self.use_empty_month and context.current_dt.month in (self.empty_month) and len(subportfolio.long_positions) > 0:
            content = context.current_dt.date().strftime("%Y-%m-%d") + self.name + ': 进入空仓期' + "\n"
            for stock in subportfolio.long_positions:
                content = content + stock + "\n"
            print(content)


    # 进入空仓期清仓
    def close_for_empty_month(self, context):
        subportfolio = context.subportfolios[self.subportfolio_index]
        if self.use_empty_month and context.current_dt.month in (self.empty_month) and len(subportfolio.long_positions) > 0:
            self.sell(context, list(subportfolio.long_positions))


    # 止损检查
    def check_stoplost(self, context):
        subportfolio = context.subportfolios[self.subportfolio_index]
        if self.use_stoplost:
            if self.stoplost_date is None:
                last_prices = history(1, unit='1m', field='close', security_list=subportfolio.long_positions)
                for stock in subportfolio.long_positions:
                    position = subportfolio.long_positions[stock]
                    if (position.avg_cost-last_prices[stock][-1])/position.avg_cost > self.stoplost_level:
                        self.stoplost_date = context.current_dt.date()
                        print(self.name + ': ' + '开始止损')
                        content = context.current_dt.date().strftime("%Y-%m-%d") + ' ' + self.name + ': 止损' + "\n"
                        for stock in subportfolio.long_positions:
                            content = content + stock + "\n"
                        print(content)
                        break
            else:   # 已经在清仓静默期
                if (context.current_dt + datetime.timedelta(days=-self.stoplost_silent_days)).date() >= self.stoplost_date:
                    self.stoplost_date = None
                    print(self.name + ': ' + '退出止损')
    
    
    # 止损时清仓
    def close_for_stoplost(self, context):
        subportfolio = context.subportfolios[self.subportfolio_index]
        if self.use_stoplost and self.stoplost_date is not None and len(subportfolio.long_positions) > 0:
            self.sell(context, list(subportfolio.long_positions))
    

    # 买入多只股票
    def buy(self, context, buy_stocks):
        subportfolio = context.subportfolios[self.subportfolio_index]
        buy_count = self.max_hold_count - len(subportfolio.long_positions)
        if buy_count > 0:
            value = subportfolio.available_cash / buy_count
            index = 0
            for stock in buy_stocks:
                if stock in subportfolio.long_positions:
                    continue
                self.__open_position(stock, value)
                index = index + 1
                if index >= buy_count:
                    break
        
    
    # 卖出多只股票
    def sell(self, context, sell_stocks):
        subportfolio = context.subportfolios[self.subportfolio_index]
        for stock in sell_stocks:
            if stock in subportfolio.long_positions:
                self.__close_position(stock)
            

    # 开仓单只
    def __open_position(self, security, value):
        order = order_target_value(security, value, pindex=self.subportfolio_index)
        if order != None and order.filled > 0:
            return True
        return False
    
    
    # 清仓单只
    def __close_position(self, security):
        order = order_target_value(security, 0, pindex=self.subportfolio_index)
        if order != None and order.status == OrderStatus.held and order.filled == order.amount:
            return True
        return False


    # 过滤科创北交
    def filter_kcbj_stock(self, stock_list):
        for stock in stock_list[:]:
            if stock[0] == '4' or stock[0] == '8' or stock[:2] == '68':
                stock_list.remove(stock)
        return stock_list
    

    # 过滤停牌股票
    def filter_paused_stock(self, stock_list):
        current_data = get_current_data()
        return [stock for stock in stock_list if not current_data[stock].paused]
    
    
    # 过滤ST及其他具有退市标签的股票
    def filter_st_stock(self, stock_list):
        current_data = get_current_data()
        return [stock for stock in stock_list
                if not current_data[stock].is_st
                and 'ST' not in current_data[stock].name
                and '*' not in current_data[stock].name
                and '退' not in current_data[stock].name]
    

    # 过滤涨停的股票
    def filter_highlimit_stock(self, context, stock_list):
        subportfolio = context.subportfolios[self.subportfolio_index]
        last_prices = history(1, unit='1m', field='close', security_list=stock_list)
        current_data = get_current_data()
        
        # 已存在于持仓的股票即使涨停也不过滤,避免此股票再次可买,但因被过滤而导致选择别的股票
        return [stock for stock in stock_list if stock in subportfolio.long_positions
            or  last_prices[stock][-1] < current_data[stock].high_limit]
    
    
    # 过滤跌停的股票
    def filter_lowlimit_stock(self, context, stock_list):
        subportfolio = context.subportfolios[self.subportfolio_index]
        last_prices = history(1, unit='1m', field='close', security_list=stock_list)
        current_data = get_current_data()
        
        return [stock for stock in stock_list if stock in subportfolio.long_positions
                or last_prices[stock][-1] > current_data[stock].low_limit]
    
    
    # 过滤次新股
    def filter_new_stock(self, context, stock_list, days):
        return [stock for stock in stock_list if not context.previous_date - get_security_info(stock).start_date < datetime.timedelta(days=days)]


    # 过滤大幅解禁
    def filter_locked_shares(self, context, stock_list, days):
        df = get_locked_shares(stock_list=stock_list, start_date=context.previous_date.strftime('%Y-%m-%d'), forward_count=days)
        df = df[df['rate1']>0.2]    # 解禁数量占总股本的百分比
        filterlist = list(df['code'])
        return [stock for stock in stock_list if stock not in filterlist]

关键函数解锁后查看:

2025-02-24
⚠️
本站资源大多来自网络,仅供网友学习交流,未经作者或上传书面授权,请勿作他用。
站长 vx: xiangyin615 或者 留言反馈 ,我们将尽快处理。
Notice: When you of the legal rights be violate, please stir to vx: xiangyin615
个人中心
购物车
优惠劵
搜索