quant1x-trader 0.0.0__py3-none-any.whl
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.
- quant1x_trader-0.0.0.dist-info/METADATA +43 -0
- quant1x_trader-0.0.0.dist-info/RECORD +14 -0
- quant1x_trader-0.0.0.dist-info/WHEEL +5 -0
- quant1x_trader-0.0.0.dist-info/entry_points.txt +2 -0
- quant1x_trader-0.0.0.dist-info/licenses/LICENSE +21 -0
- quant1x_trader-0.0.0.dist-info/top_level.txt +1 -0
- trader1x/__init__.py +5 -0
- trader1x/config.py +251 -0
- trader1x/context.py +260 -0
- trader1x/log4py.py +36 -0
- trader1x/proxy.py +583 -0
- trader1x/thinktrader.py +523 -0
- trader1x/utils.py +44 -0
- trader1x/win32serviceutil.py +1071 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: quant1x-trader
|
|
3
|
+
Version: 0.0.0
|
|
4
|
+
Summary: Quant1X programmatic automated trading
|
|
5
|
+
Author-email: Wang Feng <wangfengxy@sina.cn>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://gitee.com/quant1x/trader
|
|
8
|
+
Keywords: quant1x,auto,trader
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Natural Language :: English
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Requires-Python: >=3.8
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: future~=1.0.0
|
|
23
|
+
Requires-Dist: numpy~=2.3.0
|
|
24
|
+
Requires-Dist: pandas~=2.3.0
|
|
25
|
+
Requires-Dist: PyYAML~=6.0.1
|
|
26
|
+
Requires-Dist: pywin32==311
|
|
27
|
+
Requires-Dist: uvicorn~=0.29.0
|
|
28
|
+
Requires-Dist: fastapi~=0.110.2
|
|
29
|
+
Requires-Dist: path~=16.14.0
|
|
30
|
+
Requires-Dist: requests~=2.32.0
|
|
31
|
+
Requires-Dist: loguru~=0.7.2
|
|
32
|
+
Requires-Dist: quant1x-xtquant~=2025.5.26
|
|
33
|
+
Requires-Dist: quant1x-base~=0.6.29
|
|
34
|
+
Requires-Dist: aiohttp~=3.12.15
|
|
35
|
+
Requires-Dist: sanic~=23.12.1
|
|
36
|
+
Requires-Dist: python-multipart~=0.0.19
|
|
37
|
+
Requires-Dist: APScheduler~=3.10.4
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# trader
|
|
41
|
+
|
|
42
|
+
#### 介绍
|
|
43
|
+
python实现的自动化交易员
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
quant1x_trader-0.0.0.dist-info/licenses/LICENSE,sha256=QGbg9vBu92X93RlRr7rDfQvc632ykqWzhC1iWs17OC0,1087
|
|
2
|
+
trader1x/__init__.py,sha256=tFXhZX139csbXPeqDgknKRFQC3cTHcWiWZDeY1c7E7o,103
|
|
3
|
+
trader1x/config.py,sha256=Lln9Ze8teUQesLV9bn_vQSt0ItcAyIT8YF1wQTdpTUo,8791
|
|
4
|
+
trader1x/context.py,sha256=YKxZbNsF8RAMbBWZPlPWz21K7lkWKlSMM19_tD8lAgw,7717
|
|
5
|
+
trader1x/log4py.py,sha256=HNdS2tv_mKEwm2FljcCz9vRAaefGjsQX4xpyeDTN-Gw,1159
|
|
6
|
+
trader1x/proxy.py,sha256=K9zOs_BDcM0NjG9A1bjdquer9-zB8bQHDRCmxqdurOc,18268
|
|
7
|
+
trader1x/thinktrader.py,sha256=w6R99MoSihqnHljI7ps9ijU3bM_HDy9nmvqTSc5WSYQ,19322
|
|
8
|
+
trader1x/utils.py,sha256=pmY20CM3-JO0kfkmrUCpfFp5wTLDQYEy1SfgNIMNytc,1323
|
|
9
|
+
trader1x/win32serviceutil.py,sha256=jieIIyTdGEM3oNW4t5oKcG1Uf5MK9NC9tyTskTzUDD8,38933
|
|
10
|
+
quant1x_trader-0.0.0.dist-info/METADATA,sha256=qNCpWX6NaNmVJX81lPnThmMaL7ZsxLGEgzsYX7u_HDM,1452
|
|
11
|
+
quant1x_trader-0.0.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
12
|
+
quant1x_trader-0.0.0.dist-info/entry_points.txt,sha256=KaWVu2VzttfRIQRrZ9DxKj8etOEtnKl8REILAwditp4,58
|
|
13
|
+
quant1x_trader-0.0.0.dist-info/top_level.txt,sha256=J1TOgUDUKoWldrXZ0qIdRCfbLTcTsD53UYzHXhsAZdM,9
|
|
14
|
+
quant1x_trader-0.0.0.dist-info/RECORD,,
|
|
@@ -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 @@
|
|
|
1
|
+
trader1x
|
trader1x/__init__.py
ADDED
trader1x/config.py
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import warnings
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
import q1x.base
|
|
8
|
+
import yaml
|
|
9
|
+
from q1x.base import TradingSession
|
|
10
|
+
|
|
11
|
+
from trader1x import utils
|
|
12
|
+
from trader1x.log4py import logger
|
|
13
|
+
|
|
14
|
+
warnings.filterwarnings('ignore')
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def list_default_factory():
|
|
18
|
+
return []
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class TradeRule:
|
|
23
|
+
"""
|
|
24
|
+
交易规则
|
|
25
|
+
"""
|
|
26
|
+
id: int = -1 # 策略ID, -1无效
|
|
27
|
+
auto: bool = False # 是否自动执行
|
|
28
|
+
name: str = None # 策略名称
|
|
29
|
+
flag: str = '' # 订单标识,分早盘,尾盘和盘中
|
|
30
|
+
time: TradingSession = field(default_factory=lambda: TradingSession("09:30:00~11:30:00,13:00:00~14:56:30"))
|
|
31
|
+
weight: int = 0 # 策略权重, 默认0, 由系统自动分配
|
|
32
|
+
total: int = 3 # 订单总数, 默认是3
|
|
33
|
+
fee_max: float = 20000.00 # 可投入资金-最大
|
|
34
|
+
fee_min: float = 10000.00 # 可投入资金-最小
|
|
35
|
+
sectors: list[str] = None # 板块, 策略适用的板块列表, 默认板块为空, 即全部个股
|
|
36
|
+
ignore_margin_trading: bool = True # 剔除两融标的, 默认是剔除
|
|
37
|
+
|
|
38
|
+
def __init__(self, params: dict = None):
|
|
39
|
+
"""
|
|
40
|
+
初始化
|
|
41
|
+
"""
|
|
42
|
+
# 先让 dataclass 初始化默认值
|
|
43
|
+
# 手动初始化所有字段
|
|
44
|
+
self.time = TradingSession("09:30:00~11:30:00,13:00:00~14:56:30")
|
|
45
|
+
if params is None:
|
|
46
|
+
return
|
|
47
|
+
for key, value in params.items():
|
|
48
|
+
if not hasattr(self, key):
|
|
49
|
+
continue
|
|
50
|
+
tmp_value = getattr(self, key)
|
|
51
|
+
if isinstance(tmp_value, TradingSession):
|
|
52
|
+
new_value = TradingSession(value)
|
|
53
|
+
setattr(self, key, new_value)
|
|
54
|
+
else:
|
|
55
|
+
setattr(self, key, value)
|
|
56
|
+
|
|
57
|
+
def enable(self) -> bool:
|
|
58
|
+
"""
|
|
59
|
+
是否有效
|
|
60
|
+
:return:
|
|
61
|
+
"""
|
|
62
|
+
return self.auto and self.id >= 0
|
|
63
|
+
|
|
64
|
+
def buy_enable(self) -> bool:
|
|
65
|
+
"""
|
|
66
|
+
买入有效
|
|
67
|
+
:return:
|
|
68
|
+
"""
|
|
69
|
+
return self.enable() and self.total > 0
|
|
70
|
+
|
|
71
|
+
def sell_enable(self) -> bool:
|
|
72
|
+
"""
|
|
73
|
+
卖出有效
|
|
74
|
+
:return:
|
|
75
|
+
"""
|
|
76
|
+
return self.enable()
|
|
77
|
+
|
|
78
|
+
def is_cookie_cutter_for_sell(self) -> bool:
|
|
79
|
+
"""
|
|
80
|
+
是否一刀切卖出
|
|
81
|
+
:return:
|
|
82
|
+
"""
|
|
83
|
+
return self.sell_enable() and self.total == 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class TraderConfig:
|
|
88
|
+
"""
|
|
89
|
+
配置信息
|
|
90
|
+
"""
|
|
91
|
+
# 账号ID
|
|
92
|
+
account_id: str = ''
|
|
93
|
+
# 运行路径
|
|
94
|
+
order_path: str = ''
|
|
95
|
+
# 时间范围 - 早盘策略
|
|
96
|
+
head_time: TradingSession = field(default_factory=lambda: TradingSession("09:27:00~14:57:00"))
|
|
97
|
+
# 时间范围 - 尾盘策略
|
|
98
|
+
tail_time: TradingSession = field(default_factory=lambda: TradingSession("14:45:00~14:59:50"))
|
|
99
|
+
# 时间范围 - 盘中订单
|
|
100
|
+
tick_time: TradingSession = field(default_factory=lambda: TradingSession("09:39:00-14:57:00"))
|
|
101
|
+
# 时间范围 - 持仓卖出
|
|
102
|
+
ask_time: TradingSession = field(default_factory=lambda: TradingSession("09:50:00~14:59:30"))
|
|
103
|
+
# 时间范围 - 撤销订单
|
|
104
|
+
cancel_time: TradingSession = field(default_factory=lambda: TradingSession("09:15:00~09:19:59, 09:30:00~14:56:59"))
|
|
105
|
+
# 时间范围 - 盘后复盘
|
|
106
|
+
review_time: TradingSession = field(default_factory=lambda: TradingSession("00:00:00~08:30:00, 15:01:00~23:59:59"))
|
|
107
|
+
# 买入持仓率, 资金控制阀值
|
|
108
|
+
position_ratio: float = 0.5000
|
|
109
|
+
# 印花税 - 买入, 按照成交金额, 买入0.0%
|
|
110
|
+
stamp_duty_rate_for_buy: float = 0.0000
|
|
111
|
+
# 印花税 - 卖出, 按照成交金额, 卖出0.1%
|
|
112
|
+
stamp_duty_rate_for_sell: float = 0.0010
|
|
113
|
+
# 过户费 - 双向, 按照数量收取, 默认万分之六, 0.06%
|
|
114
|
+
transfer_rate: float = 0.0006
|
|
115
|
+
# 券商佣金 - 双向, 按成交金额计算, 默认万分之二点五, 0.025%
|
|
116
|
+
commission_rate: float = 0.00025
|
|
117
|
+
# 券商佣金最低, 默认5.00
|
|
118
|
+
commission_min: float = 5.00
|
|
119
|
+
# 保留现金
|
|
120
|
+
keep_cash: float = 10000.00
|
|
121
|
+
# tick订单最大金额, 默认20000.00
|
|
122
|
+
tick_order_max_amount: float = 20000.00
|
|
123
|
+
# tick订单最小金额, 默认10000.00
|
|
124
|
+
tick_order_min_amount: float = 10000.00
|
|
125
|
+
# 买入最大金额
|
|
126
|
+
buy_amount_max: float = 250000.00
|
|
127
|
+
# 买入最小金额
|
|
128
|
+
buy_amount_min: float = 1000.00
|
|
129
|
+
# 每策略最多可买股票数量, 这里默认3
|
|
130
|
+
max_stock_quantity_for_strategy: int = 3
|
|
131
|
+
# 自动卖出, 默认为True
|
|
132
|
+
sell_order_auto: bool = False
|
|
133
|
+
# 自动交易head订单, 默认为True
|
|
134
|
+
head_order_auto: bool = False
|
|
135
|
+
# 自动交易tick订单, 默认为True
|
|
136
|
+
tick_order_auto: bool = False
|
|
137
|
+
# 自动交易tail订单, 默认为True
|
|
138
|
+
tail_order_auto: bool = False
|
|
139
|
+
|
|
140
|
+
# 启动了mimiQMT机器的代理服务的监听地址, 默认为回环地址127.0.0.1, 强烈不推荐使用0.0.0.0
|
|
141
|
+
proxy_address: str = q1x.base.lan_address()
|
|
142
|
+
# 代理服务的监听端口, 默认18168
|
|
143
|
+
proxy_port: int = 18168
|
|
144
|
+
# 代理服务默认工作线程数, 默认为cpu核数的二分之一
|
|
145
|
+
proxy_workers: int = q1x.base.max_procs()
|
|
146
|
+
# qmt proxy地址
|
|
147
|
+
proxy_url: str = ""
|
|
148
|
+
# 策略集合
|
|
149
|
+
strategies: list[TradeRule] = list_default_factory
|
|
150
|
+
# 可撤单的交易时段
|
|
151
|
+
cancel: TradingSession = field(
|
|
152
|
+
default_factory=lambda: TradingSession("09:15:00~09:19:59,09:25:00~11:29:59,13:00:00~14:59:59"))
|
|
153
|
+
|
|
154
|
+
def __fix_instance(self):
|
|
155
|
+
"""
|
|
156
|
+
加载后修复
|
|
157
|
+
:return:
|
|
158
|
+
"""
|
|
159
|
+
if isinstance(self.ask_time, str):
|
|
160
|
+
ts = TradingSession(self.ask_time)
|
|
161
|
+
if ts.is_valid():
|
|
162
|
+
self.ask_time = ts
|
|
163
|
+
if isinstance(self.cancel_time, str):
|
|
164
|
+
ts = TradingSession(self.cancel_time)
|
|
165
|
+
if ts.is_valid():
|
|
166
|
+
self.cancel_time = ts
|
|
167
|
+
if isinstance(self.tick_time, str):
|
|
168
|
+
ts = TradingSession(self.tick_time)
|
|
169
|
+
if ts.is_valid():
|
|
170
|
+
self.tick_time = ts
|
|
171
|
+
|
|
172
|
+
def __post_init__(self):
|
|
173
|
+
"""
|
|
174
|
+
__init__()后调用, 调整类型
|
|
175
|
+
:return:
|
|
176
|
+
"""
|
|
177
|
+
self.__fix_instance()
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# 全局变量 - 配置
|
|
181
|
+
__global_config = TraderConfig()
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def load(config_filename: str = '') -> TraderConfig:
|
|
185
|
+
"""
|
|
186
|
+
加载配置文件
|
|
187
|
+
:return:
|
|
188
|
+
"""
|
|
189
|
+
global __global_config
|
|
190
|
+
config = TraderConfig()
|
|
191
|
+
config_filename = config_filename.strip()
|
|
192
|
+
if len(config_filename) == 0:
|
|
193
|
+
config_filename = utils.get_quant1x_config_filename()
|
|
194
|
+
config_filename = os.path.expanduser(config_filename)
|
|
195
|
+
logger.info(config_filename)
|
|
196
|
+
if not os.path.isfile(config_filename):
|
|
197
|
+
logger.error('QMT config {}: 不存在', config_filename)
|
|
198
|
+
sys.exit(utils.errno_config_not_exist)
|
|
199
|
+
try:
|
|
200
|
+
with open(config_filename, 'r', encoding='utf-8') as f:
|
|
201
|
+
result = yaml.load(f, Loader=yaml.FullLoader)
|
|
202
|
+
key_trader = "trader"
|
|
203
|
+
if isinstance(result, dict) and key_trader in result:
|
|
204
|
+
trader = result[key_trader]
|
|
205
|
+
for key, value in trader.items():
|
|
206
|
+
if not hasattr(config, key):
|
|
207
|
+
continue
|
|
208
|
+
tmp_value = getattr(config, key)
|
|
209
|
+
if isinstance(tmp_value, TradingSession):
|
|
210
|
+
new_value = TradingSession(value)
|
|
211
|
+
setattr(config, key, new_value)
|
|
212
|
+
elif tmp_value is list_default_factory:
|
|
213
|
+
if key == 'strategies':
|
|
214
|
+
tmp_list = []
|
|
215
|
+
if isinstance(value, list):
|
|
216
|
+
for d in value:
|
|
217
|
+
rule = TradeRule(d)
|
|
218
|
+
tmp_list.append(rule)
|
|
219
|
+
setattr(config, key, tmp_list)
|
|
220
|
+
elif key == 'account_id':
|
|
221
|
+
setattr(config, 'account_id', str(value))
|
|
222
|
+
elif key == 'top_n':
|
|
223
|
+
setattr(config, 'max_stock_quantity_for_strategy', value)
|
|
224
|
+
else:
|
|
225
|
+
setattr(config, key, value)
|
|
226
|
+
except Exception as e:
|
|
227
|
+
logger.error(f"发生了一个错误:{config_filename}\n错误信息:{e}")
|
|
228
|
+
logger.warning('系统将使用默认配置')
|
|
229
|
+
config = TraderConfig()
|
|
230
|
+
# finally:
|
|
231
|
+
# logger.warning('系统将使用默认配置')
|
|
232
|
+
# 检查重点配置
|
|
233
|
+
if config.account_id == '':
|
|
234
|
+
logger.error('配置缺少账户id')
|
|
235
|
+
sys.exit(utils.errno_not_found_account_id)
|
|
236
|
+
if config.order_path == '':
|
|
237
|
+
logger.error('配置缺少订单路径')
|
|
238
|
+
sys.exit(utils.errno_not_found_order_path)
|
|
239
|
+
__global_config = config
|
|
240
|
+
return config
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def get() -> TraderConfig:
|
|
244
|
+
global __global_config
|
|
245
|
+
return __global_config
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
if __name__ == '__main__':
|
|
249
|
+
filename = '~/.q1x/quant1x.yaml'
|
|
250
|
+
config = load(filename)
|
|
251
|
+
print(config)
|
trader1x/context.py
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# coding=utf-8
|
|
2
|
+
import os
|
|
3
|
+
import time
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from xtquant import xtdata
|
|
7
|
+
from xtquant.xttype import StockAccount
|
|
8
|
+
|
|
9
|
+
from trader1x import utils
|
|
10
|
+
from trader1x.config import TraderConfig
|
|
11
|
+
from trader1x.log4py import logger
|
|
12
|
+
|
|
13
|
+
# 禁止显示XtQuant的hello信息
|
|
14
|
+
xtdata.enable_hello = False
|
|
15
|
+
|
|
16
|
+
order_buy = 1
|
|
17
|
+
order_sell = 2
|
|
18
|
+
order_junk = 3
|
|
19
|
+
flag_buy = "b"
|
|
20
|
+
flag_sell = "s"
|
|
21
|
+
flag_junk = "j"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class QmtContext(object):
|
|
25
|
+
"""
|
|
26
|
+
QMT 上下文
|
|
27
|
+
TODO: 按日期切换数据
|
|
28
|
+
"""
|
|
29
|
+
current_date: str # 当前日期
|
|
30
|
+
config_filename: str # 配置文件名
|
|
31
|
+
order_path: str # quant1x系统输出订单的路径
|
|
32
|
+
account_id: str # 账号ID
|
|
33
|
+
t89k_order_file: str # 订单文件
|
|
34
|
+
t89k_flag_ready: str # 订单就绪标志
|
|
35
|
+
t89k_flag_done: str # 订单执行完成标志
|
|
36
|
+
positions_sell_done: str # 持仓卖出状态
|
|
37
|
+
qmt_order_filename: str # qmt系统输出的订单路径
|
|
38
|
+
qmt_order_done: str # qmt系统每日订单刷新完成标志
|
|
39
|
+
qmt_positions_filename: str # qmt持仓
|
|
40
|
+
|
|
41
|
+
def __init__(self, conf: TraderConfig):
|
|
42
|
+
self._config = conf
|
|
43
|
+
self.current_date = time.strftime(utils.kFormatFileDate)
|
|
44
|
+
self.account_id = conf.account_id
|
|
45
|
+
self.order_path = conf.order_path
|
|
46
|
+
self.switch_date()
|
|
47
|
+
|
|
48
|
+
def account(self) -> StockAccount:
|
|
49
|
+
return StockAccount(self.account_id)
|
|
50
|
+
|
|
51
|
+
def sell_is_ready(self) -> bool:
|
|
52
|
+
"""
|
|
53
|
+
卖出条件是否就绪
|
|
54
|
+
:return:
|
|
55
|
+
"""
|
|
56
|
+
return self._config.ask_time.is_trading()
|
|
57
|
+
|
|
58
|
+
def sell_is_auto(self) -> bool:
|
|
59
|
+
"""
|
|
60
|
+
卖出操作是否自动一刀切
|
|
61
|
+
:return:
|
|
62
|
+
"""
|
|
63
|
+
return self._config.sell_order_auto
|
|
64
|
+
|
|
65
|
+
def head_order_is_ready(self) -> bool:
|
|
66
|
+
"""
|
|
67
|
+
早盘(买入)订单是否准备就绪
|
|
68
|
+
:return:
|
|
69
|
+
"""
|
|
70
|
+
if os.path.isfile(self.t89k_flag_ready) and os.path.isfile(self.t89k_order_file):
|
|
71
|
+
return True
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
def head_order_is_auto(self) -> bool:
|
|
75
|
+
"""
|
|
76
|
+
是否执行 早盘订单的交易
|
|
77
|
+
:return:
|
|
78
|
+
"""
|
|
79
|
+
return self._config.head_order_auto
|
|
80
|
+
|
|
81
|
+
def tick_order_is_auto(self) -> bool:
|
|
82
|
+
"""
|
|
83
|
+
是否执行 盘中即时订单的交易
|
|
84
|
+
:return:
|
|
85
|
+
"""
|
|
86
|
+
return self._config.tick_order_auto
|
|
87
|
+
|
|
88
|
+
def tail_order_is_auto(self) -> bool:
|
|
89
|
+
"""
|
|
90
|
+
是否执行 尾盘订单的交易
|
|
91
|
+
:return:
|
|
92
|
+
"""
|
|
93
|
+
return self._config.tail_order_auto
|
|
94
|
+
|
|
95
|
+
def can_review(self) -> bool:
|
|
96
|
+
"""
|
|
97
|
+
是佛可以复盘
|
|
98
|
+
:return:
|
|
99
|
+
"""
|
|
100
|
+
return self._config.review_time.is_trading()
|
|
101
|
+
|
|
102
|
+
def load_head_order(self) -> pd.DataFrame:
|
|
103
|
+
"""
|
|
104
|
+
加载早盘订单
|
|
105
|
+
:return:
|
|
106
|
+
"""
|
|
107
|
+
df = pd.read_csv(self.t89k_order_file)
|
|
108
|
+
return df
|
|
109
|
+
|
|
110
|
+
def switch_date(self):
|
|
111
|
+
"""
|
|
112
|
+
重置属性
|
|
113
|
+
:return:
|
|
114
|
+
"""
|
|
115
|
+
logger.warning("switch_date...")
|
|
116
|
+
# self.current_date = time.strftime(utils.kFormatFileDate)
|
|
117
|
+
v = xtdata.get_market_last_trade_date('SH')
|
|
118
|
+
local_time = time.localtime(v / 1000)
|
|
119
|
+
trade_date = time.strftime(utils.kFormatFileDate, local_time)
|
|
120
|
+
self.current_date = trade_date
|
|
121
|
+
logger.warning("switch_date...{}", self.current_date)
|
|
122
|
+
flag = 'head'
|
|
123
|
+
self.t89k_flag_ready = os.path.join(self.order_path, f'{self.current_date}-{flag}.ready')
|
|
124
|
+
self.t89k_flag_done = os.path.join(self.order_path, f'{self.current_date}-{flag}-{self.account_id}.done')
|
|
125
|
+
self.t89k_order_file = os.path.join(self.order_path, f'{self.current_date}-{flag}.csv')
|
|
126
|
+
self.positions_sell_done = os.path.join(self.order_path, f'{self.current_date}-sell-{self.account_id}.done')
|
|
127
|
+
# qmt订单文件
|
|
128
|
+
self.qmt_order_filename = os.path.join(self.order_path, f'{self.account_id}-orders.csv')
|
|
129
|
+
self.qmt_order_done = os.path.join(self.order_path, f'{self.account_id}-orders-{self.current_date}.done')
|
|
130
|
+
# qmt持仓文件
|
|
131
|
+
self.qmt_positions_filename = os.path.join(self.order_path, f'{self.account_id}-positions.csv')
|
|
132
|
+
|
|
133
|
+
def orders_has_refreshed(self) -> bool:
|
|
134
|
+
"""
|
|
135
|
+
当日订单是否已经刷新完成
|
|
136
|
+
:return:
|
|
137
|
+
"""
|
|
138
|
+
return self.__filelock(self.qmt_order_done)
|
|
139
|
+
|
|
140
|
+
def push_orders_refreshed(self):
|
|
141
|
+
"""
|
|
142
|
+
标注订单刷新已完成
|
|
143
|
+
:return:
|
|
144
|
+
"""
|
|
145
|
+
self._push_local_message(self.qmt_order_done)
|
|
146
|
+
|
|
147
|
+
def push_head_order_buy_completed(self):
|
|
148
|
+
"""
|
|
149
|
+
买入操作完成
|
|
150
|
+
:return:
|
|
151
|
+
"""
|
|
152
|
+
self._push_local_message(self.t89k_flag_done)
|
|
153
|
+
logger.info('订单买入操作完成')
|
|
154
|
+
|
|
155
|
+
def head_order_buy_is_finished(self) -> bool:
|
|
156
|
+
"""
|
|
157
|
+
早盘订单是否完成
|
|
158
|
+
:return:
|
|
159
|
+
"""
|
|
160
|
+
return os.path.isfile(self.t89k_flag_done)
|
|
161
|
+
|
|
162
|
+
def push_positions_sell_completed(self):
|
|
163
|
+
"""
|
|
164
|
+
标记卖出操作完成
|
|
165
|
+
:return:
|
|
166
|
+
"""
|
|
167
|
+
self._push_local_message(self.positions_sell_done)
|
|
168
|
+
|
|
169
|
+
def positions_sell_finished(self):
|
|
170
|
+
"""
|
|
171
|
+
卖出是否操作完成
|
|
172
|
+
:return:
|
|
173
|
+
"""
|
|
174
|
+
return os.path.isfile(self.positions_sell_done)
|
|
175
|
+
|
|
176
|
+
def check_buy_order_done_status(self, code: str) -> bool:
|
|
177
|
+
"""
|
|
178
|
+
检查买入订单执行完成状态
|
|
179
|
+
:return:
|
|
180
|
+
"""
|
|
181
|
+
flag = self.get_order_flag(code, order_buy)
|
|
182
|
+
return os.path.exists(flag)
|
|
183
|
+
|
|
184
|
+
def push_buy_order_done_status(self, code: str):
|
|
185
|
+
"""
|
|
186
|
+
推送买入订单完成状态
|
|
187
|
+
:param code:
|
|
188
|
+
:return:
|
|
189
|
+
"""
|
|
190
|
+
flag = self.get_order_flag(code, order_buy)
|
|
191
|
+
self._push_local_message(flag)
|
|
192
|
+
|
|
193
|
+
def _push_local_message(self, filename: str):
|
|
194
|
+
"""
|
|
195
|
+
推送消息
|
|
196
|
+
:param filename:
|
|
197
|
+
:return:
|
|
198
|
+
"""
|
|
199
|
+
with open(filename, 'w') as done_file:
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
def get_order_flag(self, code: str, type: int) -> str:
|
|
203
|
+
"""
|
|
204
|
+
获取订单标识
|
|
205
|
+
:param self:
|
|
206
|
+
:param code:
|
|
207
|
+
:param type: 1-b(buy),2-s(ell),3-j(unk)
|
|
208
|
+
:return:
|
|
209
|
+
"""
|
|
210
|
+
today = time.strftime(utils.kFormatFileDate)
|
|
211
|
+
if type == order_buy:
|
|
212
|
+
order_flag = flag_buy
|
|
213
|
+
elif type == order_sell:
|
|
214
|
+
order_flag = flag_sell
|
|
215
|
+
else:
|
|
216
|
+
order_flag = flag_junk
|
|
217
|
+
order_flag_path = self.order_path + "/var/" + today
|
|
218
|
+
q1x_base.mkdirs(order_flag_path)
|
|
219
|
+
stock_order_flag = os.path.join(order_flag_path, f'{today}-{self.account_id}-{code}-{order_flag}.done')
|
|
220
|
+
return stock_order_flag
|
|
221
|
+
|
|
222
|
+
def fix_security_code(self, symbol: str) -> str:
|
|
223
|
+
"""
|
|
224
|
+
调整证券代码
|
|
225
|
+
:param symbol:
|
|
226
|
+
:return:
|
|
227
|
+
"""
|
|
228
|
+
security_code = ''
|
|
229
|
+
if len(symbol) == 6:
|
|
230
|
+
flag = self.get_security_type(symbol)
|
|
231
|
+
security_code = f'{symbol}.{flag}'
|
|
232
|
+
elif len(symbol) == 8 and symbol[:2] in ["sh", "sz", "SH", "SZ"]:
|
|
233
|
+
security_code = symbol[2:] + '.' + symbol[:2].upper()
|
|
234
|
+
else:
|
|
235
|
+
raise utils.errBadSymbol
|
|
236
|
+
return security_code
|
|
237
|
+
|
|
238
|
+
def get_security_type(self, symbol: str) -> str:
|
|
239
|
+
"""
|
|
240
|
+
获取股票市场标识
|
|
241
|
+
:param symbol: 代码
|
|
242
|
+
:return:
|
|
243
|
+
"""
|
|
244
|
+
if len(symbol) != 6:
|
|
245
|
+
raise utils.errBadSymbol
|
|
246
|
+
code_head = symbol[:2]
|
|
247
|
+
if code_head in ["00", "30"]:
|
|
248
|
+
return "SZ"
|
|
249
|
+
if code_head in ["60", "68"]: # 688XXX科创板
|
|
250
|
+
return "SH"
|
|
251
|
+
if code_head in ["510"]:
|
|
252
|
+
return "SH"
|
|
253
|
+
raise utils.errBadSymbol
|
|
254
|
+
|
|
255
|
+
def __filelock(self, filename: str) -> bool:
|
|
256
|
+
"""
|
|
257
|
+
文件锁状态
|
|
258
|
+
:return:
|
|
259
|
+
"""
|
|
260
|
+
return os.path.isfile(filename)
|
trader1x/log4py.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# -*- coding: UTF-8 -*-
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from loguru import logger as __logger
|
|
7
|
+
from q1x.base import file, application
|
|
8
|
+
|
|
9
|
+
from trader1x import utils
|
|
10
|
+
|
|
11
|
+
# 获取默认的配置文件路径
|
|
12
|
+
config_filename = utils.get_quant1x_config_filename()
|
|
13
|
+
# 转换用户路径
|
|
14
|
+
config_filename = os.path.expanduser(config_filename)
|
|
15
|
+
# 检查配置文件是否存在
|
|
16
|
+
if not os.path.isfile(config_filename):
|
|
17
|
+
__logger.error('QMT config {}: 不存在', config_filename)
|
|
18
|
+
sys.exit(utils.errno_config_not_exist)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
with open(config_filename, 'r', encoding='utf-8') as f:
|
|
22
|
+
result = yaml.load(f, Loader=yaml.FullLoader)
|
|
23
|
+
key_basedir = "basedir"
|
|
24
|
+
if isinstance(result, dict) and key_basedir in result:
|
|
25
|
+
quant1x_data_path = result[key_basedir]
|
|
26
|
+
except Exception as e:
|
|
27
|
+
quant1x_data_path = file.homedir()
|
|
28
|
+
quant1x_data_path = os.path.expanduser(quant1x_data_path)
|
|
29
|
+
_, filename, _ = application()
|
|
30
|
+
if filename == 'pythonservice':
|
|
31
|
+
filename = 'proxy'
|
|
32
|
+
__log_file = f"{quant1x_data_path}/logs/{filename}.log"
|
|
33
|
+
# print(__log_file)
|
|
34
|
+
__logger.add(__log_file, encoding="utf-8", rotation="00:00", retention="10 days")
|
|
35
|
+
|
|
36
|
+
logger = __logger
|