quant1x-trader 0.1.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 王布衣
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.1
2
+ Name: quant1x-trader
3
+ Version: 0.1.5
4
+ Summary: Quant1X程序化自动化交易
5
+ Home-page: https://gitee.com/quant1x/trader
6
+ Author: WangFeng
7
+ Author-email: wangfengxy@sina.cn
8
+ License: MIT license
9
+ Keywords: quant1x auto trader
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ License-File: LICENSE
20
+ Requires-Dist: setuptools~=68.2.2
21
+ Requires-Dist: future~=0.18.3
22
+ Requires-Dist: numpy~=1.26.0
23
+ Requires-Dist: pandas~=2.1.1
24
+ Requires-Dist: PyYAML~=6.0.1
25
+ Requires-Dist: pywin32==306
26
+ Requires-Dist: uvicorn==0.18.1
27
+ Requires-Dist: fastapi~=0.103.1
28
+ Requires-Dist: path~=16.7.1
29
+ Requires-Dist: requests==2.31.0
30
+ Requires-Dist: loguru~=0.7.2
File without changes
@@ -0,0 +1,10 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ import os
4
+ import sys
5
+
6
+ quant1x_parent_path = __file__.split('quant1x')[0]
7
+ rootPath = os.path.abspath(quant1x_parent_path)
8
+ # print(rootPath)
9
+ sys.path.insert(0, rootPath)
10
+ # print(sys.path)
@@ -0,0 +1,14 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ import os
4
+ import sys
5
+
6
+ quant1x_parent_path = __file__.split('quant1x')[0]
7
+ rootPath = os.path.abspath(quant1x_parent_path)
8
+ sys.path.insert(0, rootPath)
9
+
10
+ from quant1x.autotrader.trader import auto_trader
11
+
12
+
13
+ if __name__ == '__main__':
14
+ sys.exit(auto_trader())
@@ -0,0 +1,141 @@
1
+ # -*- coding: UTF-8 -*-
2
+ import re
3
+ import threading
4
+ import time
5
+ from dataclasses import dataclass, field
6
+ from typing import List
7
+
8
+ TIME_FORMAT_TIMESTAMP = '%H:%M:%S'
9
+
10
+
11
+ class Singleton:
12
+ """
13
+ 线程安全的单例模式
14
+ """
15
+ _instance = None
16
+ _lock = threading.Lock()
17
+
18
+ def __new__(cls, *args, **kwargs):
19
+ with cls._lock:
20
+ if not cls._instance:
21
+ #cls._instance = super().__new__(cls, *args, **kwargs)
22
+ cls._instance = super().__new__(cls)
23
+ return cls._instance
24
+
25
+ def __init__(self):
26
+ pass
27
+
28
+
29
+ @dataclass
30
+ class TimeRange(object):
31
+ """
32
+ 时间范围
33
+ """
34
+ begin: str
35
+ end: str
36
+
37
+ def __init__(self, time_range: str):
38
+ """
39
+ 构造
40
+ :param time_range:
41
+ :return:
42
+ """
43
+ self.begin = ''
44
+ self.end = ''
45
+
46
+ time_range = time_range.strip()
47
+ list = re.split(r"[~-]\s*", time_range)
48
+ if len(list) != 2:
49
+ raise RuntimeError("非法的时间格式")
50
+ # TODO:时间格式校验
51
+ # 时间排序
52
+ self.begin = list[0].strip()
53
+ self.end = list[1].strip()
54
+ if self.begin > self.end:
55
+ self.begin, self.end = self.end, self.begin
56
+
57
+ def is_trading(self, timestamp: str = "") -> bool:
58
+ """
59
+ 是否交易中
60
+ :param timestamp:
61
+ :return:
62
+ """
63
+ timestamp = timestamp.strip()
64
+ if len(timestamp) == 0:
65
+ timestamp = time.strftime(TIME_FORMAT_TIMESTAMP)
66
+ if self.begin <= timestamp <= self.end:
67
+ return True
68
+ return False
69
+
70
+ def is_valid(self) -> bool:
71
+ """
72
+ 时段是否有效
73
+ :return:
74
+ """
75
+ return self.begin != '' and self.end != ''
76
+
77
+
78
+ @dataclass
79
+ class TradingSession:
80
+ """
81
+ 交易时段
82
+ """
83
+ sessions: List
84
+
85
+ def __init__(self, time_range: str):
86
+ """
87
+ 构造
88
+ :param time_range:
89
+ """
90
+ self.sessions = []
91
+ time_range = time_range.strip()
92
+ list = re.split(r",\s*", time_range)
93
+ for v in list:
94
+ v = v.strip()
95
+ r = TimeRange(v)
96
+ self.sessions.append(r)
97
+
98
+ # def __str__(self):
99
+ # sb = []
100
+ # for v in self.sessions:
101
+ # sb.append(v)
102
+ # return sb.__str__()
103
+
104
+ def is_trading(self, timestamp: str = "") -> bool:
105
+ """
106
+ 是否交易中
107
+ :param timestamp:
108
+ :return:
109
+ """
110
+ timestamp = timestamp.strip()
111
+ if len(timestamp) == 0:
112
+ timestamp = time.strftime(TIME_FORMAT_TIMESTAMP)
113
+ for item in self.sessions:
114
+ v = item
115
+ if v.is_trading(timestamp):
116
+ return True
117
+ return False
118
+
119
+ def is_valid(self) -> bool:
120
+ """
121
+ 时段是否有效
122
+ :return:
123
+ """
124
+ for item in self.sessions:
125
+ if not item.is_valid():
126
+ return False
127
+ return True
128
+
129
+
130
+ if __name__ == '__main__':
131
+ time_range = " 09:30:00 ~ 14:56:30 "
132
+ time_range = " 14:56:30 - 09:30:00 "
133
+ tr = TimeRange(time_range)
134
+ print(tr)
135
+
136
+ time_range = "11:30:00 ~ 09:15:00 , 15:00:00 - 13:00:00 "
137
+ time_range = "15:00:00 - 13:00:00 "
138
+
139
+ tr = TradingSession(time_range)
140
+ print(tr)
141
+ print(tr.is_trading('09:30:00'))
@@ -0,0 +1,83 @@
1
+ # coding=utf-8
2
+
3
+ from xtquant.xttrader import XtQuantTraderCallback
4
+
5
+
6
+ class QmtTraderCallback(XtQuantTraderCallback):
7
+ def on_disconnected(self):
8
+ """
9
+ 连接断开
10
+ :return:
11
+ """
12
+ print("connection lost")
13
+
14
+ def on_stock_order(self, order):
15
+ """
16
+ 委托回报推送
17
+ :param order: XtOrder对象
18
+ :return:
19
+ """
20
+ print("on order callback:")
21
+ print(order.stock_code, order.order_status, order.order_sysid)
22
+
23
+ def on_stock_asset(self, asset):
24
+ """
25
+ 资金变动推送 注意,该回调函数目前不生效
26
+ :param asset: XtAsset对象
27
+ :return:
28
+ """
29
+ print("on asset callback")
30
+ print(asset.account_id, asset.cash, asset.total_asset)
31
+
32
+ def on_stock_trade(self, trade):
33
+ """
34
+ 成交变动推送
35
+ :param trade: XtTrade对象
36
+ :return:
37
+ """
38
+ print("on trade callback")
39
+ print(trade.account_id, trade.stock_code, trade.order_id)
40
+
41
+ def on_stock_position(self, position):
42
+ """
43
+ 持仓变动推送 注意,该回调函数目前不生效
44
+ :param position: XtPosition对象
45
+ :return:
46
+ """
47
+ print("on position callback")
48
+ print(position.stock_code, position.volume)
49
+
50
+ def on_order_error(self, order_error):
51
+ """
52
+ 委托失败推送
53
+ :param order_error:XtOrderError 对象
54
+ :return:
55
+ """
56
+ print("on order_error callback")
57
+ print(order_error.order_id, order_error.error_id, order_error.error_msg)
58
+
59
+ def on_cancel_error(self, cancel_error):
60
+ """
61
+ 撤单失败推送
62
+ :param cancel_error: XtCancelError 对象
63
+ :return:
64
+ """
65
+ print("on cancel_error callback")
66
+ print(cancel_error.order_id, cancel_error.error_id, cancel_error.error_msg)
67
+
68
+ def on_order_stock_async_response(self, response):
69
+ """
70
+ 异步下单回报推送
71
+ :param response: XtOrderResponse 对象
72
+ :return:
73
+ """
74
+ print("on_order_stock_async_response")
75
+ print(response.account_id, response.order_id, response.seq)
76
+
77
+ def on_account_status(self, status):
78
+ """
79
+ :param status: status账号状态
80
+ :return:
81
+ """
82
+ print("on_account_status")
83
+ print(status.account_id, status.account_type, status.status)
@@ -0,0 +1,91 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ from dataclasses import dataclass, field
4
+
5
+ import yaml
6
+
7
+ from quant1x.autotrader.base import TradingSession
8
+ from quant1x.autotrader.logger import logger
9
+ from quant1x.autotrader import env
10
+
11
+
12
+ @dataclass
13
+ class TraderConfig:
14
+ # 时间范围 - 持仓卖出
15
+ ask_time: TradingSession = TradingSession("09:51:00~14:59:30")
16
+ # 时间范围 - 撤销订单
17
+ cancel_time: TradingSession = TradingSession("09:15:00~09:19:59, 09:30:00~14:56:59")
18
+ # 时间范围 - 盘中订单
19
+ tick_time: TradingSession = TradingSession("09:30:00-14:57:00")
20
+ # 买入持仓率, 资金控制阀值
21
+ position_ratio: float = 0.5000
22
+ # 买入交易费率
23
+ buy_trade_rate: float = 0.0250
24
+ # TODO: 废弃, 相对开盘价溢价多少买入
25
+ buy_premium_rate: float = 0.0200
26
+ # 印花税, 按照成交金额, 买入没有, 卖出0.1%
27
+ stamp_duty_rate: float = 0.0010
28
+ # 过户费, 按照数量收取, 默认万分之六, 0.06%
29
+ transfer_rate: float = 0.0006
30
+ # 券商佣金, 按成交金额计算, 默认万分之二点五, 0.025%
31
+ commission_rate: float = 0.00025
32
+ # tick订单最大金额
33
+ tick_order_max_amount: float = 10000.00
34
+ # 买入最大金额
35
+ buy_amount_max: float = 250000.00
36
+
37
+ def __fix_instance(self):
38
+ """
39
+ 加载后修复
40
+ :return:
41
+ """
42
+ if isinstance(self.ask_time, str):
43
+ ts = TradingSession(self.ask_time)
44
+ if ts.is_valid():
45
+ self.ask_time = ts
46
+ if isinstance(self.cancel_time, str):
47
+ ts = TradingSession(self.cancel_time)
48
+ if ts.is_valid():
49
+ self.cancel_time = ts
50
+ if isinstance(self.tick_time, str):
51
+ ts = TradingSession(self.tick_time)
52
+ if ts.is_valid():
53
+ self.tick_time = ts
54
+
55
+ def __post_init__(self):
56
+ """
57
+ __init__()后调用, 调整类型
58
+ :return:
59
+ """
60
+ self.__fix_instance()
61
+
62
+
63
+ def load() -> TraderConfig:
64
+ """
65
+ 加载配置文件
66
+ :return:
67
+ """
68
+ consts = TraderConfig()
69
+ config_filename = env.get_quant1x_config_filename()
70
+ logger.info(config_filename)
71
+ try:
72
+ with open(config_filename, 'r', encoding='utf-8') as f:
73
+ config_dict = yaml.load(f, Loader=yaml.FullLoader)
74
+ if isinstance(config_dict, dict):
75
+ consts = TraderConfig(**config_dict['trader'])
76
+ except FileNotFoundError:
77
+ logger.error(f"文件未找到:{config_filename}")
78
+ except IsADirectoryError:
79
+ logger.error(f"路径是一个目录:{config_filename}")
80
+ except PermissionError as e:
81
+ logger.error(f"没有权限访问文件:{config_filename}\n错误信息:{e}")
82
+ except Exception as e:
83
+ logger.error(f"发生了一个错误:{config_filename}\n错误信息:{e}")
84
+ # finally:
85
+ # logger.warning('系统将使用默认配置')
86
+ return consts
87
+
88
+
89
+ if __name__ == '__main__':
90
+ config = load()
91
+ print(config)
@@ -0,0 +1,159 @@
1
+ # coding=utf-8
2
+ import os
3
+ import platform
4
+ import time
5
+
6
+ import pandas as pd
7
+ import yaml
8
+ from xtquant.xttype import StockAccount
9
+
10
+ from quant1x.autotrader import utils, env
11
+ from quant1x.autotrader.logger import logger
12
+
13
+
14
+ class QmtContext(object):
15
+ """
16
+ QMT 上下文
17
+ """
18
+ current_date: str # 当前日期
19
+ config_filename: str # 配置文件名
20
+ order_path: str # 运行路径
21
+ account_id: str # 账号ID
22
+ t89k_order_file: str # 订单文件
23
+ t89k_flag_ready: str # 订单就绪标志
24
+ t89k_flag_done: str # 订单执行完成标志
25
+
26
+ def __init__(self):
27
+ self.current_date = time.strftime(utils.kFormatFileDate)
28
+ self.config_filename = env.get_quant1x_config_filename()
29
+ logger.info('配置文件:{}', self.config_filename)
30
+ if not os.path.isfile(self.config_filename):
31
+ print('QMT config %s: 不存在' % self.config_filename)
32
+ exit(utils.errno_config_not_exist)
33
+ with open(self.config_filename, 'r', encoding='utf-8') as f:
34
+ result = yaml.load(f.read(), Loader=yaml.FullLoader)
35
+ self.account_id = str(result['order']['account_id'])
36
+ self.order_path = str(result['order']['order_path'])
37
+ self.switch_date()
38
+
39
+ def account(self) -> StockAccount:
40
+ return StockAccount(self.account_id)
41
+
42
+ def sell_is_ready(self) -> bool:
43
+ """
44
+ 卖出条件是否就绪
45
+ :return:
46
+ """
47
+ timestamp = time.strftime(utils.kTimestamp)
48
+ return utils.ask_begin <= timestamp <= utils.ask_end
49
+
50
+ def buy_is_ready(self) -> bool:
51
+ """
52
+ 买入订单是否准备就绪
53
+ :return:
54
+ """
55
+ if not os.path.isfile(self.t89k_flag_ready) or not os.path.isfile(self.t89k_order_file):
56
+ return False
57
+ return True
58
+
59
+ def load_order(self) -> pd.DataFrame:
60
+ """
61
+ 加载订单
62
+ :return:
63
+ """
64
+ df = pd.read_csv(self.t89k_order_file)
65
+ return df
66
+
67
+ def switch_date(self):
68
+ """
69
+ 重置属性
70
+ :return:
71
+ """
72
+ logger.warning("switch_date...")
73
+ self.current_date = time.strftime(utils.kFormatFileDate)
74
+ logger.warning("switch_date...{}", self.current_date)
75
+ flag = 'head'
76
+ self.t89k_flag_ready = os.path.join(self.order_path, f'{self.current_date}-{flag}.ready')
77
+ self.t89k_flag_done = os.path.join(self.order_path, f'{self.current_date}-{flag}-{self.account_id}.done')
78
+ self.t89k_order_file = os.path.join(self.order_path, f'{self.current_date}-{flag}.csv')
79
+
80
+ def order_buy_completed(self):
81
+ """
82
+ 买入操作完成
83
+ :return:
84
+ """
85
+ self._push_local_message(self.t89k_flag_done)
86
+ print('订单买入操作完成')
87
+
88
+ def check_buy_order_done_status(self, code: str) -> bool:
89
+ """
90
+ 检查买入订单执行完成状态
91
+ :return:
92
+ """
93
+ flag = self.get_order_flag(code, 1)
94
+ return os.path.exists(flag)
95
+
96
+ def push_buy_order_done_status(self, code: str):
97
+ """
98
+ 推送买入订单完成状态
99
+ :param ctx:
100
+ :param code:
101
+ :return:
102
+ """
103
+ flag = self.get_order_flag(code, 1)
104
+ self._push_local_message(flag)
105
+
106
+ def _push_local_message(self, filename: str):
107
+ """
108
+ 推送消息
109
+ :param filename:
110
+ :return:
111
+ """
112
+ with open(filename, 'w') as done_file:
113
+ pass
114
+
115
+ def get_order_flag(self, code: str, type: int) -> str:
116
+ """
117
+ 获取订单标识
118
+ :param self:
119
+ :param code:
120
+ :param type: 1-b,2-s
121
+ :return:
122
+ """
123
+ today = time.strftime(utils.kFormatFileDate)
124
+ order_type = "b" if type == 1 else "s"
125
+ stock_order_flag = os.path.join(self.order_path, f'{today}-{self.account_id}-{code}-{order_type}.done')
126
+ return stock_order_flag
127
+
128
+ def fix_security_code(self, symbol: str) -> str:
129
+ """
130
+ 调整证券代码
131
+ :param symbol:
132
+ :return:
133
+ """
134
+ security_code = ''
135
+ if len(symbol) == 6:
136
+ flag = self.get_security_type(symbol)
137
+ security_code = f'{symbol}.{flag}'
138
+ elif len(symbol) == 8 and symbol[:2] in ["sh", "sz", "SH", "SZ"]:
139
+ security_code = symbol[2:] + '.' + symbol[:2].upper()
140
+ else:
141
+ raise utils.errBadSymbol
142
+ return security_code
143
+
144
+ def get_security_type(self, symbol: str) -> str:
145
+ """
146
+ 获取股票市场标识
147
+ :param symbol: 代码
148
+ :return:
149
+ """
150
+ if len(symbol) != 6:
151
+ raise utils.errBadSymbol
152
+ code_head = symbol[:2]
153
+ if code_head in ["00", "30"]:
154
+ return "SZ"
155
+ if code_head in ["60", "68"]: # 688XXX科创板
156
+ return "SH"
157
+ if code_head in ["510"]:
158
+ return "SH"
159
+ raise utils.errBadSymbol
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/python
2
+ # -*- coding: UTF-8 -*-
3
+
4
+ import getpass
5
+ import os
6
+ import platform
7
+
8
+ import win32com.client
9
+
10
+
11
+ def get_quant1x_config_filename() -> str:
12
+ """
13
+ 获取quant1x.yaml文件路径
14
+ :return:
15
+ """
16
+ # 默认配置文件名
17
+ default_config_filename = 'quant1x.yaml'
18
+
19
+ yaml_filename = '~/runtime/etc/' + default_config_filename
20
+ system = platform.system().lower()
21
+ if system == 'windows':
22
+ user_home = os.getenv("GOX_HOME")
23
+ if len(user_home) > 0:
24
+ user_home = user_home.strip()
25
+ if len(user_home) == 0:
26
+ user_home = '~'
27
+ quant1x_root = user_home + '/' + '.quant1x'
28
+ yaml_filename = os.path.expanduser(quant1x_root + '/' + default_config_filename)
29
+ yaml_filename = os.path.expanduser(yaml_filename)
30
+ return yaml_filename
31
+
32
+
33
+ def get_gjzq_qmt_exec_path() -> str:
34
+ """
35
+ 获取QMT安装路径
36
+ """
37
+ username = getpass.getuser() # 当前用户名
38
+ qmt_exec_lnk = rf'C:\Users\{username}\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\国金证券QMT交易端\启动国金证券QMT交易端.lnk'
39
+ shell = win32com.client.Dispatch("WScript.Shell")
40
+ shortcut = shell.CreateShortCut(qmt_exec_lnk)
41
+ # print(shortcut.Targetpath)
42
+ target_path = str(shortcut.Targetpath)
43
+ paths = target_path.split(r'\bin.x64')
44
+ exec_path = os.path.expanduser(paths[0])
45
+ exec_path = exec_path.replace('\\', '/')
46
+ return exec_path
47
+
48
+
49
+ if __name__ == '__main__':
50
+ path = get_gjzq_qmt_exec_path()
51
+ print(path)
@@ -0,0 +1,4 @@
1
+ # -*- coding: UTF-8 -*-
2
+ from loguru import logger as __logger
3
+
4
+ logger = __logger
@@ -0,0 +1,51 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ BadSymbolError = RuntimeError("无法识别的证券代码")
4
+
5
+ # 市场缩写
6
+ MARKET_SHANGHAI = 'SH' # 上海
7
+ MARKET_SHENZHEN = 'SZ' # 深圳
8
+ MARKET_HONGKONG = 'HK' # 香港
9
+
10
+ # 市场缩写元组
11
+ tup_market = (MARKET_SHANGHAI, MARKET_SHENZHEN)
12
+ # 上海交易所证券代码前缀
13
+ tup_prefix_shanghai = ('60', '68', '510')
14
+ # 深圳交易所证券代码前缀
15
+ tup_prefix_shenzhen = ('00', '30')
16
+
17
+
18
+ def get_security_type(symbol: str) -> str:
19
+ """
20
+ 获取股票市场标识
21
+ :param symbol: 代码
22
+ :return:
23
+ """
24
+ symbol = symbol.strip()
25
+ code = symbol.upper()
26
+ if code.startswith(tup_market):
27
+ return code[:2].upper()
28
+ elif code.endswith(tup_market):
29
+ return code[:-2].upper()
30
+ elif code.startswith(tup_prefix_shanghai):
31
+ return MARKET_SHANGHAI
32
+ elif code.startswith(tup_prefix_shenzhen):
33
+ return MARKET_SHENZHEN
34
+ raise BadSymbolError
35
+
36
+
37
+ def fix_security_code(symbol: str) -> str:
38
+ """
39
+ 调整证券代码
40
+ :param symbol:
41
+ :return:
42
+ """
43
+ security_code = ''
44
+ if len(symbol) == 6:
45
+ flag = get_security_type(symbol)
46
+ security_code = f'{symbol}.{flag}'
47
+ elif len(symbol) == 8 and symbol[:2] in ["sh", "sz", "SH", "SZ"]:
48
+ security_code = symbol[2:] + '.' + symbol[:2].upper()
49
+ else:
50
+ raise BadSymbolError
51
+ return security_code
@@ -0,0 +1,209 @@
1
+ # -*- coding: UTF-8 -*-
2
+ import math
3
+ from typing import Tuple, Any
4
+
5
+ from xtquant import xtdata
6
+ from xtquant.xttrader import XtQuantTrader
7
+ from xtquant.xttype import *
8
+
9
+ from quant1x.autotrader import env, utils
10
+ from quant1x.autotrader.base import *
11
+ from quant1x.autotrader.config import TraderConfig
12
+ from quant1x.autotrader.logger import logger
13
+
14
+
15
+ class ThinkTrader(Singleton):
16
+ """
17
+ 迅投XtQuant-miniQMT交易
18
+ """
19
+ xt_trader = None
20
+ account = None
21
+
22
+ def __init__(self, conf: TraderConfig):
23
+ """
24
+ 初始化
25
+ :param conf:
26
+ """
27
+ super().__init__()
28
+ # self._ctx = None
29
+ self._config = conf
30
+
31
+ def stop(self):
32
+ """
33
+ 析构方法, 销毁对象
34
+ """
35
+ if self.xt_trader is not None:
36
+ self.xt_trader.stop()
37
+ logger.info("thinktrader shutdown")
38
+
39
+ def set_trader(self, qmt_dir: str = '', session_id: int = 0) -> int:
40
+ qmt_dir.strip()
41
+ if qmt_dir == '':
42
+ qmt_dir = env.get_gjzq_qmt_exec_path() + '/userdata_mini'
43
+ logger.info("miniQmt: {}", qmt_dir)
44
+ if session_id == 0:
45
+ # session_id为会话编号,策略使用方对于不同的Python策略需要使用不同的会话编号
46
+ now = time.time()
47
+ session_id = int(now)
48
+ logger.info("session id: {}", session_id)
49
+ self.xt_trader = XtQuantTrader(qmt_dir, session_id)
50
+ # 启动交易线程
51
+ self.xt_trader.start()
52
+ # 建立交易连接,返回0表示连接成功
53
+ connect_result = self.xt_trader.connect()
54
+ return connect_result
55
+
56
+ def set_account(self, account_id, account_type='STOCK'):
57
+ self.account = StockAccount(account_id, account_type=account_type)
58
+ return self.account
59
+
60
+ @property
61
+ def get_account(self):
62
+ return self.account
63
+
64
+ @property
65
+ def get_trader(self):
66
+ return self.xt_trader
67
+
68
+ def query_asset(self):
69
+ """
70
+ 获取资产数据
71
+ :return:
72
+ """
73
+ return self.xt_trader.query_stock_asset(self.get_account)
74
+
75
+ def single_available(self, total: int) -> float:
76
+ """
77
+ 调整单一可用资金
78
+ :param asset:
79
+ :param total:
80
+ :return:
81
+ """
82
+ asset = self.query_asset()
83
+ if asset is None:
84
+ return 0.00
85
+ # 总资产
86
+ quant_balance = asset.total_asset
87
+ # 修订可以量化用金额
88
+ quant_available = asset.cash
89
+ if quant_available / quant_balance > self._config.position_ratio:
90
+ quant_available = quant_balance * self._config.position_ratio
91
+ if total > 0:
92
+ single_funds_available = quant_available / total
93
+ else:
94
+ single_funds_available = 0.00
95
+ # 检查最大值
96
+ if single_funds_available > self._config.buy_amount_max:
97
+ single_funds_available = self._config.buy_amount_max
98
+ return single_funds_available
99
+
100
+ def available_amount(self, stock_total: int) -> float:
101
+ """
102
+ 计算单一标的可用金额
103
+ :param stock_total: 股票总数
104
+ :return:
105
+ """
106
+ single_funds_available = self.single_available(stock_total)
107
+ if single_funds_available <= 100:
108
+ return 0.00
109
+ # 扣除可能发生的交易费率
110
+ single_funds_available = single_funds_available * (1 - self._config.buy_trade_rate)
111
+ if single_funds_available > self._config.tick_order_max_amount:
112
+ # 超出81号策略单只个股最大买入金额
113
+ single_funds_available = self._config.tick_order_max_amount
114
+ return single_funds_available
115
+
116
+ def get_snapshot(self, security_code: str = '') -> Any:
117
+ """
118
+ 获得快照
119
+ :param security_code:
120
+ :return:
121
+ """
122
+ tick_list = xtdata.get_full_tick([security_code])
123
+ if len(tick_list) != 1:
124
+ return None
125
+ snapshot = tick_list[security_code]
126
+ return snapshot
127
+
128
+ def available_price(self, price: float) -> float:
129
+ """
130
+ 计算适合的买入价格
131
+ :param price:
132
+ :return:
133
+ """
134
+ lastPrice = price
135
+ # 价格笼子, +2%和+0.10哪个大
136
+ buy_price = max(lastPrice * 1.02, lastPrice + 0.10)
137
+ # 当前价格+0.05
138
+ # buy_price = snapshot['askPrice'][0] + 0.05
139
+ buy_price = lastPrice + 0.05
140
+ # 最后修订价格
141
+ buy_price = utils.price_round(buy_price)
142
+ return buy_price
143
+
144
+ def calculate_stock_volumes(self, fund: float, price: float) -> int:
145
+ """
146
+ 可以购买的股票数量(股)
147
+ :return: 股票数量
148
+ """
149
+ # 1. 印花税, 按照成交金额计算, 买入没有, 卖出, 0.1%
150
+ # stamp_duty = amount * stamp_duty_rate
151
+ _stamp_duty_fee = price * self._config.stamp_duty_rate
152
+ # 2. 过户费, 按照股票数量, 双向, 0.06%
153
+ # transfer_fee = volume * transfer_rate
154
+ _transfer_fee = self._config.transfer_rate
155
+ # 3. 券商佣金, 按照成交金额计算, 双向, 0.025%
156
+ # commissions = amount * commission_rate
157
+ _commission_fee = price * self._config.commission_rate
158
+ volume = fund / (_stamp_duty_fee + _transfer_fee + _commission_fee)
159
+ volume = math.floor(volume / 100) * 100
160
+ return volume
161
+
162
+ def buy(self, code: str, price: float, vol: int, strategy_name='', order_remark='') -> int:
163
+ """
164
+ 同步下买单
165
+ """
166
+ order_id = self.xt_trader.order_stock(self.account, code, xtconstant.STOCK_BUY, vol, xtconstant.FIX_PRICE,
167
+ price, strategy_name, order_remark)
168
+ return order_id
169
+
170
+ def tick_order_can_trade(self) -> bool:
171
+ """
172
+ 检查盘中订单是否可以交易
173
+ :return:
174
+ """
175
+ return self._config.tick_time.is_trading()
176
+
177
+ def tick_order_is_ready(self) -> bool:
178
+ """
179
+ 盘中订单是否就绪
180
+ :return:
181
+ """
182
+ return True
183
+
184
+ def current_date(self) -> tuple[str, str]:
185
+ """
186
+ 今天
187
+ :return:
188
+ """
189
+ today = time.strftime(utils.kFormatOnlyDate)
190
+ v = xtdata.get_market_last_trade_date('SH')
191
+ local_time = time.localtime(v / 1000)
192
+ trade_date = time.strftime(utils.kFormatOnlyDate, local_time)
193
+ return today, trade_date
194
+
195
+ def today_is_trading_date(self) -> bool:
196
+ """
197
+ 今天是否交易日
198
+ :return:
199
+ """
200
+ (today, trade_date) = self.current_date()
201
+ logger.info('today={}, trade_date={}', today, trade_date)
202
+ return today == trade_date
203
+
204
+ def order_can_cancel(self) -> bool:
205
+ """
206
+ 委托订单可以撤销
207
+ :return:
208
+ """
209
+ return self._config.cancel_time.is_trading()
@@ -0,0 +1,126 @@
1
+ # -*- coding: UTF-8 -*-
2
+
3
+ import os
4
+ import time
5
+
6
+ import pandas as pd
7
+
8
+ from quant1x.autotrader import market, utils, context, thinktrader, config
9
+ from quant1x.autotrader.logger import logger
10
+
11
+ # 应用名称
12
+ application = 'quant1x-trader'
13
+
14
+
15
+ def auto_trader() -> int:
16
+ """
17
+ 自动化交易入口
18
+ """
19
+ logger.info('{} start...', application)
20
+ # 0. 加载配置文件
21
+ logger.info('加载配置...')
22
+ conf = config.load()
23
+ logger.info('配置信息: {}', conf)
24
+ logger.info('加载配置...OK')
25
+ trader = thinktrader.ThinkTrader(conf)
26
+ # 1. 连接miniQMT
27
+ connect_result = trader.set_trader()
28
+ if connect_result == 0:
29
+ logger.info('connect miniQmt: success')
30
+ else:
31
+ logger.error('connect miniQmt: failed')
32
+ return utils.errno_miniqmt_connect_failed
33
+ logger.info('{} start...OK', application)
34
+ # 2. 设置账号
35
+ ctx = context.QmtContext()
36
+ trader.set_account(ctx.account_id)
37
+ # 3. 盘中交易流程
38
+ # 3.3 检测新增标的
39
+ logger.info('订单路径: {}', ctx.order_path)
40
+ last_mtime = 0
41
+ date_updated = False
42
+ while True:
43
+ time.sleep(1)
44
+ logger.info("检测[交易日]...")
45
+ # 3.3.1 检测当前日期是否最后一个交易日
46
+ (today, trade_date) = trader.current_date()
47
+ if today != trade_date:
48
+ logger.error('today={}, trade_date={}, 非交易日', today, trade_date)
49
+ continue
50
+ elif not date_updated:
51
+ # 如果状态还没调整
52
+ logger.error('today={}, trade_date={}, 当前日期为交易日, 等待开市', today, trade_date)
53
+ ctx.switch_date()
54
+ date_updated = True
55
+ logger.info("检测[交易时段]...")
56
+ if not trader.tick_order_can_trade():
57
+ logger.info('非盘中交易时段, waiting...')
58
+ continue
59
+ # 3.3.2 检测新标的
60
+ logger.warning('检测新增标的...')
61
+ filename_stock_pool = ctx.order_path + '/stock_pool.csv'
62
+ update_time = os.path.getmtime(filename_stock_pool)
63
+ if update_time == last_mtime:
64
+ logger.warning('检测新增标的...无变化')
65
+ continue
66
+ else:
67
+ last_mtime = update_time
68
+ mtime = time.localtime(last_mtime)
69
+ timestamp = time.strftime(utils.kFormatTimestamp, mtime)
70
+ logger.info('{} last modify: {}', filename_stock_pool, timestamp)
71
+ # 3.3.3 检查当日所有的订单
72
+ df = pd.read_csv(filename_stock_pool)
73
+ if len(df) == 0:
74
+ continue
75
+ # 过滤条件: 当日订单且策略编号为81
76
+ condition = (df['date'] == today) & (df['strategy_code'] == 81)
77
+ tick_orders = df[condition]
78
+ if len(tick_orders) == 0:
79
+ continue
80
+ stock_total = len(tick_orders)
81
+ logger.warning('盘中水位观测: {}', stock_total)
82
+ # 遍历订单
83
+ for idx, stock in tick_orders.iterrows():
84
+ # print(stock)
85
+ date = stock['date']
86
+ code = stock['code']
87
+ # 检查买入状态
88
+ if ctx.check_buy_order_done_status(code):
89
+ # 已经买入跳过
90
+ continue
91
+ strategy_code = stock['strategy_code']
92
+ strategy_name = stock['strategy_name']
93
+ security_name = stock['name']
94
+ security_code = market.fix_security_code(code)
95
+
96
+ # 评估可以委托买入的价格和数量
97
+ # 查询计算单一标的可用资金
98
+ single_funds_available = trader.available_amount(stock_total)
99
+ if single_funds_available <= 0:
100
+ logger.warning('!!!已满仓!!!')
101
+ continue
102
+ # 获取快照
103
+ snapshot = trader.get_snapshot(security_code)
104
+ # 计算溢价
105
+ last_price = snapshot['lastPrice']
106
+ buy_price = trader.available_price(last_price)
107
+ # 计算可买数量
108
+ buy_num = trader.calculate_stock_volumes(single_funds_available, buy_price)
109
+ if buy_num < 100:
110
+ logger.warning('单一股价过高, 分仓购买力不足1手')
111
+ stock_total = stock_total - 1
112
+ continue
113
+ logger.warning('{}: 证券名称={}, 证券代码={}, date={}, strategy_code={}, price={},vol={}', strategy_code,
114
+ security_name, security_code, date, strategy_code, buy_price, buy_num)
115
+ # 委托买入
116
+ order_id = trader.buy(security_code, buy_price, buy_num, strategy_name, 'swfz')
117
+ logger.warning('order id: {}', order_id)
118
+ # 设置执行下单完成状态
119
+ ctx.push_buy_order_done_status(code)
120
+
121
+ # 4. 关闭
122
+ logger.info('{} stop...', application)
123
+ trader.stop()
124
+ logger.info('{} stop...OK', application)
125
+ logger.info('{} shutdown', application)
126
+ return 0
@@ -0,0 +1,89 @@
1
+ # coding=utf-8
2
+ import time
3
+ from decimal import Decimal, ROUND_HALF_UP
4
+
5
+ import numpy as np
6
+ from xtquant import xtdata
7
+ from xtquant.xttype import *
8
+
9
+ # 执行成功
10
+ errno_success = 0
11
+ # qmt错误码基数
12
+ qmt_errno_base = 1000
13
+ # miniQMT 没有找到
14
+ errno_miniqmt_not_found = qmt_errno_base + 1
15
+ # quant1x.yaml配置文件没找到
16
+ errno_config_not_exist = qmt_errno_base + 2
17
+ # 连接miniQMT失败
18
+ errno_miniqmt_connect_failed = qmt_errno_base + 3
19
+ # 非交易日
20
+ errno_not_trade_day = qmt_errno_base + 4
21
+
22
+ kFormatFileDate = '%Y%m%d'
23
+ kFormatOnlyDate = '%Y-%m-%d'
24
+ kFormatTimestamp = '%Y-%m-%d %H:%M:%S'
25
+ kTimestamp = '%H:%M:%S'
26
+ errBadSymbol = RuntimeError("无法识别的证券代码")
27
+
28
+ # # 买入持仓率, 资金控制阀值
29
+ # position_ratio = 0.5000
30
+ # # 买入交易费率
31
+ # buy_trade_rete = 0.0250
32
+ # # 相对开盘价溢价多少买入
33
+ # buy_premium_rate = 0.0200
34
+ # # 买入最大金额
35
+ # buy_amount_max = 250000.00
36
+ #
37
+ # # 竞价开始时间 - 卖出
38
+ # ask_begin = '09:50:00'
39
+ # # 竞价结束时间 - 卖出
40
+ # ask_end = '14:59:30'
41
+ #
42
+ # # 盘中订单 - 开始时间
43
+ # TICK_BEGIN = '09:30:00'
44
+ # # 盘中订单 - 结束时间
45
+ # TICK_END = '14:57:00'
46
+ #
47
+ # cancel_begin = '09:00:00'
48
+ # cancel_end = '15:00:00'
49
+
50
+
51
+ def is_nan(n) -> bool:
52
+ """
53
+ 判断是否nan或inf
54
+ :param n:
55
+ :return:
56
+ """
57
+ return np.isnan(n) or np.isinf(n)
58
+
59
+
60
+ def price_round(num: float, digits: int = 2) -> float:
61
+ """
62
+ 价格四舍五入
63
+ :param num:
64
+ :param digits: 小数点后几位数字
65
+ :return:
66
+ """
67
+ if isinstance(num, float):
68
+ num = str(num)
69
+ x = Decimal(num).quantize((Decimal('0.' + '0' * digits)), rounding=ROUND_HALF_UP)
70
+ return float(x)
71
+
72
+
73
+ def today_is_tradeday() -> bool:
74
+ """
75
+ 今天是否交易日
76
+ :param start_time:
77
+ :param end_time:
78
+ :param count:
79
+ :return:
80
+ """
81
+ time_format = '%Y%m%d'
82
+ today = time.strftime(time_format)
83
+ dates = xtdata.get_trading_dates('SH', today, today)
84
+ if len(dates) == 0:
85
+ return False
86
+ date = time.strftime(time_format, time.localtime(dates[0] / 1000))
87
+ return today == date
88
+
89
+
@@ -0,0 +1,30 @@
1
+ Metadata-Version: 2.1
2
+ Name: quant1x-trader
3
+ Version: 0.1.5
4
+ Summary: Quant1X程序化自动化交易
5
+ Home-page: https://gitee.com/quant1x/trader
6
+ Author: WangFeng
7
+ Author-email: wangfengxy@sina.cn
8
+ License: MIT license
9
+ Keywords: quant1x auto trader
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Natural Language :: English
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ License-File: LICENSE
20
+ Requires-Dist: setuptools~=68.2.2
21
+ Requires-Dist: future~=0.18.3
22
+ Requires-Dist: numpy~=1.26.0
23
+ Requires-Dist: pandas~=2.1.1
24
+ Requires-Dist: PyYAML~=6.0.1
25
+ Requires-Dist: pywin32==306
26
+ Requires-Dist: uvicorn==0.18.1
27
+ Requires-Dist: fastapi~=0.103.1
28
+ Requires-Dist: path~=16.7.1
29
+ Requires-Dist: requests==2.31.0
30
+ Requires-Dist: loguru~=0.7.2
@@ -0,0 +1,22 @@
1
+ LICENSE
2
+ README.rst
3
+ setup.py
4
+ quant1x/autotrader/__init__.py
5
+ quant1x/autotrader/__main__.py
6
+ quant1x/autotrader/base.py
7
+ quant1x/autotrader/callback.py
8
+ quant1x/autotrader/config.py
9
+ quant1x/autotrader/context.py
10
+ quant1x/autotrader/env.py
11
+ quant1x/autotrader/logger.py
12
+ quant1x/autotrader/market.py
13
+ quant1x/autotrader/thinktrader.py
14
+ quant1x/autotrader/trader.py
15
+ quant1x/autotrader/utils.py
16
+ quant1x_trader.egg-info/PKG-INFO
17
+ quant1x_trader.egg-info/SOURCES.txt
18
+ quant1x_trader.egg-info/dependency_links.txt
19
+ quant1x_trader.egg-info/entry_points.txt
20
+ quant1x_trader.egg-info/not-zip-safe
21
+ quant1x_trader.egg-info/requires.txt
22
+ quant1x_trader.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ quant1x-auto-trader = quant1x.autotrader.__main__:auto_trader
@@ -0,0 +1,11 @@
1
+ setuptools~=68.2.2
2
+ future~=0.18.3
3
+ numpy~=1.26.0
4
+ pandas~=2.1.1
5
+ PyYAML~=6.0.1
6
+ pywin32==306
7
+ uvicorn==0.18.1
8
+ fastapi~=0.103.1
9
+ path~=16.7.1
10
+ requests==2.31.0
11
+ loguru~=0.7.2
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env python
2
+ # coding=utf-8
3
+ """The setup script."""
4
+ import setuptools
5
+ from git import Repo
6
+
7
+ # from quant1x import __author__
8
+
9
+ # try:
10
+ # from setuptools import find_packages, setup
11
+ # except ImportError:
12
+ # from distutils.core import find_packages, setup
13
+
14
+ repo = Repo(r'./')
15
+ tags = []
16
+ for __tag in repo.tags:
17
+ tag = str(__tag)
18
+ tag = tag[1:]
19
+ tags.append(tag)
20
+ tags.sort(key=lambda x: tuple(int(v) for v in x.split('.')))
21
+ latest = tags[-1]
22
+
23
+ app__version__ = latest
24
+ __author__ = "WangFeng"
25
+
26
+
27
+ def parse_requirements(filename):
28
+ line_iter = (line.strip() for line in open(filename))
29
+ return [line for line in line_iter if line and not line.startswith("#")]
30
+
31
+
32
+ with open("README.rst", encoding="utf-8") as readme_file:
33
+ readme = readme_file.read()
34
+
35
+ with open("HISTORY.rst", encoding="utf-8") as history_file:
36
+ history = history_file.read()
37
+
38
+ requirements = parse_requirements("requirements.txt")
39
+ test_requirements = requirements
40
+
41
+ setuptools.setup(
42
+ name="quant1x-trader",
43
+ description="Quant1X程序化自动化交易",
44
+ author_email="wangfengxy@sina.cn",
45
+ url="https://gitee.com/quant1x/trader",
46
+ version=app__version__,
47
+ author=__author__,
48
+ long_description=readme,
49
+ packages=setuptools.find_packages(include=["quant1x.autotrader", "quant1x.autotrader.*"]),
50
+ include_package_data=True,
51
+ install_requires=requirements,
52
+ license="MIT license",
53
+ zip_safe=False,
54
+ keywords="quant1x auto trader",
55
+ entry_points={
56
+ "console_scripts": [
57
+ "quant1x-auto-trader=quant1x.autotrader.__main__:auto_trader",
58
+ ]
59
+ },
60
+ classifiers=[
61
+ "Development Status :: 2 - Pre-Alpha",
62
+ "Intended Audience :: Developers",
63
+ "License :: OSI Approved :: MIT License",
64
+ "Natural Language :: English",
65
+ "Programming Language :: Python :: 3",
66
+ "Programming Language :: Python :: 3.8",
67
+ "Programming Language :: Python :: 3.9",
68
+ "Programming Language :: Python :: 3.10",
69
+ "Programming Language :: Python :: 3.11",
70
+ ],
71
+ data_files=[
72
+ # ('xtquant', ['xtquant/xtdata.ini', 'xtquant/xtdata.log4cxx']),
73
+ ],
74
+ package_data={
75
+ '': ['*.dll', '*.pyd', '*.ini', '*.log4cxx'],
76
+ },
77
+ test_suite="tests",
78
+ tests_require=test_requirements,
79
+ setup_requires=requirements,
80
+ )