量化选股,一个策略搞定

最近,在IT领域最火的必然是deepseek了,不仅在应用领域火的一塌糊涂,在国家政客也频频提起,并受到相关领导人的多次接见。

今天我们不聊ds ,而是基于ds背后老板,我们聊聊量化投资。量化听起来很高大山,本质是通过技术选择合适的指标(策略)对市场上的股票进行买入和卖出。在最近一段时间里面,我基于个人在投资时用到的几个指标,用python code 复现出来,用于对市场上5600家股票进行选股策略,以下是代码:

import backtrader as bt
import akshare as ak
import pandas as pd
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor

pd.set_option("display.max_columns", None)  # 显示所有列
pd.set_option("display.expand_frame_repr", False)  # 禁止自动换行
pd.set_option("display.max_colwidth", None)  # 显示完整列内容


"""
python --versionv  :Python 3.11.2
akshare : pip install akshare 
backtrader : pip install backtrader 

"""
# 策略类
class BollEMA_strategy(bt.Strategy):
    params = (
        ('period_boll', 20),
        ('period_ema12', 12),
        ('period_ema50', 50),
        ('pe_dict', dict),  # 接收预获取的市盈率数据
    )

    def __init__(self):
        self.boll = bt.indicators.BollingerBands(self.data.close, period=self.p.period_boll)
        self.ema12 = bt.indicators.EMA(self.data.close, period=self.p.period_ema12)   #ExponentialMovingAverage
        self.ema50 = bt.indicators.EMA(self.data.close, period=self.p.period_ema50)
        self.crossover = bt.ind.CrossOver(self.ema12, self.ema50)
        self.current_pe = self.p.pe_dict.get(self.data._name)  # 从字典获取市盈率

    def next(self):

        if len(self) < self.p.period_ema50:
            return

        # 计算买卖条件
        # buy_condition1 = self.data.close[0] > self.boll.mid[0] > self.data.close[-1]
        buy_condition2 = self.crossover[0] == 1 and self.crossover[-1] != 1
        sell_condition1 = self.data.close[0] < self.boll.mid[0] and self.data.close[-1] >= self.boll.mid[-1]
        sell_condition2 = self.crossover[0] == -1

        buy_signal = buy_condition2 and (0 < self.current_pe < 60)  # pe 是硬性条件
        sell_signal = sell_condition1 or sell_condition2

        # 执行交易
        #if buy_signal:
        #    self.buy()
        #elif sell_signal:
        #     self.sell()

        # 输出结果
        current_date = self.data.datetime.date(0)
        # 以特定格式输出日期
        # 获取今天的日期
        today = datetime.now().date()
        formatted_date = today.strftime('%Y-%m-%d')
        target_date = datetime.strptime(formatted_date, "%Y-%m-%d").date()
        # 减去一天
        days_to_subtract = timedelta(days=1)
        previous_date = target_date - days_to_subtract

        if current_date == previous_date: #target_date

            result = {
                '日期': current_date,
                '代码': self.data._name,
                '收盘价': self.data.close[0],
                'BOLL中轨': self.boll.mid[0],
                'BOLL下轨': self.boll.bot[0],
                'EMA金叉': buy_condition2,
                'EMA1': self.ema12[0],
                'EMA2': self.ema50[0],
                'EMA死叉': sell_condition2,
                '市盈率':  self.current_pe,
                '信号': '买入' if buy_signal else '卖出' if sell_signal else '持有'
            }

            df = pd.DataFrame([result])
            if (df['信号'].values == '买入'):
                print(df)


def get_stock_data(code, start_date, end_date):
    try:
        df = ak.stock_zh_a_hist(symbol=code, period="daily", start_date=start_date, end_date=end_date, adjust="")
        if df.empty:
            return None
        df['日期'] = pd.to_datetime(df['日期'])
        df.set_index('日期', inplace=True)
        df.columns = ['code', 'open', 'close', 'high', 'low', 'volume', 'amount', 'amplitude', 'rise', 'rise_amt',
                      'turnover']
        return df[['open', 'high', 'low', 'close', 'volume']]
    except Exception as e:
        print(f"获取{code}数据失败: {str(e)}")
        return None


def batch_fetch_data(stock_list, start_date, end_date, max_workers=90):
    """多线程批量获取股票数据"""
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {executor.submit(get_stock_data, code, start_date, end_date): (code, name) for code, name in
                   stock_list}

        results = {}
        for future in futures:
            code, name = futures[future]

            try:
                data = future.result()
                if data is not None and len(data) >= 60:
                    # 计算最后一天的涨幅
                    if len(data) >= 2:
                        last_close = data.close.iloc[-1]
                        pre_close = data.close.iloc[-2]
                        pct_change = (last_close - pre_close) / pre_close * 100
                        #print(code, last_close, pre_close, pct_change)
                        if pct_change < 8 and pct_change > 0 :  # 涨幅低于8%
                            results[code] = data
            except Exception as e:
                print(f"Error processing {code}: {str(e)}")
        return results

def get_all_a_shares():
    df = ak.stock_zh_a_spot_em()
    return df[['代码', '名称']].values.tolist()

def main():
    # 获取基础信息
    end_date = datetime.now().strftime("%Y%m%d")
    start_date = (datetime.now() - timedelta(days=300)).strftime("%Y%m%d")

    # 预获取市盈率数据
    spot_df = ak.stock_zh_a_spot_em()
    pe_dict = spot_df.set_index('代码')['市盈率-动态'].to_dict()
    # 获取股票列表
    all_stocks = get_all_a_shares()  # 测试时限制数量

    # 批量获取数据

    data_dict = batch_fetch_data(all_stocks, start_date, end_date)

    # 添加数据到引擎
    i = 1
    for code, df in data_dict.items():
        # 初始化回测引擎
        cerebro = bt.Cerebro()
        #print('current sequence : ', i, ' of ', len(all_stocks))
        i = i + 1
        data = bt.feeds.PandasData(dataname=df, name=code)
        # 添加策略(传递市盈率字典)
        cerebro.addstrategy(BollEMA_strategy, pe_dict=pe_dict)
        cerebro.adddata(data)

        # 批量运行策略
        try:
            cerebro.run()
            #print("end back strategy")
        except Exception as e:
            print(f"Cerebro run:{e}")
            continue

if __name__ == "__main__":
    main()

今天运行了下周五的数据,满足指标买入要求的大概是 40只左右,大家可以加入自选股中,做一个观察仓,如果发现有突破或是起量的趋势,可以逐步买入,建仓。

以下是运行结果截图:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值