[QUANTAXIS量化分析]成长股内在价值投资策略(ZZ)

基本原理
滚动复利,顾名思义,用利润滚动利润,简称:驴打滚.这种方法适用于大资金在起步建仓期,又或者在打净值安全垫期。利用极少一部分仓位将安全垫做出,这种方法比较适用于震荡市. 主要体现在灵活的仓位控制上,方法运用的过程中类似抗日战争时期八路军的"打游击”,在已既定选好股票池的基础上,以不恋战为原则,快进快出.
策略实现
初始资金100万,时间段为:2016-01-01~2018-05-01

选择市盈率在0~20的市值最大的前n只股票

平均分给每只股票现金相同,五步建仓,相当于若选50只股票的话,一只股票初始资金5万,分五次一万出来;

个股5%止损和15%止盈

购买条件:连续五日下跌的以及价格高于5日或10日均线20%的股票不操作

每日对现金进行重新分配,对于每支股票,最多只能买入五次,不过每次买入的金额可能会不同,(之前是用每次买入的金额一致,见图二),下文代码使用的是对同一支每次买入金额可能不同的情况,即图一。每次买入金额相同即意味着第一次买入某支股票时,便把金额存储起来,每日剩余现金流减去这部分金额,再均分下去

运行截图:

图1:

在这里插入图片描述

图2:

在这里插入图片描述


# coding: utf-8
# @author: lin
# @date: 2018/11/14

import QUANTAXIS as QA
import pandas as pd
import time
import matplotlib.pyplot as plt

pd.set_option('max_colwidth', 5000)
pd.set_option('display.max_columns', 5000)
pd.set_option('display.max_rows', 5000)


class RollingProfitStrategy:
    def __init__(self, start_time, stop_time, n=20, ascending=False, stock_init_cash=1000000):
        self.Account = QA.QA_Account()  # 初始化账户
        self.Account.reset_assets(stock_init_cash)  # 初始化账户
        self.Broker = QA.QA_BacktestBroker()
        self.time_quantum_list = ['-12-31', '-09-30', '-06-30', '-03-31']
        self.start_time = start_time
        self.stop_time = stop_time
        self.stock_pool = []
        self.cash_n = 5
        self.bought_price = {}  # 入场的价格
        self.last_price = {}  # 上一天的价格
        self.init_cash = {}  # 入场的现金分配
        self.bought_stock = {}  # 每种股票是否已入场, boolean
        self.init_strategy(n, ascending)

    def init_strategy(self, n, ascending):
        self.get_stock_pool_price(n, ascending)  # 获取股票和初始价格
        for stock_code in self.stock_pool:
            self.init_cash[stock_code] = [1 for i in range(self.cash_n)]  # 五等分
            self.bought_stock[stock_code] = False

    def get_financial_time(self):
        """
        得到此日期前一个财务数据的日期
        :return:
        """
        year = self.start_time[0:4]
        while (True):
            for day in self.time_quantum_list:
                the_financial_time = year + day
                if the_financial_time <= self.start_time:
                    return the_financial_time
            year = str(int(year) - 1)

    def get_assets_eps(self, stock_code, the_financial_time):
        """
        得到高级财务数据
        :param stock_code:
        :param the_financial_time: 离开始时间最近的财务数据的时间
        :return:
        """
        financial_report = QA.QA_fetch_financial_report(stock_code, the_financial_time)
        if financial_report is not None:
            return financial_report.iloc[0]['totalAssets'], financial_report.iloc[0]['EPS']
        return None, None

    def get_stock_pool_price(self, n, ascending):
        """
        选取哪些股票
        :param n: n只
        :param ascending: True则资产最少的前n,False则最多前n
        :return:
        """
        stock_code_list = QA.QA_fetch_stock_list_adv().code.tolist()
        stock_dict = {'stock': [], 'totalAssets': [], 'price': []}
        the_financial_time = self.get_financial_time()
        for stock_code in stock_code_list:
            # print(stock_code)
            assets, EPS = self.get_assets_eps(stock_code, the_financial_time)
            if assets is not None and EPS != 0:
                data = QA.QA_fetch_stock_day_adv(stock_code, self.start_time, self.stop_time)
                if data is None:
                    continue
                price = data.to_pd().iloc[0]['close']
                if 0 < price / EPS < 20:  # 满足条件才添加进行排序
                    # print(price / EPS)
                    stock_dict['stock'].append(stock_code)
                    stock_dict['totalAssets'].append(assets)
                    stock_dict['price'].append(price)
        data = pd.DataFrame(stock_dict)
        data.dropna(inplace=True)
        data.sort_values(by='totalAssets', ascending=ascending, axis=0, inplace=True)
        # print(data.iloc[:20])
        # data.reset_index(inplace=True, drop=True)
        self.stock_pool = list(data['stock'].iloc[:n])  # 前十行,若是用loc,则是查找索引
        price_list = list(data['price'].iloc[:n])
        for i in range(n):
            self.bought_price[self.stock_pool[i]] = price_list[i]
            self.last_price[self.stock_pool[i]] = price_list[i]
        print(self.stock_pool)

    def get_pre_month_date(self, date):
        # 得到一个月前的1日的日期,为了得到某天前十个交易日的数据
        date = date[:10]
        times = date.split('-')
        times = [int(i) for i in times]
        times[1] -= 1
        if times[1] == 0:
            times[0] -= 1
            times[1] = 12
        times[2] = 1
        return '%d-%02d-%02d' % (times[0], times[1], times[2])

    def is_decrease(self, data):
        # 是否连续五日下跌,此时当start_time为交易日时,第五天为start_time,若是用于实际数据中,我有点疑惑是否用reality_time数据或是前五个交易日
        close_data = list(data['close'].iloc[-5:])
        if len(close_data) < 5:
            return False
        if close_data[0] > close_data[1] > close_data[2] > close_data[3] > close_data[4]:  # 五日连续下跌
            print(close_data)
            return False
        return True

    def is_increase(self, data):
        close_data = list(data['close'].iloc[-10:])
        if len(close_data) < 10:
            return False
        mean_5_close = sum(close_data[5:10]) / 5
        mean_10_close = sum(close_data[:10]) / 10
        if (close_data[-1] - mean_5_close) / mean_5_close > 0.2 or \
                (close_data[-1] - mean_10_close) / mean_10_close > 0.2:  # 价格大于五日均线或者是十日均线 20%,则返回True
            print(close_data)
            return False
        return True

    def if_buy(self, stock_code, date):  # 若是连续五日下跌或者昨日价格大于五日或十日均线20%则不进行操作
        pre_month_date = self.get_pre_month_date(date)
        data = QA.QA_fetch_stock_day_adv(stock_code, pre_month_date, date)
        data = data.to_qfq()  # 前复权
        if self.is_decrease(data) or self.is_increase(data):
            return True
        return False

    def run(self):
        """
        每个股票初始五等分,可以进场则进场,每个股票盈亏单独算,当止损或者止盈时,股票池中所有未入场的股票现金均分一下
        也可每天均分一次,这样就把止损和止盈的情况包括进去了。
        :return:
        """

        self.Account.account_cookie = 'rolling_profit'
        # 每天均分一下现金
        data = QA.QA_fetch_stock_day_adv(self.stock_pool, self.start_time, self.stop_time).to_qfq()
        # print(data)
        for items in data.panel_gen:  # 每一天
            # 股票池中所有未入场的股票现金均分一下
            n_percent = 0
            for stock_code in self.stock_pool:
                n_percent += sum(self.init_cash[stock_code])
            if n_percent == 0:
                n_money = 0
            else:
                n_money = self.Account.cash_available / n_percent

            print(n_money)

            for item in items.security_gen:
                close_price = item.close[0]
                date = str(item.date[0])[:10]
                stock_code = item.code[0]
                # item = item.to_pd()
                # item.reset_index(inplace=True)
                if not self.bought_stock[stock_code]:  # 股票未入场的情况
                    if self.if_buy(stock_code, date):  # 若是可以买
                        if n_money >= close_price*100:
                            order = self.Account.send_order(
                                code=stock_code,
                                time=date,
                                money=n_money,
                                towards=QA.ORDER_DIRECTION.BUY,
                                price=close_price,
                                order_model=QA.ORDER_MODEL.CLOSE,
                                amount_model=QA.AMOUNT_MODEL.BY_MONEY
                            )
                            self.Broker.receive_order(QA.QA_Event(order=order, market_data=item))
                            trade_mes = self.Broker.query_orders(self.Account.account_cookie, 'filled')
                            res = trade_mes.loc[order.account_cookie, order.realorder_id]
                            order.trade(res.trade_id, res.trade_price, res.trade_amount, res.trade_time)  # date 应为res.trade_time的
                            self.init_cash[stock_code][0] = 0
                            self.bought_stock[stock_code] = True
                            self.bought_price[stock_code] = close_price  # 入场价格
                            self.last_price[stock_code] = close_price  # 最新价格
                else:  # 股票已经入场,一种是止盈或止亏,一种是继续买入
                    if (close_price - self.bought_price[stock_code]) / close_price > 0.15 \
                            or (close_price - self.bought_price[stock_code]) / close_price < -0.05:
                        # 止盈止亏
                        order = self.Account.send_order(
                            code=stock_code,
                            time=date,
                            amount=self.Account.sell_available.get(stock_code, 0),
                            towards=QA.ORDER_DIRECTION.SELL,
                            price=0,
                            order_model=QA.ORDER_MODEL.CLOSE,
                            amount_model=QA.AMOUNT_MODEL.BY_AMOUNT
                        )
                        self.Broker.receive_order(QA.QA_Event(order=order, market_data=item))
                        trade_mes = self.Broker.query_orders(self.Account.account_cookie, 'filled')
                        res = trade_mes.loc[order.account_cookie, order.realorder_id]
                        order.trade(res.trade_id, res.trade_price, res.trade_amount, res.trade_time)

                        self.bought_stock[stock_code] = False
                        self.init_cash[stock_code] = [1 for i in range(self.cash_n)]
                    else:
                        if (close_price - self.last_price[stock_code]) / close_price >= 0.01:  # 日涨1%(今日比昨日涨1%,才算)
                            for i in range(self.cash_n):
                                if self.init_cash[stock_code][i] == 1:
                                    if n_money >= close_price * 100:
                                        order = self.Account.send_order(
                                            code=stock_code,
                                            time=date,
                                            money=n_money,
                                            towards=QA.ORDER_DIRECTION.BUY,
                                            price=close_price,
                                            order_model=QA.ORDER_MODEL.CLOSE,
                                            amount_model=QA.AMOUNT_MODEL.BY_MONEY
                                        )
                                        self.Broker.receive_order(QA.QA_Event(order=order, market_data=item))
                                        trade_mes = self.Broker.query_orders(self.Account.account_cookie, 'filled')
                                        res = trade_mes.loc[order.account_cookie, order.realorder_id]
                                        order.trade(res.trade_id, res.trade_price, res.trade_amount, res.trade_time)

                                        self.init_cash[stock_code][i] = 0
                            self.last_price[stock_code] = close_price
            self.Account.settle()
        Risk = QA.QA_Risk(self.Account)
        Risk.assets.plot()  # 总资产
        plt.show()
        Risk.benchmark_assets.plot()  # 基准收益的资产
        plt.show()
        Risk.plot_assets_curve()  # 两个合起来的对比图
        plt.show()
        Risk.plot_dailyhold()  # 每只股票每天的买入量
        plt.show()


start = time.time()
one = RollingProfitStrategy('2016-01-01', '2018-05-01', ascending=False, stock_init_cash=1000000)
stop = time.time()
print(stop - start)
print(one.stock_pool)
one.run()
stoo = time.time()
print(stoo - stop)


# init_cash = {}
# init_price = {}
# for stock_code in stock_pool:
#     init_cash[stock_code] = 10000
#     init_price[stock_code] = 0    # 15%止盈



————————————————
版权声明:本文为CSDN博主「Trident_lin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39220714/article/details/88105378

posted @ 2021-03-17 20:55  马语者  阅读(212)  评论(0编辑  收藏  举报