Skip to content

Commit

Permalink
V0.9.11 更新QMT实盘对接代码 (#129)
Browse files Browse the repository at this point in the history
* 0.9.11 start coding

* 0.9.11 新增QMT实盘脚本样例

* 0.9.11 新增 get_unique_signals

* 0.9.11 update

* 0.9.11 fix bug

* 0.9.11 fix bug

* 0.9.11 action update

* 0.9.11 update

* 0.9.11 update

* 0.9.11 update

* 0.9.11 update

* 0.9.11 update

* 0.9.11 update

* 0.9.11 新增飞书API便捷接口

* 0.9.11 优化实盘接口

* 0.9.11 优化实盘接口

* 0.9.11 优化实盘接口

* 0.9.11 fix docs
  • Loading branch information
zengbin93 authored Mar 12, 2023
1 parent a77a480 commit e6703fe
Show file tree
Hide file tree
Showing 24 changed files with 613 additions and 105 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ name: Python package

on:
push:
branches: [ master, V0.9.10 ]
branches: [ master, V0.9.11 ]
pull_request:
branches: [ master ]

Expand Down
8 changes: 5 additions & 3 deletions czsc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,25 @@
"""
from czsc import envs
from czsc import ai
from czsc import fsa
from czsc import utils
from czsc import traders
from czsc import sensors
from czsc import aphorism
from czsc.analyze import CZSC
from czsc.objects import Freq, Operate, Direction, Signal, Factor, Event, RawBar, NewBar, Position
from czsc.utils.cache import home_path, get_dir_size, empty_cache_path
from czsc.traders import CzscTrader, CzscSignals, generate_czsc_signals, check_signals_acc
from czsc.traders import CzscTrader, CzscSignals, generate_czsc_signals, check_signals_acc, get_unique_signals
from czsc.traders import PairsPerformance, combine_holds_and_pairs, combine_dates_and_pairs, stock_holds_performance
from czsc.strategies import CzscStrategyBase
from czsc.utils import KlineChart, BarGenerator, resample_bars, dill_dump, dill_load, read_json, save_json
from czsc.utils import get_sub_elements, get_py_namespace, freqs_sorted, x_round, import_by_name, create_grid_params


__version__ = "0.9.10"
__version__ = "0.9.11"
__author__ = "zengbin93"
__email__ = "[email protected]"
__date__ = "20230228"
__date__ = "20230312"


if envs.get_welcome():
Expand Down
23 changes: 23 additions & 0 deletions czsc/analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,29 @@ def to_echarts(self, width: str = "1400px", height: str = '580px', bs=None):
title="{}-{}".format(self.symbol, self.freq.value))
return chart

def to_plotly(self):
"""使用 plotly 绘制K线分析图"""
import pandas as pd
from czsc.utils.plotly_plot import KlineChart

bi_list = self.bi_list
df = pd.DataFrame(self.bars_raw)
kline = KlineChart(n_rows=3, title="{}-{}".format(self.symbol, self.freq.value))
kline.add_kline(df, name="")
kline.add_sma(df, ma_seq=(5, 10, 21), row=1, visible=True, line_width=1.2)
kline.add_sma(df, ma_seq=(34, 55, 89, 144), row=1, visible=False, line_width=1.2)
kline.add_vol(df, row=2)
kline.add_macd(df, row=3)

if len(bi_list) > 0:
bi = pd.DataFrame([{'dt': x.fx_a.dt, "bi": x.fx_a.fx, "text": x.fx_a.mark.value} for x in bi_list] +
[{'dt': bi_list[-1].fx_b.dt, "bi": bi_list[-1].fx_b.fx,
"text": bi_list[-1].fx_b.mark.value[0]}])
fx = pd.DataFrame([{'dt': x.dt, "fx": x.fx} for x in self.fx_list])
kline.add_scatter_indicator(fx['dt'], fx['fx'], name="分型", row=1, line_width=2)
kline.add_scatter_indicator(bi['dt'], bi['bi'], name="笔", text=bi['text'], row=1, line_width=2)
return kline.fig

def open_in_browser(self, width: str = "1400px", height: str = '580px'):
"""直接在浏览器中打开分析结果
Expand Down
4 changes: 0 additions & 4 deletions czsc/connectors/gm_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@
dt_fmt = "%Y-%m-%d %H:%M:%S"
date_fmt = "%Y-%m-%d"

assert czsc.__version__ >= "0.9.8"


def set_gm_token(token):
__file_token = os.path.join(os.path.expanduser("~"), "gm_token.txt")
Expand Down Expand Up @@ -383,8 +381,6 @@ def gm_take_snapshot(gm_symbol, end_dt=None, file_html=None,
if file_html:
ct.take_snapshot(file_html)
print(f'saved into {file_html}')
else:
ct.open_in_browser()
return ct


Expand Down
61 changes: 36 additions & 25 deletions czsc/connectors/qmt_connector.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,15 +166,21 @@ def get_symbols(step):
return stocks_map[step]


def is_trade_time():
"""判断当前是否是A股交易时间"""
now = datetime.now().strftime("%H:%M")
if now < "09:15" or now > "15:00":
def is_trade_time(dt: datetime = datetime.now()):
"""判断指定时间是否是交易时间"""
hm = dt.strftime("%H:%M")
if hm < "09:15" or hm > "15:00":
return False
else:
return True


def is_trade_day(dt: datetime = datetime.now()):
"""判断指定日期是否是交易日"""
date = dt.strftime('%Y%m%d')
return True if xtdata.get_trading_dates('SH', date, date) else False


class TraderCallback(XtQuantTraderCallback):
"""基础回调类,主要是一些日志和IM通知功能"""

Expand Down Expand Up @@ -474,7 +480,7 @@ def cancel_timeout_orders(self, minutes=30):
"""
orders = self.query_stock_orders(cancelable_only=True)
for o in orders:
if o.order_time < datetime.now() - timedelta(minutes=minutes):
if datetime.fromtimestamp(o.order_time) < datetime.now() - timedelta(minutes=minutes):
self.xtt.cancel_order_stock(self.acc, o.order_id)

def is_order_exist(self, symbol, order_type, volume=None):
Expand Down Expand Up @@ -610,17 +616,17 @@ def update_traders(self):
for bar in news:
trader.on_bar(bar)

# 根据策略的交易信号,下单【股票只有多头】,只有当信号变化时才下单
if trader.get_ensemble_pos(method='vote') == 1 and trader.pos_changed \
and self.is_allow_open(symbol, price=news[-1].close):
assets = self.get_assets()
order_volume = min(self.symbol_max_pos * assets.total_asset, assets.cash) // news[-1].close
self.send_stock_order(stock_code=symbol, order_type=23, order_volume=order_volume)
# 根据策略的交易信号,下单【股票只有多头】,只有当信号变化时才下单
if trader.get_ensemble_pos(method='vote') == 1 and trader.pos_changed \
and self.is_allow_open(symbol, price=news[-1].close):
assets = self.get_assets()
order_volume = min(self.symbol_max_pos * assets.total_asset, assets.cash) // news[-1].close
self.send_stock_order(stock_code=symbol, order_type=23, order_volume=order_volume)

# 平多头
if trader.get_ensemble_pos(method='vote') == 0 and self.is_allow_exit(symbol):
order_volume = holds[symbol].can_use_volume
self.send_stock_order(stock_code=symbol, order_type=24, order_volume=order_volume)
# 平多头
if trader.get_ensemble_pos(method='vote') == 0 and self.is_allow_exit(symbol):
order_volume = holds[symbol].can_use_volume
self.send_stock_order(stock_code=symbol, order_type=24, order_volume=order_volume)

else:
logger.info(f"{symbol} 没有需要更新的K线,最新的K线时间是 {trader.end_dt}")
Expand All @@ -634,6 +640,7 @@ def update_traders(self):
self.traders[symbol] = trader

except Exception as e:
self.callback.push_message(f"{symbol} 更新交易策略失败,原因是 {e}")
logger.error(f"{symbol} 更新交易策略失败,原因是 {e}")

def update_offline_traders(self):
Expand All @@ -658,14 +665,15 @@ def update_offline_traders(self):
logger.info(f"{symbol} 需要更新的K线数量:{len(news)} | 最新的K线时间是 {news[-1].dt}")
for bar in news:
trader.on_bar(bar)
czsc.dill_dump(trader, file_trader)

# 根据策略的交易信号,下单【股票只有多头】,只有当信号变化时才下单
if trader.get_ensemble_pos(method='vote') == 1 and trader.pos_changed \
and self.is_allow_open(symbol, price=news[-1].close):
assets = self.get_assets()
order_volume = min(self.symbol_max_pos * assets.total_asset, assets.cash) // news[-1].close
self.send_stock_order(stock_code=symbol, order_type=23, order_volume=order_volume)
# 根据策略的交易信号,下单【股票只有多头】,只有当信号变化时才下单
if trader.get_ensemble_pos(method='vote') == 1 and trader.pos_changed \
and self.is_allow_open(symbol, price=news[-1].close):
assets = self.get_assets()
order_volume = min(self.symbol_max_pos * assets.total_asset, assets.cash) // news[-1].close
self.send_stock_order(stock_code=symbol, order_type=23, order_volume=order_volume)

czsc.dill_dump(trader, file_trader)

mean_pos = trader.get_ensemble_pos('mean')
if mean_pos == 0:
Expand Down Expand Up @@ -720,8 +728,11 @@ def report(self):
_res = []
for symbol, trader in self.traders.items():
if trader.get_ensemble_pos('mean') > 0:
_pos_str = "\n\n".join([f"{x.name}{x.pos}" for x in trader.positions if x.pos != 0])
_ops = [x.operates[-1] for x in trader.positions if x.pos != 0]
_ops_str = "\n\n".join([f"时间:{x['dt']}_价格:{x['price']}_描述:{x['op_desc']}" for x in _ops])
_res.append({'symbol': symbol, 'pos': round(trader.get_ensemble_pos('mean'), 3),
'positions': "\n\n".join([x.name for x in trader.positions if x.pos != 0])})
'positions': _pos_str, 'operates': _ops_str})
if _res:
writer.add_df_table(pd.DataFrame(_res).sort_values(by='pos', ascending=False))
else:
Expand All @@ -732,7 +743,7 @@ def report(self):
self.callback.push_message(file_docx, msg_type='file')
os.remove(file_docx)

def run(self, mode='30m', order_timeout=30):
def run(self, mode='30m', order_timeout=120):
"""运行策略"""
self.report()

Expand All @@ -750,7 +761,7 @@ def run(self, mode='30m', order_timeout=30):
now_dt = datetime.now().strftime("%H:%M")
self.cancel_timeout_orders(minutes=order_timeout)

if now_dt in _times:
if is_trade_day() and now_dt in _times:
self.update_traders()
self.report()
time.sleep(60)
Expand Down
75 changes: 73 additions & 2 deletions czsc/fsa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
describe:
"""
import requests
from loguru import logger
from czsc.fsa.base import request, FeishuApiBase
from czsc.fsa.spreed_sheets import SpreadSheets
from czsc.fsa.im import IM
Expand All @@ -25,6 +26,76 @@ def push_text(text: str, key: str) -> None:
try:
response = requests.post(api_send, json=data)
assert response.json()['StatusMessage'] == 'success'
except:
print(f"{data} - 文本消息推送失败")
except Exception as e:
logger.error(f"推送消息失败: {e}")


def read_feishu_sheet(spread_sheet_token: str, sheet_id: str = None, **kwargs):
"""读取飞书电子表格
id和token的获取,参考:https://open.feishu.cn/document/ukTMukTMukTM/uATMzUjLwEzM14CMxMTN/overview
:param spread_sheet_token: 电子表格token
:param sheet_id: 电子表格中指定 sheet 的 id
:param kwargs:
feishu_app_id: 飞书APP的app_id
feishu_app_secret: 飞书APP的app_secret
:return:
"""
ss = SpreadSheets(app_id=kwargs['feishu_app_id'], app_secret=kwargs['feishu_app_secret'])
if not sheet_id:
res = ss.get_sheets(spread_sheet_token)
sheet_id = res['data']['sheets'][0]['sheet_id']
df = ss.read_table(spread_sheet_token, sheet_id)
return df


def get_feishu_members_by_mobiles(mobiles: list, **kwargs):
"""根据手机号获取飞书用户id
飞书接口文档:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/contact-v3/user/batch_get_id
:param mobiles: 手机号列表
:param kwargs:
feishu_app_id: 飞书APP的app_id
feishu_app_secret: 飞书APP的app_secret
:return:
"""
fim = IM(app_id=kwargs['feishu_app_id'], app_secret=kwargs['feishu_app_secret'])
res = fim.get_user_id({"mobiles": mobiles})['data']['user_list']
return [x['user_id'] for x in res]


def push_message(msg: str, msg_type: str = 'text', **kwargs) -> None:
"""使用飞书APP批量推送消息
API介绍:https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/reference/im-v1/message/create
请求体构建: https://open.feishu.cn/document/uAjLw4CM/ukTMukTMukTM/im-v1/message/create_json
:param msg: 消息内容
:param msg_type: 消息类型,支持:text, image, file
:param kwargs:
feishu_app_id: 飞书APP的app_id
feishu_app_secret: 飞书APP的app_secret
feishu_members: 需要通知的飞书APP的成员列表,支持单个成员或多个成员,成员格式为:'user_id'或 'open_id'
:return:
"""
fim = IM(app_id=kwargs['feishu_app_id'], app_secret=kwargs['feishu_app_secret'])
members = kwargs['feishu_members']
if isinstance(members, str):
members = [members]

if fim and members:
for member in members:
try:
if msg_type == 'text':
fim.send_text(msg, member)
elif msg_type == 'image':
fim.send_image(msg, member)
elif msg_type == 'file':
fim.send_file(msg, member)
else:
logger.error(f"不支持的消息类型:{msg_type}")
except Exception as e:
logger.error(f"推送消息失败:{e}")

3 changes: 2 additions & 1 deletion czsc/signals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# ======================================================================================================================
# 以下是 0.9.1 开始的新标准下实现的信号函数,规范定义:
# 1. 前缀3个字符区分信号类别
# 2. 后缀 V221107 之类的标识同一个信号函数的版本
# 2. 后缀 V221107 之类的标识同一个信号函数的不同版本
# ======================================================================================================================

from czsc.signals.cxt import (
Expand All @@ -24,6 +24,7 @@
cxt_bi_end_V230222,
cxt_bi_end_V230224,
cxt_bi_base_V230228,
cxt_third_buy_V230228,
)


Expand Down
Loading

0 comments on commit e6703fe

Please sign in to comment.