量化学习(三)数据更新

前置说明
清理了 stock_codes表,仅保留深市数据,参考语句
delete from student where username like 'a%' or username like '%ST'

%代表后面可以匹配任意个字符。如 a% 可以表示以a开头的任意字符串,如:abca ,aaaa,acgfrrrrrr,akjgus,a,ab…%a% 可以表示一个有a的字符串, 如:a,sabbb,ddddda,edfg…%a 可以表示以a结尾的字符串, 如:a,sa,aaaaa,defsa…

数据获取,开始时间设置为20010101,复权:前复权

import akshare as ak
import sqlalchemy
import datetime
import multiprocessing
import pandas as pd



def create_mysql_engine():
    """
    创建数据库引擎对象
    :return: 新创建的数据库引擎对象
    """

    # 引擎参数信息
    # host = '127.0.0.1'
    # user = 'root'
    # passwd = 'A_quant88'
    # port = '3306'
    # db = 'quantf'
    host = '192.168.9.110'
    user = 'root'
    passwd = 'A_quant88'
    port = '3306'
    db = 'quant'

    # 创建数据库引擎对象
    mysql_engine = sqlalchemy.create_engine(
        'mysql+pymysql://{0}:{1}@{2}:{3}'.format(user, passwd, host, port),
        poolclass=sqlalchemy.pool.NullPool
    )

    # 如果不存在数据库db_quant则创建

    mysql_engine.execute("CREATE DATABASE IF NOT EXISTS {0} ".format(db))

    # 创建连接数据库db_quant的引擎对象
    db_engine = sqlalchemy.create_engine(
        'mysql+pymysql://{0}:{1}@{2}:{3}/{4}?charset=utf8'.format(user, passwd, host, port, db),
        poolclass=sqlalchemy.pool.NullPool
    )

    # 返回引擎对象
    return db_engine

def get_stock_codes(update=False):

    """
    获取指定日期的A股代码列表

    若参数update为False,表示从数据库中读取股票列表
    若数据库中不存在股票列表的表,或者update为True,则下载指定日期date的交易股票列表


    :param update: 是否更新股票列表,默认为False
    :return: A股代码的列表
    """

    # 创建数据库引擎对象
    engine = create_mysql_engine()

    # 数据库中股票代码的表名
    table_name = 'stock_codes'

    # 数据库中不存在股票代码表,或者需要更新股票代码表
    if table_name not in sqlalchemy.inspect(engine).get_table_names() or update:

        # 查询股票数据
        stock_zh_spot_df = ak.stock_zh_a_spot_em()  ## 获取实时数据
        stock_zh_spot_data = stock_zh_spot_df[stock_zh_spot_df['名称'] != '']  ## 去除名称为空值的数据
        codes_names = stock_zh_spot_data[['代码', '名称']]
        # 将股票代码写入数据库
        codes_names.to_sql(name=table_name, con=engine, if_exists='replace', index=False, index_label=False)
        # 返回股票列表
        return codes_names['代码'].tolist()

    # 从数据库中读取股票代码列表
    else:
        # 待执行的sql语句
        sql_cmd = 'SELECT {} FROM {}'.format('代码', table_name)
        # 读取sql,返回股票列表
        return pd.read_sql(sql=sql_cmd, con=engine)['代码'].tolist()
def create_data(stock_codes, period = "daily",start_date = '19901219', end_date = '20230228',
                    adj = 'qfq'):

    """
    下载指定日期内,指定股票的日线数据

    :param stock_codes: 待下载数据的股票代码
    :param start_date: 日线开始日期1990-12-19
    :param end_date: 日线结束日期 end_date = datetime.date.today().strftime('%Y%m%d')
    :param adjustflag: 复权选项 
    :return: None
    """
    # 创建数据库引擎对象
    engine = create_mysql_engine()
    # 下载股票循环
    for index,code in enumerate(stock_codes):
        print('({}/{})正在创建{}...'.format(index + 1, len(stock_codes), code))
        data_df = ak.stock_zh_a_hist(symbol=code, period=period, start_date=start_date, end_date=end_date,
                                         adjust=adj)  ## 日度数据,后复权
        if data_df.empty:
            print('--------------empty------------------'+ code)
            continue
                # 删除重复数据
        data_df.drop_duplicates(['日期'], inplace=True)
            # 写入数据库
        table_name = '{}'.format(code)
        data_df.to_sql(name=table_name, con=engine, if_exists='replace', index=True, index_label='id')


def get_code_group(process_num, stock_codes):
    """
    获取代码分组,用于多进程计算,每个进程处理一组股票

    :param process_num: 进程数
    :param stock_codes: 待处理的股票代码
    :return: 分组后的股票代码列表,列表的每个元素为一组股票代码的列表
    """

    # 创建空的分组
    code_group = [[] for i in range(process_num)]

    # 按余数为每个分组分配股票
    for index, code in enumerate(stock_codes):
        code_group[index % process_num].append(code)

    return code_group


def multiprocessing_func(func, args):
    """
    多进程调用函数

    :param func: 函数名
    :param args: func的参数,类型为元组,第0个元素为进程数,第1个元素为股票代码列表
    :return: 包含各子进程返回对象的列表
    """

    # 用于保存各子进程返回对象的列表
    results = []

    # 创建进程池
    with multiprocessing.Pool(processes=args[0]) as pool:
        # 多进程异步计算
        for codes in get_code_group(args[0], args[1]):
            results.append(pool.apply_async(func, args=(codes, *args[2:],)))
        # 阻止后续任务提交到进程池
        pool.close()
        # 等待所有进程结束
        pool.join()

    return results


def create_data_mp(stock_codes, process_num=4,
                   period = "daily",start_date = '20010101', end_date = '20230228', adj='qfq'):
    """
    使用多进程创建指定日期内,指定股票的日线数据,计算扩展因子

    :param stock_codes: 待创建数据的股票代码
    :param process_num: 进程数
    :param from_date: 日线开始日期
    :param to_date: 日线结束日期
    :param adjustflag: 复权选项 
    :return: None
    """

    multiprocessing_func(create_data, (process_num, stock_codes, period,start_date, end_date, adj))

if __name__ == '__main__':
    stock_codes = get_stock_codes()
    create_data_mp(stock_codes)
#     print(stock_codes)

数据更新,建议定时任务设置为三点半后

import akshare as ak
import datetime
import time
import sys
import numpy as np
import pandas as pd
import multiprocessing
import sqlalchemy

# # 可用日线数量约束
g_available_days_limit = 250

# BaoStock日线数据字段
g_baostock_data_fields = '日期,开盘,收盘,最高,最低,成交量,成交额,振幅,涨跌幅,涨跌额,换手率'


def create_mysql_engine():
    """
    创建数据库引擎对象

    :return: 新创建的数据库引擎对象
    """

    # 引擎参数信息
    # 引擎参数信息
    host = '192.168.9.110'
    user = 'root'
    passwd = 'A_quant88'
    port = '3306'
    db = 'quant'

    # 创建数据库引擎对象
    mysql_engine = sqlalchemy.create_engine(
        'mysql+pymysql://{0}:{1}@{2}:{3}'.format(user, passwd, host, port),
        poolclass=sqlalchemy.pool.NullPool
    )

    # 如果不存在数据库db_quant则创建
    mysql_engine.execute("CREATE DATABASE IF NOT EXISTS {0} ".format(db))

    # 创建连接数据库db_quant的引擎对象
    db_engine = sqlalchemy.create_engine(
        'mysql+pymysql://{0}:{1}@{2}:{3}/{4}?charset=utf8'.format(user, passwd, host, port, db),
        poolclass=sqlalchemy.pool.NullPool
    )

    # 返回引擎对象
    return db_engine


def get_stock_codes(update=False):

    """
    获取指定日期的A股代码列表

    若参数update为False,表示从数据库中读取股票列表
    若数据库中不存在股票列表的表,或者update为True,则下载指定日期date的交易股票列表


    :param update: 是否更新股票列表,默认为False
    :return: A股代码的列表
    """

    # 创建数据库引擎对象
    engine = create_mysql_engine()

    # 数据库中股票代码的表名
    table_name = 'stock_codes'

    # 数据库中不存在股票代码表,或者需要更新股票代码表
    if table_name not in sqlalchemy.inspect(engine).get_table_names() or update:

        # 查询股票数据
        stock_zh_spot_df = ak.stock_zh_a_spot_em()  ## 获取实时数据
        stock_zh_spot_data = stock_zh_spot_df[stock_zh_spot_df['名称'] != '']  ## 去除名称为空值的数据
        codes_names = stock_zh_spot_data[['代码', '名称']]
        # 将股票代码写入数据库
        codes_names.to_sql(name=table_name, con=engine, if_exists='replace', index=False, index_label=False)
        # 返回股票列表
        return codes_names['代码'].tolist()

    # 从数据库中读取股票代码列表
    else:
        # 待执行的sql语句
        sql_cmd = 'SELECT {} FROM {}'.format('代码', table_name)
        # 读取sql,返回股票列表
        return pd.read_sql(sql=sql_cmd, con=engine)['代码'].tolist()


def create_data(stock_codes, period = "daily",start_date = '20010101', end_date = '20230301',
                    adj = 'qfq'):

    """
    下载指定日期内,指定股票的日线数据

    :param stock_codes: 待下载数据的股票代码
    :param start_date: 日线开始日期1990-12-19
    :param end_date: 日线结束日期 end_date = datetime.date.today().strftime('%Y%m%d')
    :param adjustflag: 复权选项 
    :return: None
    """
    # 创建数据库引擎对象
    engine = create_mysql_engine()
    # 下载股票循环
    for index,code in enumerate(stock_codes):
        print('({}/{})正在创建{}+++'.format(index + 1, len(stock_codes), code))
        data_df = ak.stock_zh_a_hist(symbol=code, period=period, start_date=start_date, end_date=end_date,
                                         adjust=adj)  ## 日度数据,后复权
        if data_df.empty:
            print('--------------empty------------------'+ code)
            continue
                # 删除重复数据
        data_df.drop_duplicates(['日期'], inplace=True)
            # 写入数据库
        table_name = '{}'.format(code)
        data_df.to_sql(name=table_name, con=engine, if_exists='replace', index=True, index_label='id')

        print('({}/{})创建完成{}...'.format(index + 1, len(stock_codes), code))


def get_code_group(process_num, stock_codes):
    """
    获取代码分组,用于多进程计算,每个进程处理一组股票

    :param process_num: 进程数
    :param stock_codes: 待处理的股票代码
    :return: 分组后的股票代码列表,列表的每个元素为一组股票代码的列表
    """

    # 创建空的分组
    code_group = [[] for i in range(process_num)]

    # 按余数为每个分组分配股票
    for index, code in enumerate(stock_codes):
        code_group[index % process_num].append(code)

    return code_group


def multiprocessing_func(func, args):
    """
    多进程调用函数

    :param func: 函数名
    :param args: func的参数,类型为元组,第0个元素为进程数,第1个元素为股票代码列表
    :return: 包含各子进程返回对象的列表
    """

    # 用于保存各子进程返回对象的列表
    results = []

    # 创建进程池
    with multiprocessing.Pool(processes=args[0]) as pool:
        # 多进程异步计算
        for codes in get_code_group(args[0], args[1]):
            results.append(pool.apply_async(func, args=(codes, *args[2:],)))

        # 阻止后续任务提交到进程池
        pool.close()

        # 等待所有进程结束
        pool.join()

    return results


def create_data_mp(stock_codes, process_num=4,
                   period = "daily",start_date = '20010101', end_date = '20230301', adj='qfq'):
    """
    使用多进程创建指定日期内,指定股票的日线数据,计算扩展因子

    :param stock_codes: 待创建数据的股票代码
    :param process_num: 进程数
    :param from_date: 日线开始日期19901219
    :param to_date: 日线结束日期datetime.date.today().strftime('%Y%m%d')
    :param adjustflag: 复权选项 
    :return: None
    """

    multiprocessing_func(create_data, (process_num, stock_codes, period,start_date, end_date, adj))




def multiprocessing_func_df(func, args):
    """
    多进程调用函数,收集返回各子进程返回的DataFrame

    :param func: 函数名
    :param args: func的参数,类型为元组,第0个元素为进程数,第1个元素为股票代码列表
    :return: 包含各子进程返回值的DataFrame
    """

    # 多进程调用函数func,获取子进程返回对象的列表
    results = multiprocessing_func(func, args)

    # 构建空DataFrame
    df = pd.DataFrame()

    # 收集各进程返回值
    for i in results:
        df = df.append(i.get())

    return df





def update_data(stock_codes, query_days=60, adjust='qfq'):
    """
    更新日线数据,计算相关因子

    :param stock_codes: 待更新数据的股票代码
    :param query_days: 在数据库中查询历史日线数据的天数,用于计算扩展因子,需要根据扩展因子设置,这里要计算60日均线,所以最小设置为60
    :param adjustflag: 复权选项 
    :return: 包含所有待处理股票的最新一日日线数据及扩展因子的DataFrame
    """

    # 创建数据库引擎对象
    engine = create_mysql_engine()

    # 创建空DataFrame,存储最新一日的数据
    latest_df = pd.DataFrame()

    # 更新数据循环
    for index, code in enumerate(stock_codes):
        print('({}/{})正在创建{}---'.format(index + 1, len(stock_codes), code))



        # 股票数据在数据库中的表名
        table_name = '{}'.format(code)

        # 判断是否存在该表,不存在则跳过
        if table_name not in sqlalchemy.inspect(engine).get_table_names():
            continue

        # 获取按时间排序的最后query_days行数据
        sql_cmd = 'SELECT * FROM {} ORDER BY 日期 DESC LIMIT {};'.format('quant.'+table_name, query_days)
        read_df = pd.read_sql(sql=sql_cmd, con=engine)

        # 如果数据少于query_days,则不更新
        if read_df.shape[0] < query_days:
            continue

        # 数据按id(date)升序排序
        read_df = read_df.sort_values(by='id', ascending=True)

        # 获取数据库中最新数据日期及id
        last_id = read_df['id'].iloc[-1]
        from_date = read_df['日期'].iloc[-1]
        print(from_date)

        # 如果数据库中已包含最新一日数据
        if from_date >= datetime.date.today().strftime('%Y-%m-%d'):

            # 将最新一日数据,添加code字段,append到latest_df中,并进行下一只股票更新
            latest_series = read_df.iloc[-1].copy()
            latest_series['code'] = code[3:]
            latest_df = latest_df.append(latest_series)
            print('{}当前已为最新数据'.format(code))
            continue

        # 计算待更新数据的开始日期
        from_date = (datetime.datetime.strptime(
            from_date, '%Y-%m-%d') + datetime.timedelta(days=1)).strftime('%Y-%m-%d')
        
        # 下载日线数据
        out_df = ak.stock_zh_a_hist(symbol=code, period="daily", start_date=(datetime.datetime.strptime(from_date,'%Y-%m-%d').strftime("%Y%m%d")),
                                    end_date='20230301',adjust=adjust)

        if out_df.empty:
            continue

        # 新添加行数
        new_rows = out_df.shape[0]

        # 获取下载字段,拼接DataFrame,用于后续计算扩展指标
        out_df = read_df[list(out_df)].append(out_df)

        # 重置索引
        out_df.reset_index(drop=True, inplace=True)


        # 取最后new_rows行
        out_df = out_df.iloc[-new_rows:]

        # 判读是否有字段缺失
        if np.any(out_df.isnull()):
            print('{}有缺失字段!!!'.format(code))

        # 更新id
        out_df['id'] = pd.Series(np.arange(last_id + 1, last_id + 1 + new_rows), index=out_df.index)

        # 将更新数据写入数据库
        out_df.to_sql(name=table_name, con=engine, if_exists='append', index=False)

        # 将更新的最后一行添加code字段,append到latest_df中
        latest_series = out_df.iloc[-1].copy()
        latest_series['code'] = code
        latest_df = latest_df.append(latest_series)

    # 返回包含最新一日股票日线数据的DataFrame
    return latest_df


def update_data_mp(stock_codes, process_num=4, query_days=60, adj='qfq'):
    """
    使用多进程更新日线数据,计算扩展因子

    :param stock_codes: 待更新数据的股票代码
    :param process_num: 进程数
    :param query_days: 在数据库中查询历史日线数据的天数,用于计算扩展因子
    :param adjustflag: 复权选项 
    :return: 包含所有待处理股票的最新日线数据的DataFrame
    """

    # 多进程更新数据,获取最新日线数据的DataFrame
    latest_df = multiprocessing_func_df(update_data, (process_num, stock_codes, query_days, adj))


    # 将所有股票最新日线数据写入数据库表latest
    if latest_df.shape[0]:
        latest_df.to_sql(name='latest', con=create_mysql_engine(), if_exists='replace', index=False)


    return latest_df


if __name__ == '__main__':
    start_time = time.time()
    stock_codes = get_stock_codes()
    update_data_mp(stock_codes)
    end_time = time.time()
    print('程序运行时间:{}s'.format(end_time - start_time))

备注:注意end_date = datetime.date.today().strftime(‘%Y%m%d’)替换

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值