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.
- quant1x-trader-0.1.5/LICENSE +21 -0
- quant1x-trader-0.1.5/PKG-INFO +30 -0
- quant1x-trader-0.1.5/README.rst +0 -0
- quant1x-trader-0.1.5/quant1x/autotrader/__init__.py +10 -0
- quant1x-trader-0.1.5/quant1x/autotrader/__main__.py +14 -0
- quant1x-trader-0.1.5/quant1x/autotrader/base.py +141 -0
- quant1x-trader-0.1.5/quant1x/autotrader/callback.py +83 -0
- quant1x-trader-0.1.5/quant1x/autotrader/config.py +91 -0
- quant1x-trader-0.1.5/quant1x/autotrader/context.py +159 -0
- quant1x-trader-0.1.5/quant1x/autotrader/env.py +51 -0
- quant1x-trader-0.1.5/quant1x/autotrader/logger.py +4 -0
- quant1x-trader-0.1.5/quant1x/autotrader/market.py +51 -0
- quant1x-trader-0.1.5/quant1x/autotrader/thinktrader.py +209 -0
- quant1x-trader-0.1.5/quant1x/autotrader/trader.py +126 -0
- quant1x-trader-0.1.5/quant1x/autotrader/utils.py +89 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/PKG-INFO +30 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/SOURCES.txt +22 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/dependency_links.txt +1 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/entry_points.txt +2 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/not-zip-safe +1 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/requires.txt +11 -0
- quant1x-trader-0.1.5/quant1x_trader.egg-info/top_level.txt +1 -0
- quant1x-trader-0.1.5/setup.cfg +4 -0
- quant1x-trader-0.1.5/setup.py +80 -0
|
@@ -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,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,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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
quant1x
|
|
@@ -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
|
+
)
|