富途证券OpenAPI 量化接口,目前已经不能提供,存量中国境内用户还能用,富途行情软件是目前市面上界面最漂亮的行情软件,加上openapi,功能非常强大。
参考官网python版本,终于完成了自选股票K线数据入库。
from futu import *
from Sqlhelper import *
import schedule
class Futuapi:
def __init__(self):
"""
初始化函数
参数: 无
返回值: 无
功能描述:
1. 初始化交易数据上下文环境;
2. 初始化股票代码列表、开始日期字典、表格列表和周期类型列表。
"""
self.quote_ctx = OpenQuoteContext(host='127.0.0.1', port=11112)
self.codes = []#自选股列表
self.start_date = {}#历史数据获取需要
self.table_list = ['m1', 'm5', 'm15', 'm30', 'm60', 'day']#K线周期对应表名
self.type_list = [SubType.K_1M, SubType.K_5M, SubType.K_15M, SubType.K_30M, SubType.K_60M,SubType.K_DAY ]#FUTUAPI的K线周期
def get_user_security(self):#获取自选股列表
"""
获取用户的自选股列表,并对每只股票进行数据初始化。
函数不接受参数,也不直接返回值,但会对类的内部状态进行更新。
"""
# 尝试从API接口获取用户自选股票列表
ret, data = self.quote_ctx.get_user_security("cc")
if ret == RET_OK and data.shape[0] > 0:
self.codes = data.to_dict(orient = 'records')
# 将股票代码数据存储为字典格式,每个股票代码对应一条记录
for i in self.codes: # 遍历每只股票代码
i['label'] = i['code'].replace(".", "")# 生成股票标签,移除股票代码中的点号
dbname = i['label']# 用作数据库名
init_db(dbname)# 创建数据库,LABEL为数据库名
Creat_table(dbname) # 初始化股票数据表
dt = self.get_rehab(i['code']) #获取复权因子
if len(dt)>0:# 如果成功获取复权因子,则添加到股票信息SEFL.CODES中
i['rehab'] = dt
# 清空STOCK数据库中历史数据
delete_all_data_in_stock()
# 更新股票代码和复权因子信息到数据库
update_stock_code(self.codes)
update_stock_rehab(self.codes)
else:
print('error:', data)
#获取复权因子['forward_adj_factorA']
def get_rehab(self,code):#每 30 秒内最多请求 60 次获取复权因子接口。
"""
获取复权因子数据。
参数:
code: 股票代码。
返回值:
rehab_list: 包含复权因子信息的列表,每个元素是一个字典,包括日期和两个复权因子。
"""
rehab_list = []
ret, data = self.quote_ctx.get_rehab(code)
if ret == RET_OK:
# 成功获取数据,转换为字典列表格式
rehab = data.to_dict(orient = 'records')
for i in rehab:
rehab_list.append( {"date":i['ex_div_date'],"factorA":i['forward_adj_factorA'],"factorB":i['forward_adj_factorB']})
else:
print('error:', data)
#time.sleep(0.6)自选股数量没有超过50个
return rehab_list
def request_history_kline(self,Code,Start,End,Ktype):#获取历史 K 线
"""
获取历史 K 线数据
参数:
Code: 要查询的股票代码
Start: 查询的起始时间,格式为 'YYYY-MM-DD HH-MM-SS'
End: 查询的结束时间,格式为 'YYYY-MM-DD HH-MM-SS'
Ktype: K 线的类型,例如 [SubType.K_1M, SubType.K_5M, SubType.K_15M, SubType.K_30M, SubType.K_60M,SubType.K_DAY ]#FUTUAPI的K线周期
返回值:
如果请求成功,返回一个包含 K 线数据的元组列表,每个元组包含 (时间, 开盘价, 最高价, 最低价, 收盘价)
如果请求失败,打印错误信息
"""
ret, data, page_req_key = self.quote_ctx.request_history_kline(code=Code, start=Start, end=End,ktype=Ktype,autype=AuType.QFQ,fields=[KL_FIELD.ALL],max_count=None, page_req_key=None) # 每页5个,请求第一页
if ret == RET_OK:
# 将DataFrame转换为元组列表,方便mysql插入数据库
data = data[['time_key', 'open', 'high', 'low', 'close']]
data.rename(columns={'time_key': 'time'}, inplace=True)
values_list = [tuple(row) for row in data.itertuples(index=False)]
return values_list
else:
print('error:', data)
def get_date(self):#读取历史K线所需的start end 日期
"""
获取历史K线数据的起止日期,用于请求最近一段时间的K线数据。
:返回: 无返回值,但会更新实例的start_date字典,其中包含了不同周期K线的最新起始日期。
"""
End = datetime.now()
Start = End - timedelta(days=1000)
End = End.strftime('%Y-%m-%d %H:%M:%S')
Start = Start.strftime('%Y-%m-%d %H:%M:%S')
ktype = KLType.K_DAY
#从沪深300指数的日K中提取
ret = self.request_history_kline("SZ.399300",Start,End,ktype)
self.start_date['m1'] = ret[-3][0] # 更新1分钟周期的最新起始日期
self.start_date['m5'] = ret[-15][0]# 更新5分钟周期的最新起始日期
self.start_date['m15'] = ret[-45][0] # 更新15分钟周期的最新起始日期
self.start_date['m30'] = ret[-90][0] # 更新30分钟周期的最新起始日期
self.start_date['m60'] = ret[-180][0] # 更新60分钟周期的最新起始日期
self.start_date['day'] = ret[-540][0] # 更新日线周期的最新起始日期
def get_ktype(self,table):
"""
根据传入的表名,返回相应的K线类型。
参数:
- self: 方法的对象引用
- table: 字符串,表示需要查询的表名,也是K线周期
返回值:
- KLType: 枚举类型,表示对应的K线周期
"""
if table == 'm1':
return KLType.K_1M
elif table == 'm5':
return KLType.K_5M
elif table == 'm15':
return KLType.K_15M
elif table == 'm30':
return KLType.K_30M
elif table == 'm60':
return KLType.K_60M
elif table == 'day':
return KLType.K_DAY
def get_klines(self,Code,table):#每 30 秒内最多请求 60 次历史 K 线接口
"""
请求历史 K 线数据并插入数据库。
每 30 秒内最多请求 60 次历史 K 线接口。
参数:
- Code: 股票代码,字符串类型。
- table: 数据表名称,用于存储 K 线数据。
返回值:
- 无返回值,但会将请求到的 K 线数据插入到指定的数据库表中。
"""
# 获取当前时间作为结束时间
End = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 从存储开始日期的字典中获取开始时间
Start = self.start_date[table]
# 根据表名获取 K 线类型
ktype = self.get_ktype(table)
# 请求历史 K 线数据
ret = self.request_history_kline(Code,Start,End,ktype)
# 移除股票代码中的点号,用于数据库表名
dbname = Code.replace(".", "")
# 将获取到的 K 线数据插入数据库
insert_klines(dbname,table,ret)#插入数据
def insert_new_klines(self):
"""
向数据库中插入新的K线数据。
遍历self.codes中的所有代码,对于每个代码,首先删除对应数据库中的所有数据,然后根据self.table_list中的表ID获取K线数据并插入。
不接受任何参数。
无返回值。
"""
for code in self.codes:
dbname = code['code'].replace(".", "") # 从代码中提取数据库名,去掉点号
delete_all_data_in_db(dbname)#删除所有数据,重新插入
for table_id in self.table_list:
# 获取K线数据
self.get_klines(code['code'],table_id)
print("插入新数据 " + code['code'] + " " + table_id )
time.sleep(0.6)# 为了防止请求过于频繁,每插入一条数据后暂停0.6秒,每 30 秒内最多50 次,不会超过接口60次限制
def update_m1_klines(self):
"""
获取指定股票的K线数据(m1分钟线数据)。与实时K线差1分钟,主要是接口限制。
因为不超过50只自选股,这里不用sleep
参数:
self: 类的实例。
返回值:
无返回值,该函数主要为了获取数据并进行后续操作,不直接返回数据。
"""
print(datetime.now().strftime('%H:%M:%S') + ' 更新1分钟K线数据')
table = 'm1'# 指定获取的K线数据类型为1分钟线,只更新1分钟数据,其他数据根据1分钟K线合成
for code in self.codes:
dbname = code['code'].replace(".", "")
# 从数据库中获取K线数据
klines = from_db_get_klines(dbname,table)
if len(klines) > 0:# 如果获取到数据
# 获取最新K线数据的时间作为开始时间,最后一个K
starttime = klines[-1][0].strftime('%Y-%m-%d %H:%M:%S')
# 获取当前时间作为结束时间
end = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 请求历史K线数据
ret = self.request_history_kline(code['code'],starttime,end,self.get_ktype(table))
# 如果请求到的数据条数大于1,数据中会包含最后一个K,大于1,说明有新的K线
if len(ret) > 1:
for j in range(1,len(ret)):# 遍历请求到的数据,从第二条开始
if ret[j][0] > starttime: # 如果数据的时间大于数据库中的最后一个K线的时间
# 插入数据到数据库
insert_kline(dbname,table,ret[j])
#历史数据不存在update
def run_update_history(self):
"""
启动定时任务,每隔30秒执行一次update_m1_klines方法。
无参数
无返回值
"""
self.get_user_security()#获取自选股列表,更新stock库中的代码列表和复权因子数据
self.get_date()#获取开始时间
self.insert_new_klines()#第一次更新所有周期数据,更新前清空所有数据
# 每隔30秒执行一次update_m1_klines方法,只更新1分钟历史K线,与实时差1分钟
schedule.every(30).seconds.do(self.update_m1_klines)
# 无限循环,确保定时任务可以持续执行
while True:
# 运行所有可以运行的任务
schedule.run_pending()
time.sleep(1) # 防止CPU占用过高
# 示例用法
if __name__ == "__main__":
f = Futuapi()
f.run_update_history()