# 忽略某些模块的提示信息
import warnings
warnings.filterwarnings("ignore")
# 在全局配置中添加RQData账号信息
import rqdatac as rq
from typing import List
import pandas as pd
import numpy as np
import re
from datetime import datetime, timedelta,time
from dateutil.relativedelta import relativedelta
from vnsw.alpha_research.data_center import DataCenter
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.setting import SETTINGS
rq.init(username=SETTINGS["datafeed.username"],password=SETTINGS["datafeed.password"])
def to_clickhouse(dc,table_name,df):
SAVE_REFERENCE_QUERY = "INSERT INTO {table} VALUES"
query = SAVE_REFERENCE_QUERY.format(table=table_name)
# 生成更新时间列
update = datetime.now()
df["update"] =update
# 生成要传参用的DataFrame
# 写入数据库并返回条数
try:
dc.client.insert_dataframe(query, df)
print('已存储',set(df['symbol']),min(df['interval']),min(df['datetime']),'至',max(df['datetime']))
except Exception as e:
print ("报错",e)
def load_rq_data(symbols:List[str],exchange:Exchange,start_date:datetime,end_date:datetime,frequency:str)->pd.DataFrame:#米筐的total_turnover是成交额,open_interest是持仓量
if frequency=='1d' or frequency=='1m':
df=rq.get_price(symbols, start_date, end_date,frequency,expect_df=True).reset_index()
else:
df=pd.DataFrame()
start_date=start_date.replace(hour=0, minute=0, second=0,microsecond=0)
while start_date<end_date:
# print(start_date)
df2=rq.get_price(symbols, start_date, start_date,frequency,expect_df=True) #米筐不看时分秒,会把所有涵盖日期的所有的x分钟bar返回
if df2 is None:
start_date+=relativedelta(days=1)
continue
elif len(df2):
# print(df2)
df=pd.concat([df,df2.reset_index()])
start_date+=relativedelta(days=1)
if max(df['datetime']).time()<time(15, 15):
df.drop(df[df['datetime']==max(df['datetime'])].index,inplace=True)
df.rename(columns={'order_book_id':'symbol','date':'datetime','close':'close_price','open':'open_price','high':'high_price','low':'low_price','volume':'volume','open_interest':'open_interest','total_turnover':'turnover','settlement':'settlement_price'},inplace=True)
df['exchange']=exchange.value
df['interval']=frequency
df['turnover']=np.nan
if frequency!='1d':
df['settlement_price']=np.nan
else:
df['interval']='d'
df=df[['symbol','exchange','interval','datetime','volume','open_interest','open_price','high_price','low_price','close_price','settlement_price','turnover']]
# print(df)
return df
#入库国债期货全部后复权行情
def save_bond_future_889_history(dc,start_date,end_date=datetime.now(),frequency:str='1d'):
table_name = "ficc.bar_data_new"
exchange=Exchange.CFFEX
symbols_head=['T','TF','TS','TL','IH','IF','IC','IM']
symbols=[symbol+'889' for symbol in symbols_head]
df=pd.DataFrame()
df=load_rq_data(symbols,exchange,start_date,end_date,frequency)
if len(df):
to_clickhouse(dc,table_name,df)
#入库具体国债期货历史行情数据
def save_bond_future_history(dc,start_date,end_date=datetime.now(),frequency:str='1d'):
bond_start_date={'T':datetime(2015,3,20),'TF':datetime(2013,9,6),'TS':datetime(2018,8,17),'TL':datetime(2023,4,21),
'IH':datetime(2015,4,16),'IF':datetime(2010,4,16),'IC':datetime(2015,4,16),'IM':datetime(2022,7,22)}
to_year=end_date.year
to_month=end_date.month
table_name = "ficc.bar_data_new"
exchange=Exchange.CFFEX
for symbol_head in ['T','TF','TS','TL']:
start_year=max(bond_start_date[symbol_head],start_date).year
start_month=max(bond_start_date[symbol_head],start_date).month
for year in range(start_year,to_year+2):
for month in [3,6,9,12]:
df=pd.DataFrame()
symbol=symbol_head+str(year-2000)+str(month).zfill(2)
load_start_date=max(datetime(year,month,1)-relativedelta(years=1),start_date)
if load_start_date>end_date:
continue
load_end_date=min(datetime(year,month,1)+relativedelta(months=1)-relativedelta(days=1),end_date)
print(f'{symbol} startdate{load_start_date} enddate{load_end_date}')
try:
df=load_rq_data(symbol,exchange,load_start_date,load_end_date,frequency)
except Exception as e:
# print(f'{symbol}未取到{start_date}至{end_date}RQ数据',e)
continue
if len(df):
to_clickhouse(dc,table_name,df)
for symbol_head in ['IH','IF','IC','IM']:
start_year=max(bond_start_date[symbol_head],start_date).year
start_month=max(bond_start_date[symbol_head],start_date).month
for year in range(start_year,to_year+2):
for month in range(1,13):
df=pd.DataFrame()
symbol=symbol_head+str(year-2000)+str(month).zfill(2)
load_start_date=max(datetime(year,month,1)-relativedelta(years=1),start_date)
if load_start_date>end_date:
continue
load_end_date=min(datetime(year,month,1)+relativedelta(months=1)-relativedelta(days=1),end_date)
print(f'{symbol} startdate{load_start_date} enddate{load_end_date}')
try:
df=load_rq_data(symbol,exchange,load_start_date,load_end_date,frequency)
except Exception as e:
# print(f'{symbol}未取到{start_date}至{end_date}RQ数据',e)
continue
if len(df):
to_clickhouse(dc,table_name,df)
def save_all_bond_future_history(dc):
end_date=datetime.now()
start_date=datetime(2013,9,6)
for frequency in ['1d','1m','30m']:
save_bond_future_889_history(dc,start_date,end_date,frequency)
save_bond_future_history(dc,start_date,end_date,frequency)
def save_daily_cumulative_bond_future_history(dc):
end_date=datetime.now()
start_date=end_date-relativedelta(days=14)
for frequency in ['1d','1m','30m']:
save_bond_future_889_history(dc,start_date,end_date,frequency)
save_bond_future_history(dc,start_date,end_date,frequency)
def calc_start(dc,vt_symbol,interval):
bond_start_date={'T':datetime(2015,3,20),'TF':datetime(2013,9,6),'TS':datetime(2018,8,17),'TL':datetime(2023,4,21),'IH':datetime(2015,4,16),'IF':datetime(2010,4,16),'IC':datetime(2015,4,16),'IM':datetime(2022,7,22)}
symbol_head=re.findall(r'[a-zA-Z]+',vt_symbol)[0]
df=dc.load_bar_data([vt_symbol],interval,bond_start_date[symbol_head],bond_start_date[symbol_head]+relativedelta(days=1))[['datetime','volume','close_price']]
# print(df)
return df['close_price'][0]
def calculate_trend(dc,vt_symbols: List[str],intervals:List[str], start: datetime, end: datetime):
for interval in intervals:
for vt_symbol in vt_symbols:
symbol=vt_symbol.split('.')[0]
df=dc.load_bar_data([vt_symbol],interval,start,end)[['datetime','volume','close_price']]
df.drop_duplicates(subset=['datetime'],keep='first',inplace=True)
df.sort_values(by='datetime',ascending=True,inplace=True)
df['cum_return']=df['close_price']-calc_start(dc,vt_symbol,interval)
df['cum_return_diff']=df['cum_return'].diff()
df['cum_return_short_avg']=df['cum_return'].rolling(30).apply(lambda x :np.average(x,weights=list(range(1,31))))
df['cum_return_long_avg']=df['cum_return'].rolling(200).apply(lambda x :np.average(x,weights=list(range(1,201))))
df['cum_return_short_avg_diff']=df['cum_return_short_avg'].diff()
df['cum_return_minus_short_avg']=df['cum_return']-df['cum_return_short_avg']
df['long_trend']=df.apply(lambda x: x['cum_return'] if (x['cum_return_short_avg_diff']>0)and(x['cum_return_minus_short_avg']>0) else np.nan,axis=1)
df['short_trend']=df.apply(lambda x: x['cum_return'] if (x['cum_return_short_avg_diff']<0)and(x['cum_return_minus_short_avg']<0) else np.nan,axis=1)
df.dropna(subset=['cum_return_long_avg'],inplace=True)#第一次全量的时候可以注释掉
df.set_index('datetime',inplace=True)
dc.save_factor_df(
df=df, # 因子值DataFrame
name="bond_future_trend", # 因子名
interval=f"{interval.value}", # 周期
parameter=f"{symbol}", # 因子参数
author="lrj" # 因子作者
)#保存不了非数值型内容
print(min(df.index),'至',max(df.index),f'代码{symbol}频率{interval.value}因子已保存')
# print(df)
def run_cumulative_index(dc):
end=datetime.now()
start=end-relativedelta(years=1)
vt_symbols=['T889.CFFEX','TF889.CFFEX','TS889.CFFEX','TL889.CFFEX']
intervals=[Interval.MINUTE_30,Interval.DAILY]
calculate_trend(dc,vt_symbols,intervals, start, end)
def run_all_index(dc):
end=datetime.now()
start=datetime(2013,9,6)
vt_symbols=['T889.CFFEX','TF889.CFFEX','TS889.CFFEX','TL889.CFFEX']
intervals=[Interval.MINUTE_30,Interval.DAILY]
calculate_trend(dc,vt_symbols,intervals, start, end)
if __name__ == '__main__':
dc = DataCenter()
save_daily_cumulative_bond_future_history(dc)
# save_all_bond_future_history(dc)
run_cumulative_index(dc)
# run_all_index(dc)
print('done')
这段代码主要实现了从米筐数据平台(RQData)获取期货数据,将数据存储到ClickHouse数据库,并进行一些趋势计算和因子保存的功能。以下是对代码的详细分析:
代码功能概述
- 数据获取:通过米筐数据接口获取不同期货合约的历史行情数据,包括日线、分钟线等不同频率的数据。
- 数据存储:将获取到的数据整理后存储到ClickHouse数据库中。
- 趋势计算:根据存储在数据库中的数据,计算期货合约的累积收益、短期和长期平均收益等指标,并根据这些指标确定短期和长期趋势。
- 因子保存:将计算得到的趋势数据作为因子保存到数据库中。
代码详细分析
模块导入
import warnings
warnings.filterwarnings("ignore")
import rqdatac as rq
from typing import List
import pandas as pd
import numpy as np
import re
from datetime import datetime, timedelta,time
from dateutil.relativedelta import relativedelta
from vnsw.alpha_research.data_center import DataCenter
from vnpy.trader.constant import Exchange, Interval
from vnpy.trader.setting import SETTINGS
rq.init(username=SETTINGS["datafeed.username"],password=SETTINGS["datafeed.password"])
- 导入必要的模块,包括处理警告信息的
warnings
,米筐数据接口rqdatac
,类型提示模块typing
,数据处理模块pandas
和numpy
,正则表达式模块re
,日期时间处理模块datetime
和dateutil.relativedelta
,以及自定义的数据中心模块DataCenter
,vnpy
交易框架中的常量模块Exchange
和Interval
,以及设置模块SETTINGS
。 - 初始化米筐数据接口,使用配置文件中的用户名和密码进行登录。
数据存储函数
def to_clickhouse(dc,table_name,df):
SAVE_REFERENCE_QUERY = "INSERT INTO {table} VALUES"
query = SAVE_REFERENCE_QUERY.format(table=table_name)
# 生成更新时间列
update = datetime.now()
df["update"] =update
# 生成要传参用的DataFrame
# 写入数据库并返回条数
try:
dc.client.insert_dataframe(query, df)
print('已存储',set(df['symbol']),min(df['interval']),min(df['datetime']),'至',max(df['datetime']))
except Exception as e:
print ("报错",e)
- 该函数用于将数据存储到ClickHouse数据库中。
- 构造插入数据的SQL查询语句。
- 为数据添加一个
update
列,表示数据的更新时间。 - 尝试将数据插入到数据库中,并打印存储成功的信息。如果插入失败,打印错误信息。
数据获取函数
def load_rq_data(symbols:List[str],exchange:Exchange,start_date:datetime,end_date:datetime,frequency:str)->pd.DataFrame:
#...
return df
- 该函数从米筐数据平台获取指定期货合约的历史行情数据。
- 根据不同的频率(
1d
或1m
),使用rq.get_price
方法获取数据。如果频率不是1d
或1m
,则按天循环获取数据。 - 对获取到的数据进行列名重命名和数据格式整理,添加
exchange
、interval
等列,并根据频率设置turnover
和settlement_price
列的值。
数据保存函数
def save_bond_future_889_history(dc,start_date,end_date=datetime.now(),frequency:str='1d'):
#...
def save_bond_future_history(dc,start_date,end_date=datetime.now(),frequency:str='1d'):
#...
def save_all_bond_future_history(dc):
#...
def save_daily_cumulative_bond_future_history(dc):
#...
- 这些函数用于保存不同类型的期货历史行情数据到ClickHouse数据库。
save_bond_future_889_history
保存特定后缀为889
的期货合约数据。save_bond_future_history
保存具体的期货合约数据,根据合约类型和到期时间循环获取并保存数据。save_all_bond_future_history
保存所有期货合约的历史数据,涵盖不同频率。save_daily_cumulative_bond_future_history
保存最近14天的期货合约数据,涵盖不同频率。
趋势计算和因子保存函数
def calc_start(dc,vt_symbol,interval):
#...
return df['close_price'][0]
def calculate_trend(dc,vt_symbols: List[str],intervals:List[str], start: datetime, end: datetime):
#...
def run_cumulative_index(dc):
#...
def run_all_index(dc):
#...
calc_start
函数用于计算指定合约在特定时间的起始价格。calculate_trend
函数根据获取到的数据计算期货合约的趋势指标,包括累积收益、短期和长期平均收益等,并根据这些指标确定短期和长期趋势。最后将计算得到的因子数据保存到数据库中。run_cumulative_index
和run_all_index
函数分别根据不同的时间范围调用calculate_trend
函数,计算并保存期货合约的趋势因子。
主程序
if __name__ == '__main__':
dc = DataCenter()
save_daily_cumulative_bond_future_history(dc)
# save_all_bond_future_history(dc)
run_cumulative_index(dc)
# run_all_index(dc)
print('done')
- 主程序中创建了
DataCenter
实例,调用save_daily_cumulative_bond_future_history
函数保存最近14天的期货合约数据,调用run_cumulative_index
函数计算并保存最近一年的期货合约趋势因子。
代码改进建议
- 错误处理优化:在数据获取和存储过程中,当前的错误处理较为简单,仅打印错误信息。可以考虑增加更详细的日志记录,以便更好地定位和解决问题。
- 代码复用:部分代码存在重复,可以将一些重复的代码片段提取成独立的函数,提高代码的复用性和可维护性。
- 性能优化:在数据处理过程中,有些操作可以进行优化,例如在
calculate_trend
函数中,可以考虑使用更高效的向量化操作来替代apply
方法,以提高计算效率。 - 注释和文档:增加更多的注释和文档,特别是对于复杂的计算逻辑和业务流程,以提高代码的可读性和可理解性。
- 配置管理:将一些硬编码的参数(如合约起始日期、数据库表名等)提取到配置文件中,以便于修改和维护。
通过以上改进,可以使代码更加健壮、高效和易于维护。