说明
上一篇说到,交易量可能可以作为策略规则的支持度分析,但是(我现在还不想付费买数据)现成的接口似乎并没有这样的统计。获取某一只股票的日交易数据是相对简单的,市场上也就不到5000只的股票,总数据量应该也不会超过18M(5000*3000)。所以可以获取全市场的日数据,然后自己汇总。
以下是流程:
- 1 列出所有的股票
- 2 for each 抓取所有的日数据
- 3 将数据存到数据库
- 4 进行统计,并可视化
- 5 分析过去几年交易量的变化
内容
使用akshare来抓取数据,这个是接口文档。
我认为,真正的算法交易并不需要太多太频繁的数据,也不需要高频交易。过去有很多技术性套利的方法反而被视为正统,例如利用服务器时间差快速、大量交易。这本身导致了交易系统承受类似DDOS般的影响,也使得交易者的心态收到DDOS的攻击:价格暴跌,直接击穿心理预期。然后这也招致了更多的”管理约束“。从人工智能的角度,算法应该能够和类似人类交易者的方式获取数据,就像一场体育比赛一样。真正实现超越的是内在的方法,例如alphago击败李世石那样。
所以我所基于量化分析的整个体系,对于数据的维度、频率要求都非常低。真正的复杂性在于获得这些数据之后所采取的算法和运行架构,以及基于这些架构产生大量数据之后所作出的决策和调整。另外,从我自己多年处理数据的经验来说,更多的依赖数据商的指标其实不是什么好事。由于数据本身的复杂性,还有业务相关的变化,大部分的指标是无法维护,甚至是有欺骗性的(不是恶意的),使用这些指标无异于坐在火山口上。
1 列出所有的股票
import akshare as ak
stock_info_a_code_name_df = ak.stock_info_a_code_name()
print(stock_info_a_code_name_df)
这步分数据也存一下吧,一次性的,就不使用ORM了
from Basefuncs import *
chcfg = CHCfg(database='qtv200')
click_para = chcfg.dict()
chc = CHClient(**click_para)
chc._exe_sql('show tables')
# 简单表格
create_table_sql = '''
CREATE TABLE stock_name_list
(
code String,
name String,
watch_time String
)
ENGINE = MergeTree
PRIMARY KEY (code)
order by code
'''
chc._exe_sql(create_table_sql)
chc.insert_df2table(table_name='stock_name_list', some_df=stock_info_a_code_name_df, pid_name='code')
2 抓取日数据/存库
采用东财历史数据接口
import akshare as ak
stock_zh_a_hist_df = ak.stock_zh_a_hist(symbol="000001", period="daily", start_date="20000101", end_date='20990528', adjust="")
print(stock_zh_a_hist_df)
日期 股票代码 开盘 收盘 最高 最低 成交量 成交额 振幅 涨跌幅 涨跌额 换手率
0 2017-03-01 000001 9.49 9.49 9.55 9.47 346994 3.301580e+08 0.84 0.11 0.01 0.21
1 2017-03-02 000001 9.51 9.43 9.54 9.42 403629 3.823959e+08 1.26 -0.63 -0.06 0.24
2 2017-03-03 000001 9.41 9.40 9.43 9.36 342655 3.219525e+08 0.74 -0.32 -0.03 0.20
3 2017-03-06 000001 9.40 9.45 9.46 9.39 404511 3.812123e+08 0.74 0.53 0.05 0.24
4 2017-03-07 000001 9.44 9.45 9.46 9.40 294673 2.777474e+08 0.63 0.00 0.00 0.17
... ... ... ... ... ... ... ... ... ... ... ... ...
1755 2024-05-22 000001 11.56 11.56 11.74 11.46 2115531 2.458449e+09 2.42 0.09 0.01 1.09
1756 2024-05-23 000001 11.53 11.40 11.59 11.37 1841623 2.110799e+09 1.90 -1.38 -0.16 0.95
1757 2024-05-24 000001 11.37 11.31 11.49 11.30 1398276 1.593330e+09 1.67 -0.79 -0.09 0.72
1758 2024-05-27 000001 11.31 11.51 11.53 11.31 1454361 1.663272e+09 1.95 1.77 0.20 0.75
1759 2024-05-28 000001 11.50 11.40 11.58 11.36 1204323 1.377107e+09 1.91 -0.96 -0.11 0.62
[1760 rows x 12 columns]
我只需要成交金额,日期和股票代码,所以可以建立如下数据模型:
import pandas as pd
from sqlalchemy import Column, Integer, String, create_engine,Float,DateTime, func, Text, Index
# 映射
from sqlalchemy import MetaData, Table,select
from clickhouse_sqlalchemy import make_session, engines
from sqlalchemy.orm import sessionmaker, declarative_base
from datetime import datetime
# 连接字符串格式: clickhouse+http://<username>:<password>@<host>:<port>/<database
engine = create_engine('clickhouse+http://xxxx:xxxx@127.0.0.1:18123/qtv200')
Base = declarative_base()
# MergeTree才会持久化
class StockTrade(Base):
__tablename__ = 'stock_trade'
id = Column(Integer, primary_key=True)
create_time = Column(DateTime, default=lambda: datetime.now())
code = Column(String)
name = Column(String)
data_dt = Column(String)
amt = Column(Float)
__table_args__ = (
engines.MergeTree(order_by=['id']),
)
然后获取所有的的code, 通过映射已创建表格的一部分字段,然后使用select来实现。
# 创建元数据对象
metadata = MetaData()
# 映射部分字段
# 定义表结构,并添加 extend_existing=True 参数
stock_name_list = Table('stock_name_list', metadata,
Column('code', Integer, primary_key=True),
Column('name', String),
extend_existing=True # 允许重新定义表
)
# 查询所有的code列
with engine.connect() as connection:
# 构建查询语句
query = select(stock_name_list.c.code)
# 执行查询
result = connection.execute(query)
# 获取所有结果
codes = [x[0] for x in result.fetchall()]
# 创建表
Base.metadata.create_all(engine)
# 创建会话
Session = sessionmaker(bind=engine)
将取数和存库封在一个函数里,入参是code
# 获取数据
def get_and_save(some_code):
import akshare as ak
stock_zh_a_hist_df = ak.stock_zh_a_hist(symbol=some_code, period="daily", start_date="20000101", end_date='20990528', adjust="")
stock_zh_a_hist_df['data_dt'] = stock_zh_a_hist_df['日期']
stock_zh_a_hist_df['amt'] = stock_zh_a_hist_df['成交额']
stock_zh_a_hist_df['code'] = stock_zh_a_hist_df['股票代码']
lod = stock_zh_a_hist_df[['data_dt','amt', 'code']].to_dict(orient='records')
data = [StockTrade(**x) for x in lod]
with Session() as session:
session.bulk_save_objects(data)
然后调用就可以了
import tqdm
for some_code in tqdm.tqdm(codes):
get_and_save(some_code)
速度比想象中的快
一共13M数据。
3 统计
可以感受一下clickhouse的统计速度(其实更强的是,即使是时分秒的状态,也可以随意抽出来统计)
大约200ms完成汇总计算。
将字符cast到年月统计
select toYYYYMM(toDateTime(data_dt)) AS year_month,sum(amt) as total_amt
from qtv200.stock_trade
GROUP BY year_month
ORDER BY year_month;
4 可视化及分析
先声明基本配置
from Basefuncs import *
import cufflinks as cf
# from plotly.offline import iplot, init_notebook_mode
from plotly.offline import iplot, init_notebook_mode
# 初始化cufflinks
cf.go_offline()
init_notebook_mode()
chcfg = CHCfg(database='qtv200')
click_para = chcfg.dict()
chc = CHClient(**click_para)
chc._exe_sql('show tables')
daily_sql = '''select data_dt, sum(amt) as total_amt
from stock_trade
group by data_dt
order by data_dt
'''
yymon_sql = '''
select toYYYYMM(toDateTime(data_dt)) AS year_month,sum(amt) as total_amt
from stock_trade
GROUP BY year_month
ORDER BY year_month;
'''
先看日数据,主要是校验算的是否对。看起来还是可以的。
daily_data = chc._exe_sql(daily_sql)
daily_data_df = pd.DataFrame(daily_data, columns = ['dt','amt'])
daily_data_df['amt_ww'] = daily_data_df['amt'] /1e8
daily_data_df.tail()
amt amt_ww
dt
2024-08-26 5.289036e+11 5289.036477
2024-08-27 5.139930e+11 5139.930070
2024-08-28 4.989520e+11 4989.519683
2024-08-29 6.099332e+11 6099.332157
2024-08-30 8.800362e+11 8800.362105
画图
daily_data_df.set_index('dt', inplace=True)
# 绘制图表
daily_data_df['amt_ww'].iplot( kind='line', xTitle='Date', yTitle='Amount', title='Amount Over Time')
整体看起来,似乎交易量也没想象的那么不堪
然后按月统计
yymon_data = chc._exe_sql(yymon_sql)
yymon_data_df = pd.DataFrame(yymon_data, columns = ['yymon','amt'])
yymon_data_df['yymon'] = yymon_data_df['yymon'].apply(str)
yymon_data_df['amt_ww'] = yymon_data_df['amt'] /1e8
yymon_data_df.tail()
yymon amt amt_ww
291 202404 1.828892e+13 182889.221341
292 202405 1.693721e+13 169372.120202
293 202406 1.371751e+13 137175.121734
294 202407 1.507760e+13 150776.035114
295 202408 1.313773e+13 131377.297951
yymon_data_df.set_index('yymon', inplace=True)
yymon_data_df['amt_ww'].iplot( kind='bar', xTitle='YYMon', yTitle='Amount', title='Amount Over Time')
放大一点看最近的,也没太大问题
5 结论
简单从交易量上来看,似乎也没有问题,但是这个仍然不符合直觉。说明支持度并不是单纯的交易量大小决定的。那么我想,是否应该调研价格的波动,以及交易的方向(净买入)。
所以,接下来还要进行的调研:
- 1 研究价格的波动。就全市场而言,研究每天指数的波动系数。
- 2 研究净买入方向。基于现在的全量交易额和指数涨跌,从净买入交易额来进行判定。