RULE 回测

新RULE

rules=[
    {
        "ru":1,
        "check":lambda df:df.iloc[-1]['close']>df.iloc[-1]['ma5'],
        "score":6,
        "name":"【短线】收盘价站上MA5"
    },
    {
        "ru":2,
        "check":lambda df:df.iloc[-1]['ma5']>df.iloc[-2]['ma5'],
        "score":6,
        "name":"【趋势】MA5均线向上拐头"
    },
    {
        "ru":3,
        "check":lambda df:df.iloc[-1]['money_ma5']>200000000,
        "score":7,
        "name":"【资金】5日均成交额超2亿"
    },
    {
        "ru":4,
        "check":lambda df:df.iloc[-1]['close']>(8*(df['high'].iloc[-10:].max()-df['high'].iloc[-10:].min())/10+df['high'].iloc[-10:].min()),
        "score":7,
        "name":"【位置】收盘价靠近10日高位"
    },
    {
        "ru":5,
        "check":lambda df:30<df.iloc[-1]['rsi(6)']<70,
        "score":5,
        "name":"【指标】RSI(6)处于健康区间"
    },
    {
        "ru":6,
        "check":lambda df:df.iloc[-1]['close']>df.iloc[-1]['ma20'],
        "score":7,
        "name":"【趋势】收盘价站上MA20"
    },
    {
        "ru":7,
        "check":lambda df:df.iloc[-1]['ma20']>df.iloc[-2]['ma20'],
        "score":7,
        "name":"【趋势】MA20均线向上"
    },
    {
        "ru":8,
        "check":lambda df:df.iloc[-1]['high'] > df['high'].iloc[-20:-1].max(),
        "score":12,
        "name":"【强势】创20日新高"
    },
    {
        "ru":9,
        "check":lambda df:0.2<((df['atr'].iloc[-20:]<df['atr'].iloc[-1]).sum()/20)<0.8,
        "score":5,
        "name":"【波动】ATR波动率处于中段"
    },
    {
        "ru":10,
        "check":lambda df:df['pp'].iloc[-1]>0 and 1.5<=df['kk'].iloc[-1]<2 ,
        "score":18,
        "name":"【健康】量价配合稳步上涨"
    },
    {
        "ru":11,
        "check":lambda df:df['pp'].iloc[-1]>0 and 1<=df['kk'].iloc[-1]<1.5 ,
        "score":10,
        "name":"【温和】量能平稳温和上涨"
    },
    {
        "ru":12,
        "check":lambda df:df['pp'].iloc[-1]>0 and df['kk'].iloc[-1]>=2 ,
        "score":-12,
        "name":"【警示】倍量上涨(抛压大)"
    },
    {
        "ru":13,
        "check":lambda df:df['pp'].iloc[-1]<=0 and df['kk'].iloc[-1]>=2 ,
        "score":-25,
        "name":"【恐慌】放量暴跌(踩踏出逃)"
    },
    {
        "ru":14,
        "check":lambda df:df['pp'].iloc[-1]<=0 and 1<=df['kk'].iloc[-1]<2 ,
        "score":-15,
        "name":"【走弱】放量下跌(资金出逃)"
    },
    {
        "ru":15,
        "check":lambda df:df['pp'].iloc[-1]<=0 and df['kk'].iloc[-1]<1 ,
        "score":0,
        "name":"【整理】缩量盘整(方向不明)"
    },
    {
        "ru":16,
        "check":lambda df:df['high'].iloc[-1]>df['high'].iloc[-90:-1].max() and df['close'].iloc[-1]<df['high'].iloc[-90:-1].max() ,
        "score":-18,
        "name":"【诱多】90日假突破(主力出货)"
    },
    {
        "ru":17,
        "check":lambda df:df['high'].iloc[-1]>df['high'].iloc[-90:-1].max() and df['close'].iloc[-1]>df['high'].iloc[-90:-1].max() ,
        "score":20,
        "name":"【突破】有效突破90日高位"
    },
    {
        "ru":18,
        "check":lambda df:df['high'].iloc[-1]>=0.97*df['high'].iloc[-90:-1].max() and df['close'].iloc[-1]>=0.97*df['high'].iloc[-90:-1].max() ,
        "score":10,
        "name":"【压力】临近90日压力位"
    },
    {
        "ru":19,
        "check": lambda df: (
            (df["isST"].iloc[-1] == 1 and df["pctChg"].iloc[-1] > 4.9) or
            (df["code"].iloc[-1].startswith(("sh.600","sz.000","sz.001","sz.002")) and df["pctChg"].iloc[-1] > 9.9) or
            (df["code"].iloc[-1].startswith(("sh.688","sz.300","sz.301")) and df["pctChg"].iloc[-1] > 19.8) or
            (df["code"].iloc[-1].startswith(("bj.83","bj.87","bj.88","bj.92")) and df["pctChg"].iloc[-1] > 29.7)
        ),
        "score": 20,
        "name":"【涨停】当日强势涨停"
    },
    {
        "ru":20,
        "check": lambda df: (
            ((df["isST"].iloc[-1] == 1 and df["pctChg"].iloc[-1] > 4.9) or
            (df["code"].iloc[-1].startswith(("sh.600","sz.000","sz.001","sz.002")) and df["pctChg"].iloc[-1] > 9.9) or
            (df["code"].iloc[-1].startswith(("sh.688","sz.300","sz.301")) and df["pctChg"].iloc[-1] > 19.8) or
            (df["code"].iloc[-1].startswith(("bj.83","bj.87","bj.88","bj.92")) and df["pctChg"].iloc[-1] > 29.7))
            and (df.iloc[-1]["high"] - df.iloc[-1]["low"])/df.iloc[-1]["close"] <= 0.03
        ),
        "score": 8,
        "name":"【超强】缩量窄幅涨停(溢价高)"
    },
    {
        "ru":21,
        "check": lambda df: (
            ((df["isST"].iloc[-1] == 1 and df["pctChg"].iloc[-1] > 4.9) or
            (df["code"].iloc[-1].startswith(("sh.600","sz.000","sz.001","sz.002")) and df["pctChg"].iloc[-1] > 9.9) or
            (df["code"].iloc[-1].startswith(("sh.688","sz.300","sz.301")) and df["pctChg"].iloc[-1] > 19.8) or
            (df["code"].iloc[-1].startswith(("bj.83","bj.87","bj.88","bj.92")) and df["pctChg"].iloc[-1] > 29.7))
            and (df.iloc[-1]["high"] - df.iloc[-1]["low"])/df.iloc[-1]["close"] > 0.07
        ),
        "score": -35,
        "name":"【分歧】烂板涨停(封板无力)"
    },
    {
        "ru":22,
        "check": lambda df: (
            (df["isST"].iloc[-1] == 1 and df["pctChg"].iloc[-1] < -4.9) or
            (df["code"].iloc[-1].startswith(("sh.600","sz.000","sz.001","sz.002")) and df["pctChg"].iloc[-1] < -9.9) or
            (df["code"].iloc[-1].startswith(("sh.688","sz.300","sz.301")) and df["pctChg"].iloc[-1] < -19.8) or
            (df["code"].iloc[-1].startswith(("bj.83","bj.87","bj.88","bj.92")) and df["pctChg"].iloc[-1] < -29.7)
        ),
        "score": -35,
        "name":"【致命】当日跌停(空头主导)"
    },
    {
        "ru":23,
        "check":lambda df:df['isST'].iloc[-1]==1 ,
        "score":-30,
        "name":"【警示】ST股票(风险高)"
    },
    {
        "ru":24,
        "check":lambda df:df['turn'].iloc[-1]>=25 ,
        "score":-60,
        "name":"【警示】超高换手率(风险及高)"
    },
    {
        "ru":25,
        "check":lambda df:15<=df['turn'].iloc[-1]<25 ,
        "score":-20,
        "name":"【警示】高换手率(风险高)"
    },
    {
        "ru":26,
        "check":lambda df:5<=df['turn'].iloc[-1]<15 ,
        "score":10,
        "name":"【健康】活跃换手率"
    }
]

老RULE

rules=[
    {
        "check":lambda df:df.iloc[-1]['close']>df.iloc[-1]['ma5'],
        "score":10,
        "name":"收盘价大于MA5"
    },
    {
        "check":lambda df:df.iloc[-1]['ma5']>df.iloc[-2]['ma5'],
        "score":10,
        "name":"今日MA5>昨日MA5"
    },
    {
        "check":lambda df:df.iloc[-1]['money_ma5']>200000000,
        "score":10,
        "name":"MA5成交额大于2亿"
    },
    {
        "check":lambda df:df.iloc[-1]['close']>(8*(df['high'].iloc[-10:].max()-df['high'].iloc[-10:].min())/10+df['high'].iloc[-10:].min()),
        "score":10,
        "name":"收盘价接近10日最高点"
    },
    {
        "check":lambda df:30<df.iloc[-1]['rsi(6)']<70,
        "score":10,
        "name":"RSI(6)在30到70区间内"
    },
    {
        "check":lambda df:df.iloc[-1]['close']>df.iloc[-1]['ma20'],
        "score":10,
        "name":"收盘价大于MA20"
    },
    {
        "check":lambda df:df.iloc[-1]['ma20']>df.iloc[-2]['ma20'],
        "score":10,
        "name":"今日MA20>昨日MA20"
    },
    {
        "check":lambda df:df.iloc[-1]['high'] > df['high'].iloc[-20:-1].max(),
        "score":10,
        "name":"今日最高价20日新高点"
    },
    {
        "check":lambda df:0.2<((df['atr'].iloc[-20:]<df['atr'].iloc[-1]).sum()/20)<0.8,
        "score":10,
        "name":"ATR位于20日ATR中段"
    },
    {
        "check":lambda df:df['pp'].iloc[-1]>0 and 1.5<=df['kk'].iloc[-1]<2 ,
        "score":15,
        "name":"健康上涨 量价配合"
    },
    {
        "check":lambda df:df['pp'].iloc[-1]>0 and 1<=df['kk'].iloc[-1]<1.5 ,
        "score":5,
        "name":"温和上涨"
    },
    {
        "check":lambda df:df['pp'].iloc[-1]>0 and df['kk'].iloc[-1]>=2 ,
        "score":-10,
        "name":"倍量上涨【短线】"
    },
    {
        "check":lambda df:df['pp'].iloc[-1]<=0 and df['kk'].iloc[-1]>=2 ,
        "score":-20,
        "name":"恐慌下跌"
    },
    {
        "check":lambda df:df['pp'].iloc[-1]<=0 and 1<=df['kk'].iloc[-1]<2 ,
        "score":-10,
        "name":"放量下跌"
    },
    {
        "check":lambda df:df['pp'].iloc[-1]<=0 and df['kk'].iloc[-1]<1 ,
        "score":0,
        "name":"缩量整理"
    },
    {
        "check":lambda df:df['high'].iloc[-1]>df['high'].iloc[-90:-1].max() and df['close'].iloc[-1]<df['high'].iloc[-90:-1].max() ,
        "score":-10,
        "name":"假突破90日内高点"
    },
    {
        "check":lambda df:df['high'].iloc[-1]>df['high'].iloc[-90:-1].max() and df['close'].iloc[-1]>df['high'].iloc[-90:-1].max() ,
        "score":10,
        "name":"突破90日内高位"
    },
    {
        "check":lambda df:df['high'].iloc[-1]>=0.97*df['high'].iloc[-90:-1].max() and df['close'].iloc[-1]>=0.97*df['high'].iloc[-90:-1].max() ,
        "score":10,
        "name":"接近90日内高位,存在压力位"
    }

]

gp.py 获得数据

import pandas as pd
import numpy as np
import baostock as bs
import requests
from datetime import datetime, timedelta
import time
# import talib as ta 
#使用quant()函数替换

class GP:
    def __init__(self):
        self.login()
    def login(self):
        bs.login()
    def logout(self):
        bs.logout()
    def get_history(self,gp_type,code,end_date=False,start_date=False,days=14):
        if not end_date:
            end_date=datetime.now().strftime("%Y-%m-%d")
        if not start_date:
            start_date=(datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days)).strftime("%Y-%m-%d")
        rs = bs.query_history_k_data_plus(
            code=gp_type+"."+str(code),         
            fields="code,date,open,high,low,close,preclose,volume,pctChg,turn,isST,adjustflag",  # 要获取的字段(手动指定,Akshare 自动返回全字段)
            start_date=start_date,   # 日期格式:必须带横杠(Akshare 可无)
            end_date=end_date,
            frequency="d",             # 周期:d=日线(和 Akshare 的 period="daily" 对应)
            adjustflag="2"             # 复权:2=前复权(对应 Akshare 的 adjust="qfq")
        )
        # 3. 转为DataFrame(核心步骤)
        df = rs.get_data()
        df = df.replace("", 0)  # 把所有空字符串变成 0
        if df['preclose'].iloc[-1]=="":
            df.drop(df.index[-1],inplace=True)
        # 4. 数据类型转换(避免数值以字符串显示)
        df = df.astype({
            "code":str,
            "open": float, 
            "high": float, 
            "low": float,
            "close": float, 
            "preclose": float, 
            "volume": int,
            "pctChg":float,
            "turn":float,
            "isST":int,
            "adjustflag": int  # 复权标识转整数
        })
        return df
    #股票代码数据
    def gp_code(self,code):
        str_code=str(code)
        if str_code[0] in ['0','1','2','3']:
            gp_type='sz'
        elif str_code[0] in ['5','6']:
            gp_type='sh'
        elif str_code[0] in ['4','8','9']:
            gp_type='bj'
        else:
            return False
        return [gp_type,str_code]
    #当日股票
    def get_today(self,gp_type,code):
        str_code=str(code)
        url="https://qt.gtimg.cn/q="
        url=url+gp_type+str_code
        headers = {
            'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
        }
        res=requests.get(url,headers=headers)
        if res.status_code==200:
            if len(res.content)>30:
                reb = res.content.decode("gbk")
                reb = reb.replace("~~~", "~").replace("~~", "~")
                vsx = reb.split('~')
                return [
                            {
                                "code":gp_type+"."+str_code,
                                "date":datetime.now().strftime("%Y-%m-%d"),
                                "open": float(vsx[5]), 
                                "high": float(vsx[32]), 
                                "low": float(vsx[33]),
                                "close": float(vsx[3]), 
                                "volume": float(vsx[6])*100,
                                "adjustflag": 2  # 复权标识转整数 统一baostock
                            },
                            {
                                "name":vsx[1],
                                'money':vsx[42]
                            }
                        ]
        return False


    def gupiao(self,gp_type,gp_code,days=180):
        #获得最近180天的交易 包括今天 解决baostock 没有今日数据
        if "9:30"<=datetime.now().strftime('%H:%M')<"17:30" and datetime.now().weekday() in [0,1,2,3,4]:
            ma=self.get_today(gp_type,gp_code)
            if ma:
                if int(ma[0]['volume'])  != 0:
                    df=self.get_history(gp_type,gp_code,days=days)
                    df = pd.concat([df, pd.DataFrame([ma[0]])], ignore_index=True)
                    return df
            return False
        else:
            print("user 17:30",gp_type,gp_code)
            return self.get_history(gp_type,gp_code,days=days)

    def quant(self,df):
        #处理量化指标
        #VOL MA
        df['vol_ma5']=df["volume"].rolling(window=5).mean()
        df['vol_ma10']=df["volume"].rolling(window=10).mean()
        df['vol_ma20']=df["volume"].rolling(window=20).mean()
        df['vol_ma60']=df["volume"].rolling(window=60).mean()
        #MA
        df['ma5']=df["close"].rolling(window=5).mean()
        df['ma10']=df["close"].rolling(window=10).mean()
        df['ma20']=df["close"].rolling(window=20).mean()
        df['ma60']=df["close"].rolling(window=60).mean()
        #ATR
        df['hl']=df['high']-df['low']
        df['hc']=abs(df['high']-df['close'].shift(1))
        df['lc']=abs(df['low']-df['close'].shift(1))
        df['tr']=df[['hl','hc','lc']].abs().max(axis=1) #axis=1行计算
        df['atr'] = df['tr'].ewm(span=14, adjust=False).mean()
        #RSI 14 6
        df['delta']=df['close'].diff()
        df['gain']=df['delta'].where(df['delta']>0,0)
        df['loss']=-df['delta'].where(df['delta']<0,0)
        df['avg_gain(14)']=df['gain'].rolling(14).mean()
        df['avg_loss(14)']=df['loss'].rolling(14).mean()
        df['avg_loss(14)'] = df['avg_loss(14)'].replace(0, 1e-6)
        df['rs(14)']=df['avg_gain(14)']/df['avg_loss(14)']
        df['rsi(14)']=100-(100/(1+df['rs(14)']))
        df['avg_gain(6)']=df['gain'].rolling(6).mean()
        df['avg_loss(6)']=df['loss'].rolling(6).mean()
        df['avg_loss(6)'] = df['avg_loss(6)'].replace(0, 1e-6)
        df['rs(6)']=df['avg_gain(6)']/df['avg_loss(6)']
        df['rsi(6)']=100-(100/(1+df['rs(6)']))
        #成交 MA5
        df['money_ma5']=df['ma5']*df['vol_ma5']
        #阳 1 阴 0
        df['status']=np.where((df['close']-df['open'])>0,1,0)
        #价pp
        df['pp']=df['close']-df['close'].shift(1)
        #量kk
        df['kk']=df['volume']/df['vol_ma5']
        # ==================== 【新增】MACD 标准公式 ====================
        # 短期EMA(12)、长期EMA(26)、DIF、DEA(9)、MACD柱
        df['ema12'] = df['close'].ewm(span=12, adjust=False).mean()
        df['ema26'] = df['close'].ewm(span=26, adjust=False).mean()
        df['dif'] = df['ema12'] - df['ema26']  # 快线
        df['dea'] = df['dif'].ewm(span=9, adjust=False).mean()  # 慢线
        df['macd'] = 2 * (df['dif'] - df['dea'])  # MACD柱

        # ==================== 【新增】KDJ 标准公式 ====================
        # 9,3,3 经典参数
        low_list = df['low'].rolling(9, min_periods=9).min()
        high_list = df['high'].rolling(9, min_periods=9).max()
        
        #  RSV 未成熟随机值
        df['rsv'] = (df['close'] - low_list) / (high_list - low_list) * 100
        df['rsv'] = df['rsv'].fillna(0)  # 避免分母为0
        
        # K D J 计算
        df['k'] = df['rsv'].ewm(com=2, adjust=False).mean()  # K=3日平滑
        df['d'] = df['k'].ewm(com=2, adjust=False).mean()    # D=3日平滑
        df['j'] = 3 * df['k'] - 2 * df['d']                  # J=3K-2D

        drop_cols = [
            'hl', 'hc', 'lc', 'delta', 'gain', 'loss',
            'avg_gain(14)', 'avg_loss(14)', 'avg_gain(6)', 'avg_loss(6)'
        ]
        df = df.drop(columns=drop_cols, errors='ignore')
        return df







#1 10收盘价接近10日最高点  10
# df.iloc[-1]['close']>(8*(df['high'].iloc[-10:].max()-df['high'].iloc[-10:].min())/10+df['high'].iloc[-10:].min())
# #2 收盘价大于MA5 10
# df.iloc[-1]['close']>df.iloc[-1]['ma5']
# #3 今日MA5>昨日MA5 10
# df.iloc[-1]['ma5']>df.iloc[-2]['ma5']
# #4 今日ATR在最近20个交易日内ATR排序 10
# atrr=(df['atr'].iloc[-20:]<df['atr'].iloc[-1]).sum()/20
# atrr>0.2 and atrr<0.8
# #5 平均成交额大于2亿 10
# df.iloc[-1]['money_ma5']>200000000
# #6 RSI 6小于70 10
# df.iloc[-1]['rsi(6)']<70
# df.iloc[-1]['rsi(6)']>30
# #7 收盘价大于MA20 10
# df.iloc[-1]['close']>df.iloc[-1]['ma20']
# #8 今日MA20>昨日MA20 10
# df.iloc[-1]['ma20']>df.iloc[-2]['ma20']
# #9 20日新高点 10
# df.iloc[-1]['high'] > df['high'].iloc[-20:-1].max() 
# #10 量价指标 -100 -10 5 15
# p=df.iloc[-1]['close']-df.iloc[-2]['close']
# v=df.iloc[-1]['volume']/df['volume'].iloc[-20:].mean()

#p>0 2>v>1.5 +15
#p>0 v>1 +5
#p>0 v<1  -10 缩量
#p<0 v>2 -100 恐慌抛售
#p<=0 v<1  0 横盘


def score_def(df):
    score=0
    score_list=[]
    atrr=(df['atr'].iloc[-20:]<df['atr'].iloc[-1]).sum()/20
    p=df.iloc[-1]['close']-df.iloc[-2]['close']
    v=df.iloc[-1]['volume']/df['volume'].iloc[-20:].mean()
    if df.iloc[-1]['close']>(8*(df['high'].iloc[-10:].max()-df['high'].iloc[-10:].min())/10+df['high'].iloc[-10:].min()):
        score=score+10
        score_list.append("收盘价接近10日最高点 +10")
    if df.iloc[-1]['close']>df.iloc[-1]['ma5']:
        score=score+10
        score_list.append("收盘价大于MA5 +10")
    if df.iloc[-1]['ma5']>df.iloc[-2]['ma5']:
        score=score+10
        score_list.append("今日MA5>昨日MA5 +10")
    if atrr>0.2 and atrr<0.8:
        score=score+10
        score_list.append("ATR在20日区间位置位于中部 +10")
    if df.iloc[-1]['money_ma5']>200000000:
        score=score+10
        score_list.append("平均成交额大于2亿 +10")
    if df.iloc[-1]['rsi(6)']<70 and df.iloc[-1]['rsi(6)']>30:
        score=score+10
        score_list.append("RSI6小于70大于30 +10")
    if df.iloc[-1]['close']>df.iloc[-1]['ma20']:
        score=score+10
        score_list.append("收盘价大于MA20 +10")
    if df.iloc[-1]['ma20']>df.iloc[-2]['ma20']:
        score=score+10
        score_list.append("今日MA20>昨日MA20 +10")
    if df.iloc[-1]['high'] > df['high'].iloc[-20:-1].max():
        score=score+10
        score_list.append("20日新高点 +10")
    if p > 0:  # 上涨
        if v > 1.5:
            score += 15
            score_list.append("健康上涨 +15")
        elif v >= 1:      # 1 <= v <= 1.5
            score += 5
            score_list.append("温和放量 +5")
        else:             # v < 1
            score -= 10
            score_list.append("缩量背离 -10")
    else:  # p <= 0 下跌或平盘
        if v >= 2:
            score -= 20
            score_list.append("恐慌下跌 -20")
        elif v >= 1:      # 1 <= v < 2
            score -= 10
            score_list.append("放量下跌 -10")
        else:             # v < 1
            score += 0
            score_list.append("缩量整理 0")
    return [score,score_list,df.iloc[-1].to_dict(),df.iloc[-5:].to_dict('records')]





# gp=GP()
# co=gp.gp_code('601515')
# df=gp.gupiao(co[0],co[1],days=60)
# gp.logout()

# df=gp.quant(df)
# for i in range(10):
#     print(score_def(df))
#     df=df.drop(df.index[-1])

打分规则引入

import numpy as np
import pandas as pd
from pathlib import Path
from gp import GP,score_def
import json
import time
from rules import rules

csv_dir = Path("./csv")
gp=GP()

c=time.time()
f=open("20260327.jsonl",'w')
csv_files=list(csv_dir.glob("*.csv"))
for csv_one in csv_files:
    df=pd.read_csv(csv_one)
    df=gp.quant(df)
    score=0
    date=df.iloc[-1]['date']
    score_text=[]
    code=df.iloc[-1]['code']
    if len(df)>90:
        for ru in rules:
            if ru['check'](df):
                score=score+ru['score']
                score_text.append(ru['name'])
        # print(code,score,score_text)
        if 80<score<100:
            print(code,score,score_text)
        w_list=[
            code,
            date,
            score,
            score_text,
            df.iloc[-1].to_dict(),
            df.iloc[-15:].to_dict('records')
        ]
        f.write(json.dumps(w_list,ensure_ascii=False)+"\n")
    else:
        # print(code+"上市小于90日,不参与分析打分!")
        pass

f.close()
print('DONE',time.time()-c)

获得交易数据

from gp import GP
import pandas as pd
import time

gp=GP()
df=pd.read_csv('all.csv',dtype={'code':str})

for index,row in  df.iterrows():
    c=time.time()
    df=gp.get_history(row['exchange'],row['code'],days=180)
    df.to_csv("csv/"+row['code']+".csv")
    se=time.time()-c
    if se<0.12:
        time.sleep(0.12-se)
    print("已完成",row['exchange'],row['code'],"耗时",time.time()-c)

    
发表在 None | 留下评论

代码

import pandas as pd
import numpy as np
import baostock as bs
import requests
from datetime import datetime, timedelta
import time
# import talib as ta 
#使用quant()函数替换

class GP:
    def __init__(self):
        self.login()
    def login(self):
        bs.login()
    def logout(self):
        bs.logout()
    def get_history(self,gp_type,code,end_date=False,start_date=False,days=14):
        if not end_date:
            end_date=datetime.now().strftime("%Y-%m-%d")
        if not start_date:
            start_date=(datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days)).strftime("%Y-%m-%d")
        rs = bs.query_history_k_data_plus(
            code=gp_type+"."+str(code),         
            fields="code,date,open,high,low,close,volume,adjustflag",  # 要获取的字段(手动指定,Akshare 自动返回全字段)
            start_date=start_date,   # 日期格式:必须带横杠(Akshare 可无)
            end_date=end_date,
            frequency="d",             # 周期:d=日线(和 Akshare 的 period="daily" 对应)
            adjustflag="2"             # 复权:2=前复权(对应 Akshare 的 adjust="qfq")
        )
        # 3. 转为DataFrame(核心步骤)
        df = rs.get_data()
        df = df.replace("", 0)  # 把所有空字符串变成 0
        # 4. 数据类型转换(避免数值以字符串显示)
        df = df.astype({
            "code":str,
            "open": float, 
            "high": float, 
            "low": float,
            "close": float, 
            "volume": float,
            "adjustflag": int  # 复权标识转整数
        })
        return df
    #股票代码数据
    def gp_code(self,code):
        str_code=str(code)
        if str_code[0] in ['0','1','2','3']:
            gp_type='sz'
        elif str_code[0] in ['5','6','9']:
            gp_type='sh'
        elif str_code[0] in ['4','8']:
            gp_type='bj'
        else:
            return False
        return [gp_type,str_code]
    #当日股票
    def get_today(self,gp_type,code):
        str_code=str(code)
        url="https://qt.gtimg.cn/q="
        url=url+gp_type+str_code
        headers = {
            'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
        }
        res=requests.get(url,headers=headers)
        if res.status_code==200:
            if len(res.content)>30:
                reb = res.content.decode("gbk")
                reb = reb.replace("~~~", "~").replace("~~", "~")
                vsx = reb.split('~')
                return [
                            {
                                "code":gp_type+"."+str_code,
                                "date":datetime.now().strftime("%Y-%m-%d"),
                                "open": float(vsx[5]), 
                                "high": float(vsx[32]), 
                                "low": float(vsx[33]),
                                "close": float(vsx[3]), 
                                "volume": float(vsx[6])*100,
                                "adjustflag": 2  # 复权标识转整数 统一baostock
                            },
                            {
                                "name":vsx[1],
                                'money':vsx[42]
                            }
                        ]
        return False


    def gupiao(self,gp_type,gp_code,days=180):
        #获得最近180天的交易 包括今天 解决baostock 没有今日数据
        if datetime.now().strftime('%H:%M')<"17:30":
            ma=self.get_today(gp_type,gp_code)
            if ma:
                if int(ma[0]['volume'])  != 0:
                    df=self.get_history(gp_type,gp_code,days=days)
                    df = pd.concat([df, pd.DataFrame([ma[0]])], ignore_index=True)
                    return df
            return False
        else:
            print("user 17:30",gp_type,gp_code)
            return self.get_history(gp_type,gp_code,days=days)

    def quant(self,df):
        #处理量化指标
        #VOL MA
        df['vol_ma5']=df["volume"].rolling(window=5).mean()
        df['vol_ma10']=df["volume"].rolling(window=10).mean()
        df['vol_ma20']=df["volume"].rolling(window=20).mean()
        df['vol_ma60']=df["volume"].rolling(window=60).mean()
        #MA
        df['ma5']=df["close"].rolling(window=5).mean()
        df['ma10']=df["close"].rolling(window=10).mean()
        df['ma20']=df["close"].rolling(window=20).mean()
        df['ma60']=df["close"].rolling(window=60).mean()
        #ATR
        df['hl']=df['high']-df['low']
        df['hc']=abs(df['high']-df['close'].shift(1))
        df['lc']=abs(df['low']-df['close'].shift(1))
        df['tr']=df[['hl','hc','lc']].abs().max(axis=1) #axis=1行计算
        df['atr'] = df['tr'].ewm(span=14, adjust=False).mean()
        #RSI 14 6
        df['delta']=df['close'].diff()
        df['gain']=df['delta'].where(df['delta']>0,0)
        df['loss']=-df['delta'].where(df['delta']<0,0)
        df['avg_gain(14)']=df['gain'].rolling(14).mean()
        df['avg_loss(14)']=df['loss'].rolling(14).mean()
        df['avg_loss(14)'] = df['avg_loss(14)'].replace(0, 1e-6)
        df['rs(14)']=df['avg_gain(14)']/df['avg_loss(14)']
        df['rsi(14)']=100-(100/(1+df['rs(14)']))
        df['avg_gain(6)']=df['gain'].rolling(6).mean()
        df['avg_loss(6)']=df['loss'].rolling(6).mean()
        df['avg_loss(6)'] = df['avg_loss(6)'].replace(0, 1e-6)
        df['rs(6)']=df['avg_gain(6)']/df['avg_loss(6)']
        df['rsi(6)']=100-(100/(1+df['rs(6)']))
        #成交 MA5
        df['money_ma5']=df['ma5']*df['vol_ma5']
        #阳 1 阴 0
        df['status']=np.where((df['close']-df['open'])>0,1,0)
        return df







#1 10收盘价接近10日最高点  10
# df.iloc[-1]['close']>(8*(df['high'].iloc[-10:].max()-df['high'].iloc[-10:].min())/10+df['high'].iloc[-10:].min())
# #2 收盘价大于MA5 10
# df.iloc[-1]['close']>df.iloc[-1]['ma5']
# #3 今日MA5>昨日MA5 10
# df.iloc[-1]['ma5']>df.iloc[-2]['ma5']
# #4 今日ATR在最近20个交易日内ATR排序 10
# atrr=(df['atr'].iloc[-20:]<df['atr'].iloc[-1]).sum()/20
# atrr>0.2 and atrr<0.8
# #5 平均成交额大于2亿 10
# df.iloc[-1]['money_ma5']>200000000
# #6 RSI 6小于70 10
# df.iloc[-1]['rsi(6)']<70
# df.iloc[-1]['rsi(6)']>30
# #7 收盘价大于MA20 10
# df.iloc[-1]['close']>df.iloc[-1]['ma20']
# #8 今日MA20>昨日MA20 10
# df.iloc[-1]['ma20']>df.iloc[-2]['ma20']
# #9 20日新高点 10
# df.iloc[-1]['high'] > df['high'].iloc[-20:-1].max() 
# #10 量价指标 -100 -10 5 15
# p=df.iloc[-1]['close']-df.iloc[-2]['close']
# v=df.iloc[-1]['volume']/df['volume'].iloc[-20:].mean()

#p>0 2>v>1.5 +15
#p>0 v>1 +5
#p>0 v<1  -10 缩量
#p<0 v>2 -100 恐慌抛售
#p<=0 v<1  0 横盘


def score_def(df):
    score=0
    score_list=[]
    atrr=(df['atr'].iloc[-20:]<df['atr'].iloc[-1]).sum()/20
    p=df.iloc[-1]['close']-df.iloc[-2]['close']
    v=df.iloc[-1]['volume']/df['volume'].iloc[-20:].mean()
    if df.iloc[-1]['close']>(8*(df['high'].iloc[-10:].max()-df['high'].iloc[-10:].min())/10+df['high'].iloc[-10:].min()):
        score=score+10
        score_list.append("收盘价接近10日最高点 +10")
    if df.iloc[-1]['close']>df.iloc[-1]['ma5']:
        score=score+10
        score_list.append("收盘价大于MA5 +10")
    if df.iloc[-1]['ma5']>df.iloc[-2]['ma5']:
        score=score+10
        score_list.append("今日MA5>昨日MA5 +10")
    if atrr>0.2 and atrr<0.8:
        score=score+10
        score_list.append("ATR在20日区间位置位于中部 +10")
    if df.iloc[-1]['money_ma5']>200000000:
        score=score+10
        score_list.append("平均成交额大于2亿 +10")
    if df.iloc[-1]['rsi(6)']<70 and df.iloc[-1]['rsi(6)']>30:
        score=score+10
        score_list.append("RSI6小于70大于30 +10")
    if df.iloc[-1]['close']>df.iloc[-1]['ma20']:
        score=score+10
        score_list.append("收盘价大于MA20 +10")
    if df.iloc[-1]['ma20']>df.iloc[-2]['ma20']:
        score=score+10
        score_list.append("今日MA20>昨日MA20 +10")
    if df.iloc[-1]['high'] > df['high'].iloc[-20:-1].max():
        score=score+10
        score_list.append("20日新高点 +10")
    if p > 0:  # 上涨
        if v > 1.5:
            score += 15
            score_list.append("健康上涨 +15")
        elif v >= 1:      # 1 <= v <= 1.5
            score += 5
            score_list.append("温和放量 +5")
        else:             # v < 1
            score -= 10
            score_list.append("缩量背离 -10")
    else:  # p <= 0 下跌或平盘
        if v >= 2:
            score -= 20
            score_list.append("恐慌下跌 -20")
        elif v >= 1:      # 1 <= v < 2
            score -= 10
            score_list.append("放量下跌 -10")
        else:             # v < 1
            score += 0
            score_list.append("缩量整理 0")
    return [score,score_list,df.iloc[-1].to_dict(),df.iloc[-20:].to_dict('records')]





# gp=GP()
# co=gp.gp_code('601515')
# df=gp.gupiao(co[0],co[1],days=60)
# gp.logout()

# df=gp.quant(df)
# for i in range(10):
#     print(score_def(df))
#     df=df.drop(df.index[-1])
from gp import GP
import pandas as pd
import time

gp=GP()
df=pd.read_csv('all.csv',dtype={'code':str})

for index,row in  df.iterrows():
    c=time.time()
    df=gp.gupiao(row['exchange'],row['code'],days=120)
    if df:
        df.to_csv("csv/"+row['code']+".csv")
        print("已完成",row['exchange'],row['code'],"耗时",time.time()-c)
    time.sleep(2)

csv文件循环

import numpy as np
import pandas as pd
from pathlib import Path
from gp import GP,score_def
import json
import time

csv_dir = Path("./csv")
gp=GP()

c=time.time()
f=open("20260326.jsonl",'w')
csv_files=list(csv_dir.glob("*.csv"))
for csv_one in csv_files:
    df=pd.read_csv(csv_one)
    df=gp.quant(df)
    vv=score_def(df)
    if vv[0]>60:
        print(vv[0],vv[2]['code'])
        f.write(json.dumps(vv, ensure_ascii=False)+'\n')
    else:
        # print('NONE')
        pass
f.close()
print('DONE',time.time()-c)

花费113秒处理全部JSON文件

import json

with open("20260326.jsonl",'r',encoding='utf8') as f:
    for line in f:
        e=json.loads(line)
        print('-'*40)
        print(e[0])
        print(e[1])
        print(e[2])
        print(e[3])
        print('-'*40)

发表在 None | 留下评论

备份gp

import pandas as pd
import baostock as bs
import requests
from datetime import datetime, timedelta
import talib as ta

def get_history(gp_type,code,end_date=False,start_date=False,days=14):
    if not end_date:
        end_date=datetime.now().strftime("%Y-%m-%d")
    if not start_date:
        start_date=(datetime.strptime(end_date, "%Y-%m-%d") - timedelta(days)).strftime("%Y-%m-%d")
    bs.login()
    rs = bs.query_history_k_data_plus(
        code=gp_type+"."+str(code),         
        fields="code,date,open,high,low,close,volume,adjustflag",  # 要获取的字段(手动指定,Akshare 自动返回全字段)
        start_date=start_date,   # 日期格式:必须带横杠(Akshare 可无)
        end_date=end_date,
        frequency="d",             # 周期:d=日线(和 Akshare 的 period="daily" 对应)
        adjustflag="2"             # 复权:2=前复权(对应 Akshare 的 adjust="qfq")
    )
    # 3. 转为DataFrame(核心步骤)
    df = rs.get_data()
    df = df.replace("", 0)  # 把所有空字符串变成 0
    # 4. 数据类型转换(避免数值以字符串显示)
    df = df.astype({
        "code":str,
        "open": float, 
        "high": float, 
        "low": float,
        "close": float, 
        "volume": float,
        "adjustflag": int  # 复权标识转整数
    })
    bs.logout()
    return df

def gp_code(code):
    str_code=str(code)
    if str_code[0] in ['0','1','2','3']:
        gp_type='sz'
    elif str_code[0] in ['5','6','9']:
        gp_type='sh'
    elif str_code[0] in ['4','8']:
        gp_type='bj'
    else:
        return False
    return [gp_type,str_code]

def get_today(gp_type,code):
    str_code=str(code)
    url="https://qt.gtimg.cn/q="
    url=url+gp_type+str_code
    headers = {
        'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
    }
    res=requests.get(url,headers=headers)
    if res.status_code==200:
        if len(res.content)>30:
            reb = res.content.decode("gbk")
            reb = reb.replace("~~~", "~").replace("~~", "~")
            vsx = reb.split('~')
            return [
                        {
                            "code":gp_type+"."+str_code,
                            "date":datetime.now().strftime("%Y-%m-%d"),
                            "open": vsx[5], 
                            "high": vsx[32], 
                            "low": vsx[33],
                            "close": vsx[3], 
                            "volume": float(vsx[6])*100,
                            "adjustflag": 2  # 复权标识转整数
                        },
                        {
                            "name":vsx[1],
                            'money':vsx[42]
                        }
                    ]
    return False


ea=gp_code(600302)
ma=get_today(ea[0],ea[1])



df=get_history('sh','600302',days=180)

print('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')

if ma:
    if int(ma[0]['volume'])  != 0:
        df = pd.concat([df, pd.DataFrame([ma[0]])], ignore_index=True)

df['MA5'] = ta.MA(df['close'], timeperiod=5)
df['MA10'] = ta.MA(df['close'], timeperiod=10)
df['MA20'] = ta.MA(df['close'], timeperiod=20)
df['MACD'], df['MACD_SIGNAL'], df['MACD_HIST'] = ta.MACD(df['close'])
df['RSI'] = ta.RSI(df['close'], timeperiod=14)
# 成交量均线
df['VOL_MA5'] = ta.MA(df['volume'], timeperiod=5)
df['VOL_MA10'] = ta.MA(df['volume'], timeperiod=10)
df['VOL_MA20'] = ta.MA(df['volume'], timeperiod=20)
df['ATR'] = ta.ATR(df['high'], df['low'], df['close'], timeperiod=6)

print(df.head(30))
df.to_csv("aa.csv")
发表在 None | 留下评论

腾讯股票接口 快速

 if(in_array($jugg,['0','1','2','3'])){
                    $value='sz'.$value;
                }elseif(in_array($jugg,['5','6','9'])) {
                    $value='sh'.$value;
                }elseif(in_array($jugg,['4','8'])) {
                    $value='bj'.$value;
                }else{
                    continue;
                }

qt.gtimg.cn/q=s_sh600519,s_sh601318,s_sh601899,s_sh600036,s_sh601689,s_sh600016,s_sh601088,s_sh600887,s_sh601288,s_sh601658,s_sh600000,s_sh601989,s_sh601398,s_sh600104,s_sh600900,s_sh601898,s_sh600030,s_sh600690,s_sh601166,s_sh600703,s_sz300750,s_sz000333,s_sz000858,s_sz002594,s_sz000651,s_sz002475,s_sz002415,s_sz000001,s_sz000725,s_sz002142,s_sz000568,s_sz002230,s_sz000895,s_sz002508,s_sz000983,s_sz002371,s_sz002714,s_sz002008,s_sz000063,s_sz002821,s_sz300476,s_sz300059,s_sz300124,s_sz300760,s_sz002916,s_sz002202,s_sz000776,s_sz002601,s_sz002460,s_sz002304

上面简要信息

数据:
v_s_sh600519=”1~贵州茅台~600519~1408.07~-36.93~-2.56~47256~666971~~17632.84~GP-A~”; v_s_sh601318=”1~中国平安~601318~57.47~-2.27~-3.80~1253823~726410~~10406.46~GP-A~”;

市场类型
1 = 沪 A / 51 = 深 A
股票名称
贵州茅台
股票代码
600519
当前价格
涨跌额(今价 – 昨收)
涨跌幅 %
成交量(手)
成交额(万元)
(空字段,无用)
换手率 %
股票类型
GP-A=A 股 / GP-A-CYB = 创业板

可以打包获取全部,且字段少

发表在 None | 留下评论

数据获取 baostock

pip install baostock

但是只能提前一天,需要再加上腾讯的股票API 可以获取当日交易数据

import pandas as pd
import baostock as bs

bs.login()

rs = bs.query_history_k_data_plus(
    code="sz.000021",          # 股票代码:必须加市场前缀(sz=深市,sh=沪市),Akshare 不用
    fields="date,open,high,low,close,volume,adjustflag",  # 要获取的字段(手动指定,Akshare 自动返回全字段)
    start_date="2025-01-01",   # 日期格式:必须带横杠(Akshare 可无)
    end_date="2026-03-13",
    frequency="d",             # 周期:d=日线(和 Akshare 的 period="daily" 对应)
    adjustflag="2"             # 复权:2=前复权(对应 Akshare 的 adjust="qfq")
)


# 3. 转为DataFrame(核心步骤)
df = rs.get_data()

# 4. 数据类型转换(避免数值以字符串显示)
df = df.astype({
    "open": float, 
    "high": float, 
    "low": float,
    "close": float, 
    "volume": float,
    "adjustflag": int  # 复权标识转整数
})

bs.logout()

数据结构:

,date,open,high,low,close,volume,adjustflag
0,2025-01-02,18.8231439000,18.8627091000,17.6757531000,17.9527095000,60318610,2
1,2025-01-03,18.0021660000,18.1109703000,17.3097750000,17.3196663000,46006347,2
2,2025-01-06,17.2801011000,17.5174923000,17.0229273000,17.2306446000,30090966,2
3,2025-01-07,17.3196663000,17.6559705000,17.1910794000,17.6361879000,37186445,2
4,2025-01-08,17.4680358000,17.7252096000,16.7459709000,17.4284706000,44330600,2
5,2025-01-09,17.3097750000,17.9823834000,17.3097750000,17.6757531000,42315366,2
6,2025-01-10,17.6065140000,18.1307529000,17.2998837000,17.2998837000,41871400,2
7,2025-01-13,16.9141230000,17.3592315000,16.7558622000,17.1910794000,27391847,2
8,2025-01-14,17.3196663000,18.2296659000,17.1515142000,18.1999920000,48855138,2
9,2025-01-15,18.1208616000,18.2593398000,17.9032530000,17.9428182000,31419000,2
10,2025-01-16,18.1406442000,18.3681441000,17.7054270000,17.9032530000,33532828,2
11,2025-01-17,17.8142313000,18.2692311000,17.7153183000,18.0911877000,39192662,2
发表在 None | 留下评论