Skip to content

Commit

Permalink
V0.9.6 重要更新 (#116)
Browse files Browse the repository at this point in the history
* 0.9.6 start coding

* 0.9.6 Event / Factor 对象优化

* 0.9.6 Event / Factor 对象优化

* 0.9.6 Event / Factor 对象优化

* 0.9.6 update word writer

* 0.9.6 优化 dummy backtest 过程

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 删除 ts_backtest.py

* 0.9.6 优化 BI 对象的缓存机制

* 0.9.6 新增大小级别中枢共振信号函数

* 0.9.6 新增 CzscSignals 作为信号计算的基类

* 0.9.6 update

* 0.9.6 新增简单持仓对象

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 优化缓存机制

* 0.9.6 新增 qmt 工具集

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 update

* 0.9.6 更新单元测试

* 0.9.6 fix ta

* 0.9.6 fix ta
  • Loading branch information
zengbin93 authored Jan 1, 2023
1 parent b49a48e commit ecd5c81
Show file tree
Hide file tree
Showing 36 changed files with 2,118 additions and 1,968 deletions.
4 changes: 2 additions & 2 deletions czsc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
from czsc.utils.cache import home_path, get_dir_size, empty_cache_path


__version__ = "0.9.5"
__version__ = "0.9.6"
__author__ = "zengbin93"
__email__ = "[email protected]"
__date__ = "20221212"
__date__ = "20221221"


if envs.get_welcome():
Expand Down
132 changes: 11 additions & 121 deletions czsc/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,7 @@
https://click.palletsprojects.com/en/8.0.x/quickstart/
"""
import os
import click
import glob
import pandas as pd
from loguru import logger


Expand All @@ -28,153 +25,46 @@ def aphorism():
print_one()


@czsc.command()
@click.option('-f', '--file_strategy', type=str, required=True, help="Python择时策略文件路径")
def backtest(file_strategy):
"""使用 TradeSimulator 进行择时策略回测/仿真研究"""
from collections import Counter
from czsc.traders.ts_simulator import TradeSimulator
from czsc.utils import get_py_namespace

py = get_py_namespace(file_strategy)
results_path = os.path.join(py['results_path'], "backtest")
os.makedirs(results_path, exist_ok=True)

strategy = py['trader_strategy']
dc = py['dc']
symbols = py['symbols']

ts = TradeSimulator(dc, strategy, res_path=results_path)
counter = Counter([x.split("#")[1] for x in symbols])
for asset, c in counter.items():
ts_codes = [x.split("#")[0] for x in symbols if x.endswith(asset)]
ts.update_traders(ts_codes, asset)


@czsc.command()
@click.option('-f', '--file_strategy', type=str, required=True, help="Python择时策略文件路径")
def dummy(file_strategy):
"""使用 CzscDummyTrader 进行快速的择时策略研究"""
import shutil
from datetime import datetime
from czsc.traders.advanced import CzscDummyTrader
from czsc.sensors.utils import generate_symbol_signals
from czsc.utils import get_py_namespace, dill_dump, dill_load

py = get_py_namespace(file_strategy)
signals_path = os.path.join(py['results_path'], "signals")
results_path = os.path.join(py['results_path'], f"DEXP{datetime.now().strftime('%Y%m%d%H%M')}")
os.makedirs(signals_path, exist_ok=True)
os.makedirs(results_path, exist_ok=True)
shutil.copy(file_strategy, os.path.join(results_path, 'strategy.py'))

strategy = py['trader_strategy']
dc = py['dc']
symbols = py['symbols']

for symbol in symbols:
file_dfs = os.path.join(signals_path, f"{symbol}_signals.pkl")

try:
# 可以直接生成信号,也可以直接读取信号
if os.path.exists(file_dfs):
dfs = pd.read_pickle(file_dfs)
else:
ts_code, asset = symbol.split('#')
dfs = generate_symbol_signals(dc, ts_code, asset, "20170101", "20221001", strategy, 'hfq')
dfs.to_pickle(file_dfs)

cdt = CzscDummyTrader(dfs, strategy)
dill_dump(cdt, os.path.join(results_path, f"{symbol}.cdt"))

res = cdt.results
if "long_performance" in res.keys():
logger.info(f"{res['long_performance']}")

if "short_performance" in res.keys():
logger.info(f"{res['short_performance']}")
except:
logger.exception(f"fail on {symbol}")

# 汇总结果
tactic = strategy('symbol')
files = glob.glob(f"{results_path}/*.cdt")
if tactic.get("long_pos", None):
lpf = pd.DataFrame([dill_load(file).results['long_performance'] for file in files])
lpf.to_excel(os.path.join(results_path, f'{strategy.__doc__}多头回测结果.xlsx'), index=False)

if tactic.get("short_pos", None):
spf = pd.DataFrame([dill_load(file).results['short_performance'] for file in files])
spf.to_excel(os.path.join(results_path, f'{strategy.__doc__}空头回测结果.xlsx'), index=False)
from czsc.traders.dummy import DummyBacktest
dbt = DummyBacktest(file_strategy)
dbt.replay()
dbt.execute()
dbt.report()


@czsc.command()
@click.option('-f', '--file_strategy', type=str, required=True, help="Python择时策略文件路径")
def replay(file_strategy):
"""执行择时策略在某个品种上的交易回放"""
from czsc.data import freq_cn2ts
from czsc.utils import BarGenerator
from czsc.traders.utils import trade_replay
from czsc.utils import get_py_namespace

py = get_py_namespace(file_strategy)
strategy = py['trader_strategy']
dc = py['dc']
replay_params = py.get('replay_params', None)

if not replay_params:
logger.warning(f"{file_strategy} 中没有设置策略回放参数,将使用默认参数执行")

# 获取单个品种的基础周期K线
tactic = strategy("000001.SZ")
base_freq = tactic['base_freq']
symbol = replay_params.get('symbol', "000001.SZ#E")
ts_code, asset = symbol.split("#")
sdt = replay_params.get('sdt', '20150101')
edt = replay_params.get('edt', '20220101')
bars = dc.pro_bar_minutes(ts_code, sdt, edt, freq_cn2ts[base_freq], asset, adj="hfq")
logger.info(f"交易回放参数 | {symbol} - sdt:{sdt} - edt: {edt}")

# 设置回放快照文件保存目录
res_path = os.path.join(py['results_path'], f"replay_{symbol}")
os.makedirs(res_path, exist_ok=True)

# 拆分基础周期K线,一部分用来初始化BarGenerator,随后的K线是回放区间
start_date = pd.to_datetime(replay_params.get('mdt', '20200101'))
bg = BarGenerator(base_freq, freqs=tactic['freqs'])
bars1 = [x for x in bars if x.dt <= start_date]
bars2 = [x for x in bars if x.dt > start_date]
for bar in bars1:
bg.update(bar)

trade_replay(bg, bars2, strategy, res_path)
from czsc.traders.dummy import DummyBacktest
dbt = DummyBacktest(file_strategy)
dbt.replay()


@czsc.command()
@click.option('-f', '--file_strategy', type=str, required=True, help="Python择时策略文件路径")
@click.option('-d', '--delta_days', type=int, required=False, default=5, help="两次相同信号之间的间隔天数")
@click.option('-f', '--file_strategy', type=str, required=True, help="Python信号检查文件路径")
@click.option('-d', '--delta_days', type=int, required=False, default=1, help="两次相同信号之间的间隔天数")
def check(file_strategy, delta_days):
"""执行择时策略中使用的信号在某个品种上的校验"""
from czsc.data import freq_cn2ts
from czsc.sensors.utils import check_signals_acc
from czsc.utils import get_py_namespace

py = get_py_namespace(file_strategy)
strategy = py['trader_strategy']
dc = py['dc']
check_params = py.get('check_params', None)

if not check_params:
logger.warning(f"{file_strategy} 中没有设置策略回放参数,将使用默认参数执行")

# 获取单个品种的基础周期K线
tactic = strategy("000001.SZ")
base_freq = tactic['base_freq']
symbol = check_params.get('symbol', "000001.SZ#E")
ts_code, asset = symbol.split("#")
sdt = check_params.get('sdt', '20200101')
edt = check_params.get('edt', '20220101')
bars = dc.pro_bar_minutes(ts_code, sdt, edt, freq_cn2ts[base_freq], asset, adj="hfq")
bars = py['read_bars'](symbol, sdt, edt)
logger.info(f"信号检查参数 | {symbol} - sdt: {sdt} - edt: {edt}")

check_signals_acc(bars, strategy=strategy, delta_days=delta_days)
7 changes: 7 additions & 0 deletions czsc/connectors/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
"""
author: zengbin93
email: [email protected]
create_dt: 2022/12/31 16:02
describe: 常用第三方交易框架的连接器
"""
124 changes: 124 additions & 0 deletions czsc/connectors/qmt_connector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
"""
author: zengbin93
email: [email protected]
create_dt: 2022/12/31 16:03
describe:
"""
import pandas as pd
from typing import List
from czsc.objects import Freq, RawBar
from xtquant import xtdata


def format_stock_kline(kline: pd.DataFrame, freq: Freq) -> List[RawBar]:
"""QMT A股市场K线数据转换
:param kline: QMT 数据接口返回的K线数据
time open high low close volume amount \
0 2022-12-01 10:15:00 13.22 13.22 13.16 13.18 20053 26432861.0
1 2022-12-01 10:20:00 13.18 13.19 13.15 13.15 32667 43002512.0
2 2022-12-01 10:25:00 13.16 13.18 13.13 13.16 32466 42708049.0
3 2022-12-01 10:30:00 13.16 13.19 13.13 13.18 15606 20540461.0
4 2022-12-01 10:35:00 13.20 13.25 13.19 13.22 29959 39626170.0
symbol
0 000001.SZ
1 000001.SZ
2 000001.SZ
3 000001.SZ
4 000001.SZ
:param freq: K线周期
:return: 转换好的K线数据
"""
bars = []
dt_key = 'time'
kline = kline.sort_values(dt_key, ascending=True, ignore_index=True)
records = kline.to_dict('records')

for i, record in enumerate(records):
# 将每一根K线转换成 RawBar 对象
bar = RawBar(symbol=record['symbol'], dt=pd.to_datetime(record[dt_key]), id=i, freq=freq,
open=record['open'], close=record['close'], high=record['high'], low=record['low'],
vol=record['volume'] * 100 if record['volume'] else 0, # 成交量,单位:股
amount=record['amount'] if record['amount'] > 0 else 0, # 成交额,单位:元
)
bars.append(bar)
return bars


def get_local_kline(symbol, period, start_time, end_time, count=-1, dividend_type='none', data_dir=None, update=True):
"""获取 QMT 本地K线数据
:param symbol: 股票代码 例如:'300001.SZ'
:param period: 周期 分笔"tick" 分钟线"1m"/"5m" 日线"1d"
:param start_time: 开始时间,格式YYYYMMDD/YYYYMMDDhhmmss/YYYYMMDDhhmmss.milli,
例如:"20200427" "20200427093000" "20200427093000.000"
:param end_time: 结束时间 格式同上
:param count: 数量 -1全部,n: 从结束时间向前数n个
:param dividend_type: 除权类型"none" "front" "back" "front_ratio" "back_ratio"
:param data_dir: 下载QMT本地数据路径,如 D:/迅投极速策略交易系统交易终端/datadir
:param update: 更新QMT本地数据路径中的数据
:return: df Dataframe格式的数据,样例如下
time open high low close volume amount \
0 2022-12-01 10:15:00 13.22 13.22 13.16 13.18 20053 26432861.0
1 2022-12-01 10:20:00 13.18 13.19 13.15 13.15 32667 43002512.0
2 2022-12-01 10:25:00 13.16 13.18 13.13 13.16 32466 42708049.0
3 2022-12-01 10:30:00 13.16 13.19 13.13 13.18 15606 20540461.0
4 2022-12-01 10:35:00 13.20 13.25 13.19 13.22 29959 39626170.0
symbol
0 000001.SZ
1 000001.SZ
2 000001.SZ
3 000001.SZ
4 000001.SZ
"""
field_list = ['time', 'open', 'high', 'low', 'close', 'volume', 'amount']
if update:
xtdata.download_history_data(symbol, period, start_time='20100101', end_time='21000101')
local_data = xtdata.get_local_data(field_list, [symbol], period, count=count, dividend_type=dividend_type,
start_time=start_time, end_time=end_time, data_dir=data_dir)

df = pd.DataFrame({key: value.values[0] for key, value in local_data.items()})
df['time'] = pd.to_datetime(df['time'], unit='ms') + pd.to_timedelta('8H')
df.reset_index(inplace=True, drop=True)
df['symbol'] = symbol
return df


def get_symbols(step):
"""获取择时策略投研不同阶段对应的标的列表
:param step: 投研阶段
:return: 标的列表
"""
stocks = xtdata.get_stock_list_in_sector('沪深A股')
stocks_map = {
"index": ['000905.SH', '000016.SH', '000300.SH', '000001.SH', '000852.SH',
'399001.SZ', '399006.SZ', '399376.SZ', '399377.SZ', '399317.SZ', '399303.SZ'],
"stock": stocks.ts_code.to_list(),
"check": ['000001.SZ'],
"train": stocks[:200],
"valid": stocks[200:600],
"etfs": ['512880.SH', '518880.SH', '515880.SH', '513050.SH', '512690.SH',
'512660.SH', '512400.SH', '512010.SH', '512000.SH', '510900.SH',
'510300.SH', '510500.SH', '510050.SH', '159992.SZ', '159985.SZ',
'159981.SZ', '159949.SZ', '159915.SZ'],
}
return stocks_map[step]


def test_local_kline():
# 获取所有板块
slt = xtdata.get_sector_list()
stocks = xtdata.get_stock_list_in_sector('沪深A股')

df = get_local_kline(symbol='000001.SZ', period='1m', count=1000, dividend_type='front',
data_dir=r"D:\迅投极速策略交易系统交易终端 华鑫证券QMT实盘\datadir",
start_time='20200427', end_time='20221231', update=True)
assert not df.empty
# df = get_local_kline(symbol='000001.SZ', period='5m', count=1000, dividend_type='front',
# data_dir=r"D:\迅投极速策略交易系统交易终端 华鑫证券QMT实盘\datadir",
# start_time='20200427', end_time='20221231', update=False)
# df = get_local_kline(symbol='000001.SZ', period='1d', count=1000, dividend_type='front',
# data_dir=r"D:\迅投极速策略交易系统交易终端 华鑫证券QMT实盘\datadir",
# start_time='20200427', end_time='20221231', update=False)
15 changes: 1 addition & 14 deletions czsc/data/ts.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
from functools import partial
from loguru import logger

from ..analyze import RawBar
from ..enum import Freq
from czsc.objects import RawBar, Freq


# 数据频度 :支持分钟(min)/日(D)/周(W)/月(M)K线,其中1min表示1分钟(类推1/5/15/30/60分钟)。
Expand All @@ -25,18 +24,6 @@
Freq.F60: "60min", Freq.D: 'D', Freq.W: "W", Freq.M: "M"}
freq_cn_map = {"1分钟": Freq.F1, "5分钟": Freq.F5, "15分钟": Freq.F15, "30分钟": Freq.F30,
"60分钟": Freq.F60, "日线": Freq.D}
exchanges = {
"SSE": "上交所",
"SZSE": "深交所",
"CFFEX": "中金所",
"SHFE": "上期所",
"CZCE": "郑商所",
"DCE": "大商所",
"INE": "能源",
"IB": "银行间",
"XHKG": "港交所"
}

dt_fmt = "%Y-%m-%d %H:%M:%S"
date_fmt = "%Y%m%d"

Expand Down
Loading

0 comments on commit ecd5c81

Please sign in to comment.