├── .gitignore
├── AutoTrade.py
├── Configs
├── README
├── Socket.config.gf.example
└── Tools.config.ocr.example
├── Documents
└── image
│ ├── run.jpg
│ ├── run.odg
│ ├── start.jpg
│ ├── start.odg
│ ├── 控件准备.png
│ ├── 秘钥生成1.png
│ ├── 秘钥生成2.png
│ ├── 秘钥生成3.png
│ └── 秘钥生成4.png
├── Modules
├── HeartBeat.py
├── Module.py
└── example.py
├── README.md
├── Socket
├── GFSocket.py
├── Socket.py
└── __init__.py
├── Tools
├── Notifier.py
├── Ocr.py
├── SinaApi.py
└── 广发证券秘钥生成.html
└── Trade
├── Job.py
├── Quotation.py
└── Trader.py
/.gitignore:
--------------------------------------------------------------------------------
1 | *__init__.py
2 | *__pycache__
3 | *.idea
4 | *config.gf
5 | *config.ocr
6 | *vericode*
7 | *log*
8 |
--------------------------------------------------------------------------------
/AutoTrade.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from time import sleep
3 | from Trade.Trader import Trader
4 | from Trade.Quotation import *
5 | import logging
6 | logging.basicConfig(level=logging.INFO)
7 |
8 | #
9 | # Created by 'changye' on '15-10-27'
10 | #
11 | __author__ = 'changye'
12 |
13 |
14 | class AutoTrade(object):
15 | def __init__(self, trade_account, trade_password, notifier, ocr, step_interval=0.5, simulate=True):
16 | self.__notifier = notifier
17 |
18 | self.__trader = Trader(trade_account, trade_password, notifier=self.__notifier, ocr_service=ocr)
19 |
20 | self.__modules = list()
21 | self.__focus_list = list()
22 | self.__quotes = list()
23 | self.__simulate = simulate
24 | self.__step_interval = step_interval
25 |
26 | self.__current_time = datetime.now()
27 | self.__market_close_time = datetime.now().replace(hour=15, minute=10, second=00)
28 |
29 | def report(self, level='INFO', message='I am ok!'):
30 |
31 | logging.warning('[%s] %s' % (datetime.now().strftime('%m%d-%H:%M:%S'), message))
32 |
33 | if not self.__notifier:
34 | self.__notifier.send('%s[%s]' % (level, datetime.now().strftime('%m%d-%H:%M:%S')), message)
35 |
36 | # 交易系统退出的条件, 如果判读符合条件,则退出交易系统
37 | def ready_to_exit(self):
38 | """
39 | 判断交易系统是否符合退出的条件, 暂定为市场关闭后自动退出
40 | :return: 当市场关闭后(15:00之后), 返回True, 否则返回False
41 | :rtype: bool
42 | """
43 | if self.__current_time > self.__market_close_time:
44 | logging.warning('现在不是交易时间, 自动退出交易, 如果需要改变退出条件,请修改 AutoTrade.py的ready_to_exit函数')
45 | return True
46 | return False
47 |
48 | # 退出系统
49 | def exit(self):
50 | """
51 | 退出系统
52 | :return:
53 | :rtype:
54 | """
55 | self.__trader.sign_out_socket()
56 | self.__trader.exit()
57 | # self.report('INFO', 'AutoTrade system Now exit!')
58 |
59 | # 载入module
60 | def load_module(self, module):
61 | """
62 | module载入函数
63 | :param module: module的实例
64 | :type module: Modules.Module.Module
65 | :return:
66 | :rtype:
67 | """
68 | # 初始化module
69 | module_no = len(self.__modules)
70 | # 给module设置trader
71 | module.set_trader(self.__trader)
72 | # 准备module, 通常在该步骤读取config文件, 或者获取账户信息
73 | module.prepare(module_no)
74 | # 将module加入列表
75 | self.__modules.append(module)
76 |
77 | # 获取module的关注列表
78 | def __get_focus_list_from_module(self):
79 | """
80 | 获取module的关注列表
81 | :return:
82 | :rtype:
83 | """
84 | focus_list = list()
85 | for m in self.__modules:
86 | focus_list += m.focus_list()
87 | self.__focus_list = focus_list
88 |
89 | # 获取focus_list的报价
90 | def __get_quote_of_focus_list(self):
91 | """
92 | 获取关注列表的报价
93 | :return:
94 | :rtype:
95 | """
96 | self.__quotes = get_quote(self.__focus_list)
97 |
98 | # 将报价提供给modules, 触发module的判断机制
99 | def __feed_quotes_back_to_modules(self):
100 | """
101 | 将报价提供给modules, 触发module的判断机制
102 | :return:
103 | :rtype:
104 | """
105 | for m in self.__modules:
106 | jobs = m.need_to_trade(self.__quotes, self.__current_time)
107 | if jobs is not None:
108 | self.__trader.add_jobs_to_pending_list(jobs)
109 |
110 | def prepare(self):
111 |
112 | self.__trader.initial_socket()
113 |
114 | signed_in = self.__trader.sign_in_socket()
115 |
116 | if not signed_in:
117 | auto.report('WARN', 'Can not login!')
118 | exit()
119 |
120 | # auto.report('WARN', 'Login successfully!')
121 |
122 | def start(self):
123 |
124 | self.__trader.start()
125 |
126 | self.__get_focus_list_from_module()
127 |
128 | while not self.ready_to_exit():
129 |
130 | self.__current_time = datetime.now()
131 |
132 | self.__get_quote_of_focus_list()
133 |
134 | self.__feed_quotes_back_to_modules()
135 |
136 | sleep(self.__step_interval)
137 |
138 | self.exit()
139 |
140 |
141 | if __name__ == '__main__':
142 |
143 | import json
144 | # 配置校验码识别系统的用户名和密码(校验码识别由ruokuai.com提供, 请注册该网站的用户(非开发者))
145 | config_ocr = dict()
146 | with open('Configs/Tools.config.ocr', 'r') as f:
147 | config_ocr = json.loads(f.read().replace(r'\n', ''))
148 |
149 | from Tools.Ocr import Ocr
150 | my_ocr = Ocr(config_ocr['account'], config_ocr['password'])
151 |
152 |
153 | config = dict()
154 | with open('Configs/Socket.config.gf', 'r') as f:
155 | config = json.loads(f.read().replace(r'\n', ''))
156 | auto = AutoTrade(config['account'], config['password'], notifier=None, ocr=my_ocr)
157 |
158 | auto.prepare()
159 |
160 | # 导入第一个模块 heart_beat, 该模块的作用是防止trader因为长时间无操作而过期
161 | from Modules.HeartBeat import HeartBeat
162 | heart_beat = HeartBeat(heart_beat_interval_in_minutes=0.5)
163 | auto.load_module(heart_beat)
164 |
165 |
166 |
167 | # 开始运行
168 | auto.start()
169 |
170 |
171 |
172 |
173 |
--------------------------------------------------------------------------------
/Configs/README:
--------------------------------------------------------------------------------
1 | 本目录放置配置文件, 所有全局以及自定义策略(module)相关的配置文件请放在本目录
2 |
--------------------------------------------------------------------------------
/Configs/Socket.config.gf.example:
--------------------------------------------------------------------------------
1 | {
2 | "account":"CL8*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00",
3 | "password":"CL8*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00*00"
4 | }
5 |
--------------------------------------------------------------------------------
/Configs/Tools.config.ocr.example:
--------------------------------------------------------------------------------
1 | {
2 | "account": "XXXXX",
3 | "password": "XXXXX"
4 | }
--------------------------------------------------------------------------------
/Documents/image/run.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/run.jpg
--------------------------------------------------------------------------------
/Documents/image/run.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/run.odg
--------------------------------------------------------------------------------
/Documents/image/start.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/start.jpg
--------------------------------------------------------------------------------
/Documents/image/start.odg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/start.odg
--------------------------------------------------------------------------------
/Documents/image/控件准备.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/控件准备.png
--------------------------------------------------------------------------------
/Documents/image/秘钥生成1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/秘钥生成1.png
--------------------------------------------------------------------------------
/Documents/image/秘钥生成2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/秘钥生成2.png
--------------------------------------------------------------------------------
/Documents/image/秘钥生成3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/秘钥生成3.png
--------------------------------------------------------------------------------
/Documents/image/秘钥生成4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Documents/image/秘钥生成4.png
--------------------------------------------------------------------------------
/Modules/HeartBeat.py:
--------------------------------------------------------------------------------
1 | #
2 | # Created by 'changye' on '15-10-30'
3 | #
4 |
5 | __author__ = 'changye'
6 |
7 | from Modules.Module import Module
8 | from datetime import timedelta
9 | from Trade.Job import Job
10 | import logging
11 |
12 |
13 | class HeartBeat(Module):
14 |
15 | def __init__(self, heart_beat_interval_in_minutes=5):
16 | super().__init__()
17 | self.__heart_beat_interval = heart_beat_interval_in_minutes
18 | self.__last_heart_beat_time = None
19 | # self._last_job = None
20 |
21 | def prepare(self, module_no):
22 | super().prepare(module_no)
23 |
24 | def focus_list(self):
25 | return []
26 |
27 | def need_to_trade(self, quotes, time_stamp):
28 | super().need_to_trade(quotes, time_stamp)
29 |
30 | if self.__last_heart_beat_time is None \
31 | or self.__last_heart_beat_time + timedelta(minutes=self.__heart_beat_interval) < time_stamp:
32 | self.get_balance(force_refresh=True)
33 | self.__last_heart_beat_time = time_stamp
34 | logging.info('Heartbeat! [%s]' % (time_stamp.strftime('%H:%M:%S')))
35 | # if self._last_job is None or self._last_job.action == Job.CANCEL:
36 | # job = self.create_new_job(time_stamp).set(Job.BUY, '511990', 'sh', 100, 99.9, msg='下单购买511990')
37 | # else:
38 | # job = self.create_new_job(time_stamp).set_cancel(self._last_job).set_message('撤单!')
39 | # self._last_job = job
40 | # return [job]
41 |
42 | return None
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/Modules/Module.py:
--------------------------------------------------------------------------------
1 | #
2 | # Created by 'changye' on '15-10-29'
3 | #
4 |
5 | __author__ = 'changye'
6 |
7 | import abc
8 | from Trade.Job import Job
9 | from Trade.Quotation import *
10 | import logging
11 | from datetime import datetime, date
12 |
13 |
14 | class Module(object, metaclass=abc.ABCMeta):
15 |
16 | def __init__(self):
17 | self.__module_no = -1
18 | self.__job_serial_no = 0
19 | self.__trader = None
20 | self.quotes = list()
21 |
22 | def prepare(self, module_no):
23 | self.__module_no = module_no
24 | logging.warning('preparing %s(%d)' % (self.__class__, self.__module_no))
25 |
26 | def set_trader(self, trader):
27 | self.__trader = trader
28 |
29 | def create_new_job(self, time_stamp, simulate=False):
30 | job = Job(self.__module_no, self.__job_serial_no, time_stamp)
31 | if simulate is True:
32 | job.set_simulate(True)
33 | self.__job_serial_no += 1
34 | return job
35 |
36 | @staticmethod
37 | def check_current_time_to(self, hour=00, minute=00, second=00):
38 | """
39 | 检查当前时间和目标时间的关系
40 | :param hour: hour 范围0-23
41 | :type hour: int
42 | :param minute: 范围0-59
43 | :type minute: int
44 | :param second: 范围0-59
45 | :type second: int
46 | :return: 当前时间大于目标时间时返回1, 当前时间等于目标时间时返回0, 当前时间小于目标时间时返回-1
47 | :rtype: int
48 | """
49 | now = datetime.now()
50 | target = datetime.now().replace(hour=hour, minute=minute, second=second)
51 | if now > target:
52 | return 1
53 | if now == target:
54 | return 0
55 | if now < target:
56 | return -1
57 |
58 | @staticmethod
59 | def check_current_date_to(year=datetime.now().year, month=1, dates=1):
60 | """
61 | 检查当前日期和目标日期的关系
62 | :param year: 年
63 | :type year: int
64 | :param month: month
65 | :type month: int
66 | :param date: date
67 | :type date: int
68 | :return: 当前日期大于目标日期时返回1, 当前日期等于目标日期时返回0, 当前日期小于目标日期时返回-1
69 | :rtype: int
70 | """
71 | now = datetime.now().date()
72 | target = date(year, month, dates)
73 | if now > target:
74 | return 1
75 | if now == target:
76 | return 0
77 | if now < target:
78 | return -1
79 |
80 | def ask_at_price(self, code, market, amount, price, time_stamp):
81 | job = self.create_new_job(time_stamp)\
82 | .set(Job.SELL, code, market, amount, price, msg='挂单: sell %d of %s @ %.3f' % (amount, code, price))
83 | return job
84 |
85 | def bid_at_price(self, code, market, amount, price, time_stamp):
86 | job = self.create_new_job(time_stamp)\
87 | .set(Job.BUY, code, market, amount, price, msg='挂单: buy %d of %s @ %.3f' % (amount, code, price))
88 | return job
89 |
90 | def buy_when_price_exceed(self, code, market, amount, price, time_stamp):
91 | """
92 | 在价格低于某个值时购买
93 | :param code: 代码
94 | :type code: str
95 | :param market: 市场, sh或sz
96 | :type market: str
97 | :param amount: 数量
98 | :type amount: int
99 | :param price: 价格
100 | :type price: float
101 | :param time_stamp: 时间戳
102 | :type time_stamp: datetime
103 | :return: None为失败, 成功返回job
104 | :rtype: Job
105 | """
106 |
107 | query_code = market + code
108 |
109 | if query_code not in self.quotes:
110 | return None
111 | quote = self.quotes[query_code]
112 | average_price, amount_buy, amount_all_ask, highest_price \
113 | = get_average_price_of_certain_amount_buy(quote, amount)
114 |
115 | if average_price <= price and amount_buy >= amount:
116 | job = self.create_new_job(time_stamp)\
117 | .set(Job.BUY, code, market, amount, price,
118 | msg='市场价格%.3f小于等于目标价格%.3f, 买入%s %d股' % (average_price, price, code, amount))
119 | return job
120 | else:
121 | return None
122 |
123 | def sell_when_price_exceed(self, code, market, amount, price, time_stamp):
124 | """
125 | 在价格高于某个值时卖出
126 | :param code: 代码
127 | :type code: str
128 | :param market: 市场, sh或sz
129 | :type market: str
130 | :param amount: 数量
131 | :type amount: int
132 | :param price: 价格
133 | :type price: float
134 | :param time_stamp: 时间戳
135 | :type time_stamp: datetime
136 | :return: None为失败, 成功返回job
137 | :rtype: Job
138 | """
139 |
140 | query_code = market + code
141 |
142 | if query_code not in self.quotes:
143 | return None
144 | quote = self.quotes[query_code]
145 | average_price, amount_sell, amount_all_bid, lowest_price \
146 | = get_average_price_of_certain_amount_sell(quote, amount)
147 |
148 | if average_price >= price and amount_sell >= amount:
149 | job = self.create_new_job(time_stamp)\
150 | .set(Job.SELL, code, market, amount, price,
151 | msg='市场价格%.3f大于等于目标价格%.3f, 卖出%s %d股' % (average_price, price, code, amount))
152 | return job
153 | else:
154 | return None
155 |
156 | @property
157 | def module_no(self):
158 | return self.__module_no
159 |
160 | @abc.abstractmethod
161 | def focus_list(self):
162 | raise NotImplementedError('focus_list must be defined.')
163 |
164 | def need_to_trade(self, quotes, time_stamp):
165 | self.quotes = quotes
166 |
167 | def get_stock_position(self, force_refresh=False):
168 | return self.__trader.get_stock_position()
169 |
170 | def get_balance(self, force_refresh=False):
171 | return self.__trader.get_balance()
172 |
173 | def check_job_status(self, job):
174 | return self.__trader.check_job_status(job)
175 |
--------------------------------------------------------------------------------
/Modules/example.py:
--------------------------------------------------------------------------------
1 | from Modules.Module import Module
2 | from datetime import timedelta
3 | from Trade.Job import Job
4 | from Trade.Job import Dependence
5 |
6 | #
7 | # Created by 'changye' on '15-11-18'
8 | #
9 |
10 | __author__ = 'changye'
11 |
12 |
13 | class Example(Module):
14 | def __init__(self, config):
15 | super().__init__()
16 |
17 | # 对Module初始化操作
18 | import os
19 | if not config or not os.path.isfile(config):
20 | self.__config = None
21 | raise FileNotFoundError
22 | else:
23 | self.__config = config
24 |
25 | self.__error = False
26 |
27 | # 用户在此处添加自定义的Module初始化
28 |
29 |
30 | def prepare(self, module_no):
31 | super().prepare(module_no)
32 | # 用户在此处进行Module的准备工作, 包括读取配置文件, 准备关注标的物信息等
33 |
34 |
35 | def focus_list(self):
36 |
37 | # 用户在此处将需要关注的标的物加入列表,并返回
38 | # 如果没有任何关注标的,返回空列表
39 | # 例子:
40 | # return ['sh600004, sh600036, sz000002']
41 |
42 | return []
43 |
44 | def need_to_trade(self, quotes, time_stamp):
45 | super().need_to_trade(quotes, time_stamp)
46 | if self.__error:
47 | return None
48 |
49 | # 用户策略在这里加入, 本函数会被系统反复调用, 本函数的输入共两个,
50 | # 其中quotes为关注的标的物的报价信息.
51 | # time_stamp为时间戳, 类行为datatime
52 |
53 | # 交易下单过程
54 | # 1.声明一个队列存放交易单
55 | # jobs = list()
56 |
57 | # 买入
58 | # job1 = self.create_new_job(time_stamp)\
59 | # .set(Job.BUY, '600004', 'sh', 10000, 12.8)\
60 | # .set_message('买入 %s %d @ $%.3f' % ('600004', 'sh', 10000, 12.8))
61 | # jobs.append(job1)
62 |
63 | # 卖出
64 | # job2 = self.create_new_job(time_stamp)\
65 | # .set(Job.SELL, '600004', 'sh', 10000, 12.8)\
66 | # .set_message('买入 %s %d @ $%.3f' % ('600004', 'sh', 10000, 12.8))
67 | # jobs.append(job2)
68 |
69 | # 存在依赖关系的交易
70 | # 下面的例子提交了一个交易指令: 当job1成交后再执行买入操作
71 | # job3 = self.create_new_job(time_stamp)\
72 | # .set(Job.SELL, '600004', 'sh', 10000, 12.8)\
73 | # .set_message('买入 %s %d @ $%.3f' % ('600014', 'sh', 10000, 15))
74 | # job3.add_dependence(Dependence(job1, Job.TRADED_ALL))
75 | # jobs.append(job3)
76 |
77 | # 注意: jobs.append(XX) 仅仅是将XX交易添加到交易列表, \
78 | # 只有当本函数返回的时候, 所有的交易指令才会一并提交给交易系统执行.
79 | # 执行的先后顺序按照先入先出的规律, 存在依赖关系的交易除外
80 | # return jobs
81 |
82 | # 如果不需要任何交易, 返回空列表
83 | return []
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | AutoTrade --- 自动化交易框架
2 | ====================
3 |
4 | # 1 简介
5 | AutoTrade是一款自动化交易框架. 注意, 是框架而不仅仅是一个券商交易接口(当然, 里面也包含了一个完整的券商接口,具体是哪个券商的,下面会介绍).
6 |
7 | ## 1.1 什么是框架?
8 | AutoTrade 是一个自动化交易框架, 类似于web设计里的MVC框架, AutoTrad也分为M(odel), T(rade), S(ocket) 三个部分.
9 |
10 | Model 是指交易策略, 之所以叫model是因为AutoTrade采用模块化的思维管理策略, 也就是说, 在一个交易框架里允许多个策略同时运行, 每一个策略理论上可以做到互不干扰.
11 | Model 是一个交易系统生存的灵魂, 这也是需要用户发挥想象力的地方.
12 |
13 | Socket 是指交易的接口. AutoTrade已经实现了广发证券web接口的部分功能, 包括 股票的买入和卖出, 基金的申购和赎回, 分级基金的拆分和合并等等.
14 | 随着开发的继续, 功能还会继续完善.
15 |
16 | Trade 是指交易管理, 例如交易的处理, 交易状态的维护等等.
17 | Trade 是Socket(接口) 与 Model(策略)的桥梁, 对于Model来说, 不需要关心具体的下单细节, 只需要发出相应的买入卖出指令就可以了.
18 |
19 | 上面三个部分,`需要用户开发的只有Model`. 换句话说, 用户只要专注于交易策略, Socket和Trade会把剩下的所有交易细节都帮你做好.
20 |
21 | ## 1.2 AutoTrade可以做什么? 不能做什么?
22 |
23 | > 可以做:
24 | >> 股票的自动网格买卖
25 | >> 预设价格的自动盯盘交易
26 | >> 分级基金的自动化分拆/合并套利
27 | >> 股票异常价格捕捉和交易
28 |
29 | >不能做:
30 | >> 依靠速度取胜的策略
31 | >> 过于高频的交易
32 | >> 运算量过于庞大的策略
33 |
34 | ## 1.3 AutoTrade是如何运行的?
35 | AutoTrade 的运行分为三个大的阶段
36 | > 1. 交易的准备
37 | >> a. 登录券商系统
38 | >> b. 初始化log系统
39 |
40 | > 2. 策略的准备
41 | >> a.载入用户策略
42 | >> b.将用户策略中关注的标的物加入关注表
43 |
44 | > 3. 策略的执行
45 | >> a. 获取关注表中标的物的报价信息
46 | >> b. 将这些信息返回给策略模块
47 | >> c. 按照顺序执行各个策略模块(为了防止策略之间产生冲突, 策略模块按照顺序执行)
48 | >> d. 执行策略模块提交的交易请求
49 | >> e. 已经下达订单的状态更新等维护操作
50 |
51 | 上面的三个阶段第1和第2均阶段在交易系统启动时执行一遍, 第三个阶段会反复执行, 直到发生错误或满足用户定义的退出条件.
52 |
53 | 
54 |
55 | 
56 |
57 | # 准备工作
58 | ## 2.1 账户
59 | 使用本框架需要具备下面两个基本账户
60 | 1. 广发证券账户, 作为下单账户
61 | 2. 校验码在线识别账户, 本框架选择ruokuai.com进行校验码的在线识别(基本能够做到90%以上)
62 |
63 | ## 2.2 广发证券登录秘钥生成
64 | 广发证券的web端口并不直接使用客户的用户名和交易密码登录, 它使用的是一个由用户名和密码加密后产生的秘钥对, 由于在网络上传输的是这个加密后的密钥对, 因此就不存在客户的真实用户名和密码泄露的情况.
65 | 因此, 我们需要先生成加密后的秘钥对, 然后再使用这组密钥对登录广发证券的web接口. 生成密钥对利用了广发证券页面交易系统的web控件, 我们可以利用这个控件, 将密钥对生成并保存为配置文件, 这样AutoTrade框架就可以直接调用这个配置文件自动登录广发证券的web交易接口了.
66 |
67 | 请按照下面的步骤获取交易密钥对:
68 | 1. 使用IE登录http://trade.gf.com.cn页面, 确保密码控件正确显示. (如果不显示,请按照提示安装控件)
69 | 
70 | 2. 使用IE 打开Tools目录下的本地文件: "广发证券秘钥生成.html"
71 | 
72 | 3. 允许本地运行activex, 使控件可以在页面上正常显示
73 | 
74 | 4. 填写广发证券账号和密码, 点击生成秘钥. 注意, IE可能需要你允许ActiveX交互, 点击是,允许交互
75 | 
76 | 5. 生成秘钥后存储成文件, 建议不要使用windows自带的notepad, 使用sublime或者其他专业编辑文件, 并保存为utf8格式
77 | 
78 | 6. 将准备好的Socket.config.gf文件拷贝到AutoTrade的Configs目录下
79 | 至此, 广发证券的秘钥配置文件就准备好了
80 |
81 |
82 | ## 2.3 ruokuai账号申请
83 | 1. 注册用户账号并充值(充一块钱足够了)
84 | 2. 将账号和密码填写到Configs目录下Tools.config.ocr.example文件中, 并将文件名修改为:Tools.config.ocr
85 | 至此, 验证码识别配置文件就准备好了.
86 |
87 |
88 |
--------------------------------------------------------------------------------
/Socket/GFSocket.py:
--------------------------------------------------------------------------------
1 | from Socket.Socket import Socket
2 | import uuid
3 | import re
4 | import requests
5 | from random import random as rand
6 | import json
7 | from PIL import Image
8 | from time import sleep, time, strftime
9 | import logging
10 | # logging.basicConfig(level=logging.INFO)
11 | #
12 | # Created by 'changye' on '15-9-03'
13 | # Email: changye17@163.com
14 | #
15 | # 广发交易接口
16 | # 测试环境: WIN7 python3.4
17 | # 注意事项:
18 | # 调用try_auto_login() 自动OCR 需要安装tesseract-ocr
19 | # 调用show_verify_code() 自动显示验证码,需要安装imagemagick
20 | #
21 | # 使用方法:
22 | # 初始化
23 | # gf = GFSocket('encrypted_account','encrypted_password')
24 | #
25 | # 自动登录:
26 | # while(not gf.try_auto_login()):
27 | # time.sleep(3)
28 | #
29 | # 手动登录:
30 | # gf.prepare_login() 第一步,读取验证码
31 | # gf.show_verify_code() 第二步,显示验证码(也可省略,直接看目录下vericode.jsp文件)
32 | # vericode = input("input verify code: ")
33 | # gf.enter_verify_code(vericode) 第三步,输入验证码
34 | # gf.login() 第四步,登陆 成功返回True,失败返回False
35 | # gf.prepare_trade() 第五步,获取交易参数, 完成此步后即可进行交易.
36 | #
37 | #
38 | # gf可读取参数
39 | # gf.balance 账目信息,字典
40 | # {
41 | # 'mortgage_balance': '0',
42 | # 'square_flag': ' ',
43 | # 'money_type_dict': '人民币',
44 | # 'foregift_balance': '0',
45 | # 'pre_interest': '0',
46 | # 'entrust_buy_balance': '0',
47 | # 'bank_name': '',
48 | # 'fund_account': 'xxxxx',
49 | # 'opfund_market_value': '0',
50 | # 'enable_balance': '0', 可用
51 | # 'market_value': '0',
52 | # 'pre_interest_tax': '0',
53 | # 'rate_kind': ' ',
54 | # 'asset_prop': '0',
55 | # 'integral_balance': '0',
56 | # 'pre_fine': '0',
57 | # 'money_type': '0',
58 | # 'begin_balance': '0',
59 | # 'interest': '0',
60 | # 'main_flag': '1',
61 | # 'fetch_cash': '0', 可取
62 | # 'fetch_balance_old': '0',
63 | # 'net_asset': 19090.0, 净资产
64 | # 'frozen_balance': '0',
65 | # 'asset_balance': 19090.0, 资产
66 | # 'correct_balance': '0',
67 | # 'current_balance': 19090.0, 当前账目
68 | # 'unfrozen_balance': '0',
69 | # 'fetch_balance': '0',
70 | # 'fund_balance': 19090.0,
71 | # 'bank_no': '',
72 | # 'fine_integral': '0'
73 | # }
74 |
75 | # gf.stock_position 仓位信息,列表,列表元素为字典
76 | # {
77 | # 'delist_flag': '0',
78 | # 'entrust_sell_amount': '0',
79 | # 'real_buy_amount': 100.0,
80 | # 'av_income_balance': '0',
81 | # 'income_balance': '-29.70',
82 | # 'exchange_type': '1',
83 | # 'stock_type': 'j',
84 | # 'keep_cost_price': 100.305,
85 | # 'exchange_type_dict': '上海',
86 | # 'delist_date': '0',
87 | # 'market_value': 10000.8,
88 | # 'cost_price': 100.305,
89 | # 'enable_amount': 100.0, 可用
90 | # 'last_price': 100.008,
91 | # 'cost_balance': 10030.5,
92 | # 'par_value': 1.0,
93 | # 'hold_amount': '0',
94 | # 'frozen_amount': '0',
95 | # 'av_buy_price': '0',
96 | # 'stock_account': 'XXXXXXXX',
97 | # 'position_str': 'XXX',
98 | # 'income_balance_nofare': '-29.70',
99 | # 'fund_account': '12321435',
100 | # 'stock_name': '华宝添益',
101 | # 'uncome_buy_amount': '0',
102 | # 'asset_price': 100.008,
103 | # 'client_id': '020290017429',
104 | # 'stock_code': '511990', 代码
105 | # 'real_sell_amount': '0',
106 | # 'uncome_sell_amount': '0',
107 | # 'hand_flag': '0',
108 | # 'current_amount': 100.0 持仓
109 | # }
110 | # gf.cancel_list 可撤单清单,列表,列表元素为字典
111 |
112 | # gf.entrust_list 委托清单,列表,列表元素为字典
113 | # {
114 | # 'business_amount': '0',
115 | # 'entrust_amount': 1.0,
116 | # 'report_milltime': '83902147',
117 | # 'entrust_price': 1.0,
118 | # 'init_date': '20150910',
119 | # 'report_no': '169',
120 | # 'curr_milltime': '81129637',
121 | # 'entrust_status': '8',
122 | # 'stock_account': 'XXXXXXXX',
123 | # 'business_balance': '0',
124 | # 'entrust_date': '20150910',
125 | # 'entrust_way': '4',
126 | # 'stock_code': '799999',
127 | # 'entrust_no': '169',
128 | # 'entrust_type': '0',
129 | # 'stock_name': '登记指定',
130 | # 'business_price': '0',
131 | # 'entrust_bs_dict': '买入',
132 | # 'cancel_info': ' ',
133 | # 'entrust_type_dict': '委托',
134 | # 'entrust_prop': '6',
135 | # 'entrust_bs': '1',
136 | # 'fund_account': 'XXXXXXX',
137 | # 'exchange_type': '1',
138 | # 'entrust_time': '81129',
139 | # 'position_str': 'XXXXXXXX',
140 | # 'report_time': '83902',
141 | # 'withdraw_amount': '0',
142 | # 'entrust_status_dict': '已成',
143 | # 'batch_no': '169'
144 | # }
145 |
146 | # gf.trade_list 成交清单,列表,列表元素为字典
147 | # {
148 | # 'business_time': '100149',
149 | # 'business_times': '1',
150 | # 'business_amount': 100.0,
151 | # 'stock_name': '华宝添益',
152 | # 'position_str': '20150910041001495380020200004584',
153 | # 'business_price': 100.005,
154 | # 'entrust_prop': '0',
155 | # 'entrust_bs': '1',
156 | # 'report_no': '5250',
157 | # 'exchange_type': '1',
158 | # 'entrust_bs_dict': '买入',
159 | # 'serial_no': '4584',
160 | # 'business_no': '3779535',
161 | # 'date': '20150910',
162 | # 'business_balance': 10000.5,
163 | # 'stock_code': '511990',
164 | # 'stock_account': 'XXXXXX',
165 | # 'fund_account': 'XXXXXX',
166 | # 'real_type': '0',
167 | # 'real_status': '0',
168 | # 'entrust_no': '5250'
169 | # }
170 |
171 | # gf可操作函数
172 | # 买入,成功返回委托号,如果失败返回None
173 | # gf.buy(code='600036', amount=100, price=6.66, market='sh')
174 | #
175 | # 卖出,成功返回委托号,如果失败返回None
176 | # gf.sell(code='600036', amount=100, price=6.66, market='sh'))
177 | #
178 | # 撤单,成功返回委托号,如果失败返回None
179 | # gf.cancel(entrust_no='12345')
180 | # gf.cancel_all()
181 |
182 | # 下面的函数为内部函数,请不要直接运行
183 | # 连接站点并获取信息,成功返回True,失败返回False
184 | # gf._get_balance() 获取账目
185 | # gf._get_position() 获取仓位
186 | # gf._get_cancel_list() 获取可撤单列表
187 | # gf._get_today_entrust() 获取今日委托列表
188 | # gf._get_today_trade() 获取今日成交列表
189 |
190 |
191 | __author__ = 'changye'
192 |
193 |
194 | class GFSocket(Socket):
195 |
196 | def __init__(self, encrypted_account, encrypted_password,
197 | hd_model="WD-WX31C32M1910", retry_time=3, retry_interval=0.5):
198 |
199 | # 加密后的用户账号
200 | self.__encrypted_account = encrypted_account
201 |
202 | # 用户加密后的交易密码
203 | self.__encrypted_password = encrypted_password
204 |
205 | # 用户硬盘型号
206 | self.__harddisk_model = hd_model
207 |
208 | # # 用户类型
209 | # self.__user_type = "jy"
210 |
211 | # 获取mac地址
212 | self.__mac_addr = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
213 | logging.info(self.__mac_addr)
214 |
215 | # 获取ip地址
216 | self.__ip_addr = '192.168.1.123'
217 | import socket
218 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
219 | try:
220 | s.connect(("baidu.com", 80))
221 | self.__ip_addr = s.getsockname()[0]
222 | finally:
223 | if s:
224 | s.close()
225 | logging.info(self.__ip_addr)
226 |
227 | # 验证码
228 | self.__verify_code = ""
229 |
230 | # 欢迎页面,可在该页面获得验证码
231 | self.__welcome_page = "https://trade.gf.com.cn/"
232 |
233 | # 验证码地址
234 | self.__verify_code_page = "https://trade.gf.com.cn/yzm.jpgx?code="
235 | self.__verify_code_length = 5
236 |
237 | # 登录页面
238 | self.__login_page = "https://trade.gf.com.cn/login"
239 |
240 | # 交易接口
241 | self.__trade_page = "https://trade.gf.com.cn/entry"
242 |
243 | # 初始化浏览器
244 | self.__browser = requests.session()
245 | # 设置user-agent
246 | self.__browser.headers['User-Agent'] = 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; Trident/6.0)'
247 |
248 | # 交易传输dse_sessionId
249 | self.__dse_sessionId = None
250 |
251 | # 股东账号
252 | # 上海
253 | self.__market_account_sh = None
254 | # 深圳
255 | self.__market_account_sz = None
256 |
257 | # # 交易参数
258 | # self.__trade_version = 1
259 | # self.__op_entrust_way = 7
260 |
261 | # 账目信息
262 | # 账目信息为一个字典
263 | self.__balance = None
264 |
265 | # 持仓信息
266 | # 持仓为一个数组,即list()
267 | # 数组中的元素为字典
268 | self.__stock_position = None
269 |
270 | # 可撤单交易列表
271 | # 可撤单交易列表为一个数组,即list()
272 | # 数组中的元素为字典,其内容示例为:
273 | # {
274 | # 'entrust_amount': 1000.0, 委托数量
275 | # 'exchange_type': '1',
276 | # 'entrust_prop': '0',
277 | # 'entrust_status': '2', 2为已报;
278 | # 'business_amount': '0',
279 | # 'business_price': '0',
280 | # 'entrust_no': '24555', 委托号
281 | # 'entrust_time': '110952',
282 | # 'entrust_price': 102.5, 委托价格
283 | # 'stock_name': '银华日利',
284 | # 'exchange_name': '上海A',
285 | # 'bs_name': '买入',
286 | # 'stock_account': 'A11111111', 股东账户
287 | # 'stock_code': '511880', 股票代码
288 | # 'entrust_bs': '1', 1为买入;2为卖出
289 | # 'status_name': '已报',
290 | # 'prop_name': '买卖'
291 | # }
292 | self.__cancel_list = None
293 |
294 | # 当日委托列表
295 | # 当日委托列表为一个数组,即list()
296 | # 数组中的元素为字典,其内容示例为:
297 | # {
298 | # 'entrust_price': 102.533, 委托价格
299 | # 'stock_account': 'A1111111', 股东账户
300 | # 'entrust_time': '110849', 委托时间
301 | # 'entrust_amount': 100.0, 委托数量
302 | # 'stock_name': '银华日利',
303 | # 'status_name': '已成',
304 | # 'exchange_type': '1',
305 | # 'prop_name': '买卖',
306 | # 'bs_name': '买入',
307 | # 'entrust_status': '8', 8为已成,9为废单,6为已撤,2为已报
308 | # 'entrust_no': '24410', 委托号
309 | # 'business_price': 102.533,
310 | # 'business_amount': 100.0,
311 | # 'entrust_prop': '0',
312 | # 'stock_code': '511880', 股票代码
313 | # 'entrust_bs': '1', 1为买入,2为卖出
314 | # 'exchange_name': '上海A'
315 | # }
316 | self.__entrust_list = None
317 |
318 | # 当日成交列表
319 | # 当日成交列表为一个数组,即list()
320 | # 数组中的元素为字典,其内容示例为:
321 | # {
322 | # 'business_amount': 200.0, 成交数量
323 | # 'stock_code': '511990', 股票代码
324 | # 'date': '20150828', 成交日期
325 | # 'bs_name': '卖出',
326 | # 'remark': '成交',
327 | # 'business_balance': 20001.6, 成交金额
328 | # 'stock_name': '华宝添益',
329 | # 'exchange_type': '上海A',
330 | # 'stock_account': 'A11111111', 股东账户
331 | # 'entrust_no': '26717', 委托号
332 | # 'business_price': 100.008, 成交均价
333 | # 'serial_no': '35486' 流水号
334 | # }
335 | self.__trade_list = None
336 |
337 | # 连接失败后,再次尝试的时间间隔
338 | self.__retry_interval = retry_interval
339 | # 连接失败后的尝试次数
340 | self.__retry_time = retry_time
341 |
342 | # @property
343 | def market_account_sh(self):
344 | return self.__market_account_sh
345 |
346 | # @property
347 | def market_account_sz(self):
348 | return self.__market_account_sz
349 |
350 | def verify_code_length(self):
351 | return self.__verify_code_length
352 |
353 | # @property
354 | def balance(self):
355 | counter = 0
356 | while (counter < self.__retry_time) and (not self._get_balance()):
357 | sleep(self.__retry_interval)
358 | counter += 1
359 | return self.__balance
360 |
361 | # @property
362 | def stock_position(self):
363 | counter = 0
364 | while (counter < self.__retry_time) and (not self._get_position()):
365 | sleep(self.__retry_interval)
366 | counter += 1
367 | return self.__stock_position
368 |
369 | # @property
370 | def cancel_list(self):
371 | counter = 0
372 | while (counter < self.__retry_time) and (not self._get_cancel_list()):
373 | sleep(self.__retry_interval)
374 | counter += 1
375 | return self.__cancel_list
376 |
377 | # @property
378 | def entrust_list(self):
379 | counter = 0
380 | while (counter < self.__retry_time) and (not self._get_today_entrust()):
381 | sleep(self.__retry_interval)
382 | counter += 1
383 | return self.__entrust_list
384 |
385 | # @property
386 | def trade_list(self):
387 | counter = 0
388 | while (counter < self.__retry_time) and (not self._get_today_trade()):
389 | sleep(self.__retry_interval)
390 | counter += 1
391 | return self.__trade_list
392 |
393 | # 获取验证码
394 | def get_verify_code(self):
395 | vericode_url = self.__verify_code_page + str(rand())
396 | # logging.info(vericode_url);
397 | resp = self.__browser.get(vericode_url)
398 | with open('./vericode.jpeg', 'wb') as file:
399 | file.write(resp.content)
400 | # logging.warning("verify code is stored in vericode.jsp. "
401 | # "use enter_verify_code() to input the code and then login()!")
402 | return './vericode.jpeg'
403 |
404 | # 显示验证码,需要安装imagemagick
405 | def show_verify_code(self):
406 | image = Image.open("./vericode.jpeg")
407 | image.show()
408 |
409 | # 识别验证码
410 | def recognize_verify_code(self):
411 | import tempfile
412 | import subprocess
413 | import os
414 |
415 | path = "./vericode.jpeg"
416 |
417 | temp = tempfile.NamedTemporaryFile(delete=False)
418 | process = subprocess.Popen(['tesseract', path, temp.name], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
419 | process.communicate()
420 |
421 | with open(temp.name + '.txt', 'r') as handle:
422 | contents = handle.read()
423 |
424 | os.remove(temp.name + '.txt')
425 | os.remove(temp.name)
426 |
427 | return contents.replace(" ", "").replace("\n", "").replace("><", "x")
428 |
429 | # 输入验证码
430 | def enter_verify_code(self, text):
431 | self.__verify_code = text
432 | logging.warning("verify code is entered: " + text)
433 |
434 | # 登录准备,获取验证码
435 | def prepare_login(self):
436 | # 建立初始连接
437 | self.__browser.get(self.__welcome_page, verify=False)
438 | # 获取验证码
439 | return self.get_verify_code()
440 |
441 | # 输入加密后的账号和密码
442 | def enter_encrypted_account_and_password(self, encrypted_account, encrypted_password):
443 | self.__encrypted_account = encrypted_account
444 | self.__encrypted_password = encrypted_password
445 |
446 | # 获取当前毫秒数
447 | @staticmethod
448 | def __get_msec():
449 | return int(time() * 1000)
450 |
451 | # 将字典数据打包成字符串
452 | @staticmethod
453 | def __join_keys(keys):
454 | return '&'.join([k + "=" + str(keys[k]) for k in keys])
455 |
456 | # 将dict中的value转换为float,如果可能的话.
457 | # 因为部分股票代码,如000002是以0开头的,所以转化为int会造成股票代码的错误
458 | # 因此,即使可以转换为整数,也保持其字符串属性不变
459 | @staticmethod
460 | def __convert_value_in_dict_to_float(input_dict):
461 | result = dict()
462 | for (k, v) in input_dict.items():
463 | if re.match(r'^\d+\.\d+$', v):
464 | value = float(v)
465 | elif re.match(r'^\d+$', v):
466 | if k.find('account') < 0 and k.find('code') < 0:
467 | value = int(v)
468 | else:
469 | value = v
470 | else:
471 | value = v
472 | result[k] = value
473 | return result
474 |
475 | # 自动登录,并进入交易页面, 系统需要安装tesseract-ocr
476 | def try_auto_login(self):
477 | # 连接欢迎页面,获取验证码
478 | self.prepare_login()
479 | # 识别验证码
480 | verify_code = self.recognize_verify_code()
481 | # 输入验证码
482 | self.enter_verify_code(verify_code)
483 | # 登录
484 | return self.login()
485 |
486 | # 连接交易服务器,将query 和payload发送至交易服务区,并返回结果
487 | def __connect_trade_server(self, query, payload, no_judgement=False):
488 | # 每次发送携带的sessionId不能为None
489 | if self.__dse_sessionId is None:
490 | return None
491 | else:
492 | stamp = '&dse_sessionId=' + self.__dse_sessionId
493 | # 将query转化为字符串
494 | query_string = ''
495 | if query:
496 | query_string = '?' + self.__join_keys(query) + stamp
497 |
498 | if payload:
499 | payload['dse_sessionId'] = self.__dse_sessionId
500 |
501 | # 连接交易服务器
502 | url = self.__trade_page + query_string
503 | # logging.info(query_string)
504 | # for k, v in payload.items():
505 | # logging.info('%s\t:\t%s' % (k, v))
506 | resp = self.__browser.post(url, data=payload)
507 | # 获取返回数据, 给key加上双引号
508 | content = resp.text.replace('\n', '').replace('\'', '\"')
509 | try:
510 | return_in_json = json.loads(content, parse_int=int)
511 | logging.info('return raw is: ' + content)
512 | except:
513 | content = re.sub(r'([^{":,]+):', '\"\\1\":', content)
514 | logging.info('return raw is: ' + content)
515 | return_in_json = json.loads(content)
516 |
517 | # 如果不需要判断是否返回正常,则直接返回数据.
518 | if no_judgement:
519 | return return_in_json
520 | # 判断返回数据是否成功
521 | if 'success' in return_in_json and return_in_json['success'] is True:
522 | return return_in_json
523 | else:
524 | return None
525 |
526 |
527 | # 登录
528 | def login(self):
529 | payload = {
530 | 'username': self.__encrypted_account,
531 | 'password': self.__encrypted_password,
532 | 'tmp_yzm': self.__verify_code,
533 | 'authtype': 2,
534 | 'mac': self.__mac_addr,
535 | 'disknum': self.__harddisk_model,
536 | 'loginType': 2,
537 | 'origin': 'web'
538 | }
539 | resp = self.__browser.post(self.__login_page, data=payload)
540 | content = resp.text.replace("\r\n", "")
541 | # 获取交易需要的sessionId
542 | self.__dse_sessionId = self.__browser.cookies.get('dse_sessionId')
543 | logging.info('dse_sessionId is: ' + str(self.__dse_sessionId))
544 | # with open('log', 'w') as f:
545 | # f.write(content)
546 | login_return = json.loads(content)
547 | logging.info(str(login_return))
548 | if 'success' in login_return and login_return['success'] is True \
549 | and self.__dse_sessionId is not None:
550 | return True
551 | else:
552 | return False
553 |
554 | # 退出登录
555 | def logout(self):
556 | query = {}
557 | payload = {
558 | 'classname': 'com.gf.etrade.control.AuthenticateControl',
559 | 'method': 'logout'
560 | }
561 | result = self.__connect_trade_server(query, payload)
562 | if result:
563 | return True
564 | else:
565 | return False
566 |
567 | # 获取股东账号
568 | def prepare_trade(self):
569 | query = {}
570 | payload = {
571 | 'classname': 'com.gf.etrade.control.StockUF2Control',
572 | 'method': 'getStockHolders'
573 | }
574 | result = self.__connect_trade_server(query, payload)
575 | if result:
576 | # 获取股票账户
577 | for market_account in result['data']:
578 | if 'exchange_type' in market_account:
579 | if market_account['exchange_type'] == '1':
580 | self.__market_account_sh = market_account['stock_account']
581 | if market_account['exchange_type'] == '2':
582 | self.__market_account_sz = market_account['stock_account']
583 | return True
584 | else:
585 | return False
586 |
587 | # 获取实时行情
588 | def get_realtime_quote(self, code):
589 | query = {}
590 | payload = {
591 | 'classname': 'com.gf.etrade.control.StockUF2Control',
592 | 'method': 'getStockHQ',
593 | 'stock_code': code
594 | }
595 | result = self.__connect_trade_server(query, payload, no_judgement=True)
596 | if 'hq' in result and 'success' in result['hq'] and result['hq']['success']:
597 | return result['hq']
598 | else:
599 | return None
600 |
601 | # 连接, 获取账户信息
602 | def _get_balance(self):
603 | query = {
604 | 'classname': 'com.gf.etrade.control.StockUF2Control',
605 | 'method': 'queryFund',
606 | '_dc': self.__get_msec()
607 | }
608 | payload = {
609 | }
610 | result = self.__connect_trade_server(query, payload)
611 | if result:
612 | for balance in result['data']:
613 | if 'money_type' in balance and balance['money_type'] == '0':
614 | self.__balance = self.__convert_value_in_dict_to_float(balance)
615 | logging.info('balance is : ' + str(self.__balance))
616 | return True
617 | else:
618 | return False
619 |
620 | # 查询某个股票的可用余额
621 | def _get_enable_amount(self, stock_code, exchange_type):
622 | query = {
623 | 'classname': 'com.gf.etrade.control.StockUF2Control',
624 | 'method': 'queryKYYE'
625 | }
626 | payload = {
627 | 'stock_code': stock_code,
628 | 'exchange_type': exchange_type
629 | }
630 | result = self.__connect_trade_server(query, payload)
631 | if result:
632 | return result['data'][0]['enable_amount']
633 | else:
634 | return None
635 |
636 | def get_enable_amount(self, stock_code, market='sh'):
637 | if market == 'sh':
638 | return self._get_enable_amount(stock_code, 1)
639 | if market == 'sz':
640 | return self._get_enable_amount(stock_code, 2)
641 | return None
642 |
643 | # 连接服务器, 执行买入操作, 成功返回成交单号, 失败返回None
644 | def _buy(self, account, exchange_type, code, amount, price):
645 | query = {
646 | 'classname': 'com.gf.etrade.control.StockUF2Control',
647 | 'method': 'entrust'
648 | }
649 | payload = {
650 | 'exchange_type': exchange_type, # 1为上海户, 2为深证户
651 | 'stock_account': account,
652 | 'stock_code': code,
653 | 'entrust_amount': amount,
654 | 'entrust_price': price,
655 | 'entrust_prop': 0,
656 | 'entrust_bs': 1
657 | }
658 | result = self.__connect_trade_server(query, payload)
659 | if result:
660 | for order in result['data']:
661 | if 'entrust_no' in order:
662 | return int(order['entrust_no'])
663 | return self.TRADE_FAIL
664 |
665 | # 连接服务器, 执行卖出操作, 成功返回成交单号, 失败返回None
666 | def _sell(self, account, exchange_type, code, amount, price):
667 | query = {
668 | 'classname': 'com.gf.etrade.control.StockUF2Control',
669 | 'method': 'entrust'
670 | }
671 | payload = {
672 | 'exchange_type': exchange_type, # 1为上海户, 2为深证户
673 | 'stock_account': account,
674 | 'stock_code': code,
675 | 'entrust_amount': amount,
676 | 'entrust_price': price,
677 | 'entrust_prop': 0,
678 | 'entrust_bs': 2
679 | }
680 | result = self.__connect_trade_server(query, payload)
681 | if result:
682 | for order in result['data']:
683 | if 'entrust_no' in order:
684 | return int(order['entrust_no'])
685 | return self.TRADE_FAIL
686 |
687 | def _cancel_all(self, direction='all'):
688 | query = {
689 | 'classname': 'com.gf.etrade.control.StockUF2Control',
690 | 'method': 'cancelall'
691 | }
692 |
693 | if direction == 'buy':
694 | query = {
695 | 'classname': 'com.gf.etrade.control.StockUF2Control',
696 | 'method': 'cancelbuy'
697 | }
698 |
699 | if direction == 'sell':
700 | query = {
701 | 'classname': 'com.gf.etrade.control.StockUF2Control',
702 | 'method': 'cancelsell'
703 | }
704 | payload = {}
705 | result = self.__connect_trade_server(query, payload)
706 | if result:
707 | return True
708 | else:
709 | return False
710 |
711 | # 连接服务器, 执行撤单操作, 成功返回True, 失败返回False
712 | def cancel(self, entrust_nos):
713 | query = {
714 | 'classname': 'com.gf.etrade.control.StockUF2Control',
715 | 'method': 'cancels'
716 | }
717 | payload = {
718 | 'entrust_nos': entrust_nos,
719 | 'batch_flag': 0
720 | }
721 | result = self.__connect_trade_server(query, payload)
722 | if result:
723 | return True
724 | else:
725 | return False
726 |
727 | # 连接服务器, 获取当日委托
728 | def _get_today_entrust(self):
729 | query = {
730 | 'classname': 'com.gf.etrade.control.StockUF2Control',
731 | 'method': 'queryDRWT'
732 | }
733 | payload = {
734 | 'query_direction': 0,
735 | 'action_in': 0,
736 | 'request_num': 100,
737 | 'start': 0,
738 | 'limit': 100
739 | }
740 | result = self.__connect_trade_server(query, payload)
741 | if result:
742 | self.__entrust_list = list()
743 | for order in result['data']:
744 | if 'entrust_no' in order:
745 | self.__entrust_list.append(self.__convert_value_in_dict_to_float(order))
746 | logging.info('entrust list is: ' + str(self.__entrust_list))
747 | return True
748 | else:
749 | return False
750 |
751 | # 连接服务器, 获取可撤单
752 | def _get_cancel_list(self):
753 | query = {
754 | 'classname': 'com.gf.etrade.control.StockUF2Control',
755 | 'method': 'queryDRWT'
756 | }
757 | payload = {
758 | 'query_direction': 0,
759 | 'action_in': 1,
760 | 'request_num': 100,
761 | 'start': 0,
762 | 'limit': 100
763 | }
764 | result = self.__connect_trade_server(query, payload)
765 | if result:
766 | self.__cancel_list = list()
767 | for order in result['data']:
768 | if 'entrust_no' in order:
769 | self.__cancel_list.append(self.__convert_value_in_dict_to_float(order))
770 | logging.info('entrust list is: ' + str(self.__cancel_list))
771 | return True
772 | else:
773 | return False
774 |
775 | # 连接服务器, 查询当日成交
776 | def _get_today_trade(self):
777 | query = {
778 | 'classname': 'com.gf.etrade.control.StockUF2Control',
779 | 'method': 'queryDRCJ'
780 | }
781 | payload = {
782 | 'query_direction': 0,
783 | 'position_str': '',
784 | 'query_mode': 0,
785 | 'request_num': 100,
786 | 'start': 0,
787 | 'limit': 100
788 | }
789 | result = self.__connect_trade_server(query, payload)
790 | if result:
791 | self.__trade_list = list()
792 | for order in result['data']:
793 | if 'entrust_no' in order:
794 | self.__trade_list.append(self.__convert_value_in_dict_to_float(order))
795 | logging.info('trade list is: ' + str(self.__trade_list))
796 | return True
797 | else:
798 | return False
799 |
800 | # 连接服务器,获取仓位信息
801 | def _get_position(self):
802 | query = {
803 | 'classname': 'com.gf.etrade.control.StockUF2Control',
804 | 'method': 'queryCC'
805 | }
806 | payload = {
807 | 'request_num': 500,
808 | 'start': 0,
809 | 'limit': 0
810 | }
811 | result = self.__connect_trade_server(query, payload)
812 | if result:
813 | self.__stock_position = list()
814 | for stock in result['data']:
815 | if 'exchange_type' in stock:
816 | self.__stock_position.append(self.__convert_value_in_dict_to_float(stock))
817 | logging.info('stock position is: ' + str(self.__stock_position))
818 | return True
819 | else:
820 | return False
821 |
822 | # 上海lof交易: 申购
823 | def _sh_lof_purchase(self, exchange_type, stock_account, stock_code, price, amount):
824 | query = {
825 | 'classname': 'com.gf.etrade.control.SHLOFFundControl',
826 | 'method': 'assetSecuprtTrade'
827 | }
828 | payload = {
829 | 'exchange_type': exchange_type,
830 | 'stock_account': stock_account,
831 | 'stock_code': stock_code,
832 | 'entrust_price': price,
833 | 'entrust_amount': amount,
834 | 'entrust_prop': 'LFC',
835 | 'entrust_bs': 1,
836 | 'relation_name': '',
837 | 'relation_tel': '',
838 | 'prop_seat_no': '',
839 | 'cbpconfer_id': '',
840 | 'message_notes': ''
841 | }
842 | result = self.__connect_trade_server(query, payload)
843 | if result:
844 | for order in result['data']:
845 | if 'entrust_no' in order:
846 | return int(order['entrust_no'])
847 | else:
848 | return self.TRADE_FAIL
849 |
850 | def sh_lof_purchase(self, code, amount):
851 | return self._sh_lof_purchase(1, self.__market_account_sh, code, 1, amount)
852 |
853 | # 上海lof交易: 赎回
854 | def _sh_lof_redeem(self, exchange_type, stock_account, stock_code, price, amount):
855 | query = {
856 | 'classname': 'com.gf.etrade.control.StockUF2Control',
857 | 'method': 'doDZJYEntrust'
858 | }
859 | payload = {
860 | 'exchange_type': exchange_type,
861 | 'stock_account': stock_account,
862 | 'stock_code': stock_code,
863 | 'entrust_price': price,
864 | 'entrust_amount': amount,
865 | 'entrust_prop': 'LFR',
866 | 'entrust_bs': 2,
867 | 'relation_name': '',
868 | 'relation_tel': '',
869 | 'prop_seat_no': '',
870 | 'cbpconfer_id': '',
871 | 'message_notes': ''
872 | }
873 | result = self.__connect_trade_server(query, payload)
874 | if result:
875 | for order in result['data']:
876 | if 'entrust_no' in order:
877 | return int(order['entrust_no'])
878 | else:
879 | return self.TRADE_FAIL
880 |
881 | def sh_lof_redeem(self, code, amount):
882 | return self._sh_lof_redeem(1, self.__market_account_sh, code, 1, amount)
883 |
884 | # 上海lof交易: 合并
885 | def _sh_lof_merge(self, exchange_type, stock_account, stock_code, price, amount):
886 | query = {
887 | 'classname': 'com.gf.etrade.control.SHLOFFundControl',
888 | 'method': 'assetSecuprtTrade'
889 | }
890 | payload = {
891 | 'exchange_type': exchange_type,
892 | 'stock_account': stock_account,
893 | 'stock_code': stock_code,
894 | 'entrust_price': price,
895 | 'entrust_amount': amount,
896 | 'entrust_prop': 'LFM',
897 | 'entrust_bs': '',
898 | 'relation_name': '',
899 | 'relation_tel': '',
900 | 'prop_seat_no': '',
901 | 'cbpconfer_id': '',
902 | 'message_notes': ''
903 | }
904 | result = self.__connect_trade_server(query, payload)
905 | if result:
906 | for order in result['data']:
907 | if 'entrust_no' in order:
908 | return int(order['entrust_no'])
909 | else:
910 | return self.TRADE_FAIL
911 |
912 | def sh_lof_merge(self, code, amount):
913 | return self._sh_lof_merge(1, self.__market_account_sh, code, 1, amount)
914 |
915 | # 上海lof交易: 分拆
916 | def _sh_lof_split(self, exchange_type, stock_account, stock_code, price, amount):
917 | query = {
918 | 'classname': 'com.gf.etrade.control.StockUF2Control',
919 | 'method': 'doDZJYEntrust'
920 | }
921 | payload = {
922 | 'exchange_type': exchange_type,
923 | 'stock_account': stock_account,
924 | 'stock_code': stock_code,
925 | 'entrust_price': price,
926 | 'entrust_amount': amount,
927 | 'entrust_prop': 'LFP',
928 | 'entrust_bs': '',
929 | 'relation_name': '',
930 | 'relation_tel': '',
931 | 'prop_seat_no': '',
932 | 'cbpconfer_id': '',
933 | 'message_notes': ''
934 | }
935 | result = self.__connect_trade_server(query, payload)
936 | if result:
937 | for order in result['data']:
938 | if 'entrust_no' in order:
939 | return int(order['entrust_no'])
940 | else:
941 | return self.TRADE_FAIL
942 |
943 | def sh_lof_split(self, code, amount):
944 | return self._sh_lof_split(1, self.__market_account_sh, code, 1, amount)
945 |
946 | # 上海lof交易: 查询委托
947 | def _sh_lof_get_entrust_list(self):
948 | query = {
949 | 'classname': 'com.gf.etrade.control.SHLOFFundControl',
950 | 'method': 'qryAssetSecuprtRealtime'
951 | }
952 | payload = {
953 | 'query_type': 1,
954 | 'exchange_type': '',
955 | 'stock_account': '',
956 | 'stock_code': '',
957 | 'serial_no': '',
958 | 'position_str': '',
959 | 'en_entrust_prop': 'LFM,LFP,LFS,LFC,LFR,LFT',
960 | 'request_num': 100,
961 | 'start': 0,
962 | 'limit': 100
963 | }
964 | result = self.__connect_trade_server(query, payload)
965 | if result:
966 | shlof_entrust_list = list()
967 | for order in result['data']:
968 | if 'entrust_no' in order:
969 | shlof_entrust_list.append(self.__convert_value_in_dict_to_float(order))
970 | logging.info('shlof entrust list is: ' + str(shlof_entrust_list))
971 | return shlof_entrust_list
972 | else:
973 | return None
974 |
975 | # @property
976 | def sh_lof_entrust_list(self):
977 | return self._sh_lof_get_entrust_list()
978 |
979 | # 上海lof交易: 查询成交
980 | def _sh_lof_get_trade_list(self):
981 | query = {
982 | 'classname': 'com.gf.etrade.control.SHLOFFundControl',
983 | 'method': 'qryAssetSecuprtTrade'
984 | }
985 | payload = {
986 | 'query_type': 1,
987 | 'query_kind': 0,
988 | 'exchange_type': '',
989 | 'stock_account': '',
990 | 'entrust_no': '',
991 | 'position_str': '',
992 | 'en_entrust_prop': 'LFM,LFP,LFS,LFC,LFR,LFT',
993 | 'request_num': 100,
994 | 'start': 0,
995 | 'limit': 100
996 | }
997 | result = self.__connect_trade_server(query, payload)
998 | if result:
999 | shlof_trade_list = list()
1000 | for order in result['data']:
1001 | if 'entrust_no' in order:
1002 | shlof_trade_list.append(self.__convert_value_in_dict_to_float(order))
1003 | logging.info('shlof trade list is: ' + str(shlof_trade_list))
1004 | return shlof_trade_list
1005 | else:
1006 | return None
1007 |
1008 | # sh_lof_trade_list
1009 | # {
1010 | # 'bond_term': '0',
1011 | # 'prop_stock_account': ' ',
1012 | # 'position_str': '201509100000011302',
1013 | # 'entrust_status_dict': '已成',
1014 | # 'prev_balance': '0',
1015 | # 'expire_year_rate': '0',
1016 | # 'business_amount': 50000.0,
1017 | # 'seat_no': '43003',
1018 | # 'cbp_business_id': '6000001928',
1019 | # 'preterm_year_rate': '0',
1020 | # 'entrust_status': '8',
1021 | # 'remark': ' ',
1022 | # 'entrust_prop_dict': '上证LOF母基金分拆',
1023 | # 'prop_branch_no': '0',
1024 | # 'client_id': '020290049209',
1025 | # 'back_balance': '0',
1026 | # 'init_date': '20150910',
1027 | # 'join_report_no': '0',
1028 | # 'curr_time': '100649',
1029 | # 'stock_code': '502006',
1030 | # 'exchange_type_dict': '上海',
1031 | # 'prop_seat_no': ' ',
1032 | # 'fund_account': 'xxxxx',
1033 | # 'record_no': '548',
1034 | # 'entrust_no': '11302',
1035 | # 'curr_date': '20150910',
1036 | # 'stock_account': 'xxxxxx',
1037 | # 'branch_no': '202',
1038 | # 'cbpcontract_id': ' ',
1039 | # 'operator_no': ' ',
1040 | # 'relation_tel': ' ',
1041 | # 'cbpconfer_id': ' ',
1042 | # 'entrust_bs': '2',
1043 | # 'entrust_amount': 50000.0,
1044 | # 'orig_entrust_date': '0',
1045 | # 'business_time': '100649',
1046 | # 'business_balance': 50000.0,
1047 | # 'orig_business_id': ' ',
1048 | # 'relation_name': ' ',
1049 | # 'exchange_type': '1',
1050 | # 'entrust_bs_dict': '卖出',
1051 | # 'entrust_type_dict': '委托',
1052 | # 'date_back': '0',
1053 | # 'op_entrust_way': '7',
1054 | # 'stock_name': '国企改革',
1055 | # 'entrust_price': '0',
1056 | # 'op_station': 'WEB|IP:143.84.63.231,MAC:00:50:56:c0:00:08,HDD:WD-WX31C32M1910,ORIGIN:WEB',
1057 | # 'entrust_prop': 'LFP',
1058 | # 'entrust_balance': '0',
1059 | # 'entrust_type': '0'}
1060 | # @property
1061 | def sh_lof_trade_list(self):
1062 | return self._sh_lof_get_trade_list()
1063 |
1064 | # 上海lof交易: 撤单
1065 | def sh_lof_cancel(self, entrust_nos):
1066 | query = {
1067 | 'classname': 'com.gf.etrade.control.SHLOFFundControl',
1068 | 'method': 'secuEntrustWithdraw'
1069 | }
1070 | payload = {
1071 | 'entrust_nos': entrust_nos,
1072 | 'exchange_types': 1,
1073 | 'batch_flag': 0
1074 | }
1075 | result = self.__connect_trade_server(query, payload)
1076 | if result:
1077 | return True
1078 | else:
1079 | return False
1080 |
1081 | def fund_apply(self, code, amount, market='sh'):
1082 | """
1083 | 申购场内基金
1084 | :param code: 代码
1085 | :type code: str
1086 | :param amount: 数量
1087 | :type amount: int
1088 | :param market: 市场, 'sh'为上海; 'sz'为深圳
1089 | :type market: str
1090 | :return: 委托号, -1表示失败
1091 | :rtype: int
1092 | """
1093 | if market.lower() == 'sh':
1094 | return self._fund_apply_redeem(exchange_type=1, stock_code=code, amount=amount, apply_or_redeem=1)
1095 | elif market.lower() == 'sz':
1096 | return self._fund_apply_redeem(exchange_type=2, stock_code=code, amount=amount, apply_or_redeem=1)
1097 | return self.TRADE_FAIL
1098 |
1099 | def fund_redeem(self, code, amount, market='sh'):
1100 | """
1101 | 赎回场外基金
1102 | :param code: 代码
1103 | :type code: str
1104 | :param amount: 数量
1105 | :type amount: int
1106 | :param market: 市场, 'sh'为上海; 'sz'为深圳
1107 | :type market: str
1108 | :return: 委托号, -1表示失败
1109 | :rtype: int
1110 | """
1111 | if market.lower() == 'sh':
1112 | return self._fund_apply_redeem(exchange_type=1, stock_code=code, amount=amount, apply_or_redeem=2)
1113 | elif market.lower() == 'sz':
1114 | return self._fund_apply_redeem(exchange_type=2, stock_code=code, amount=amount, apply_or_redeem=2)
1115 | return self.TRADE_FAIL
1116 |
1117 | def _fund_apply_redeem(self, exchange_type, stock_code, amount, apply_or_redeem):
1118 | """
1119 | 场内基金申购和赎回函数, 不要直接调用
1120 | :param exchange_type: 交易市场, 1为上海; 2为深圳
1121 | :type exchange_type: int
1122 | :param stock_code: 代码
1123 | :type stock_code: str
1124 | :param amount: 数量
1125 | :type amount: int
1126 | :param apply_or_redeem: 交易类型, 1为申购; 2为赎回
1127 | :type apply_or_redeem: int
1128 | :return: 委托号, -1标识失败
1129 | :rtype: int
1130 | """
1131 | query = {
1132 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1133 | 'method': 'CNJJSS',
1134 | }
1135 | payload = {
1136 | 'stock_code': stock_code,
1137 | 'exchange_type': exchange_type,
1138 | 'entrust_amount': amount,
1139 | 'entrust_bs': apply_or_redeem # 1为申购, 2为赎回
1140 | }
1141 | result = self.__connect_trade_server(query, payload)
1142 | if result:
1143 | for order in result['data']:
1144 | if 'entrust_no' in order:
1145 | return int(order['entrust_no'])
1146 | return self.TRADE_FAIL
1147 |
1148 | # 基金分拆
1149 | def fund_split(self, stock_code, amount):
1150 | """
1151 | 场内基金分拆函数(不包括上海lof)
1152 | :param stock_code: 代码
1153 | :type stock_code: str
1154 | :param amount: 数量
1155 | :type amount: int
1156 | :return: 委托号
1157 | :rtype: int, -1表示失败
1158 | """
1159 | query = {}
1160 | payload = {
1161 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1162 | 'method': 'doSplit',
1163 | 'stock_code': stock_code,
1164 | 'split_amount': amount
1165 | }
1166 | result = self.__connect_trade_server(query, payload)
1167 | if result:
1168 | for order in result['data']:
1169 | if 'entrust_no' in order:
1170 | return int(order['entrust_no'])
1171 | else:
1172 | return self.TRADE_FAIL
1173 |
1174 | # 基金合并
1175 | def fund_merge(self, stock_code, amount):
1176 | """
1177 | 场内基金合并函数(不包括上海lof)
1178 | :param stock_code: 代码
1179 | :type stock_code: str
1180 | :param amount: 数量
1181 | :type amount: int
1182 | :return: 委托号
1183 | :rtype: int, -1表示失败
1184 | """
1185 | query = {}
1186 | payload = {
1187 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1188 | 'method': 'doMerge',
1189 | 'stock_code': stock_code,
1190 | 'merge_amount': amount
1191 | }
1192 | result = self.__connect_trade_server(query, payload)
1193 | if result:
1194 | for order in result['data']:
1195 | if 'entrust_no' in order:
1196 | return int(order['entrust_no'])
1197 | else:
1198 | return self.TRADE_FAIL
1199 |
1200 | # 基金查询可撤单
1201 | def fund_cancel_list(self):
1202 | query = {
1203 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1204 | 'method': 'queryFJJJCD'
1205 | }
1206 | payload = {
1207 | 'cancelable': 0,
1208 | 'position_str': 0,
1209 | 'request_num': 100,
1210 | 'start': 0,
1211 | 'limit': 100
1212 | }
1213 | result = self.__connect_trade_server(query, payload)
1214 | if result:
1215 | fund_cancel_list = list()
1216 | for order in result['data']:
1217 | if 'entrust_no' in order:
1218 | fund_cancel_list.append(self.__convert_value_in_dict_to_float(order))
1219 | logging.info('shlof trade list is: ' + str(fund_cancel_list))
1220 | return fund_cancel_list
1221 | else:
1222 | return None
1223 |
1224 | # 基金查询成交
1225 | def fund_trade_list(self):
1226 | query = {
1227 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1228 | 'method': 'queryFJJJCJ'
1229 | }
1230 | payload = {
1231 | 'business_flag': '',
1232 | 'stock_code': '',
1233 | 'request_num': 100,
1234 | 'start': 0,
1235 | 'limit': 100
1236 | }
1237 | result = self.__connect_trade_server(query, payload)
1238 | if result:
1239 | fund_trade_list = list()
1240 | for order in result['data']:
1241 | if 'entrust_no' in order:
1242 | fund_trade_list.append(self.__convert_value_in_dict_to_float(order))
1243 | logging.info('shlof trade list is: ' + str(fund_trade_list))
1244 | return fund_trade_list
1245 | else:
1246 | return None
1247 |
1248 | # 基金查询委托
1249 | def _fund_entrust_list(self, start_date, end_date):
1250 | query = {
1251 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1252 | 'method': 'queryFJJJWT'
1253 | }
1254 | payload = {
1255 | 'ksrq': start_date,
1256 | 'jsrq': end_date,
1257 | 'cancelable': 0,
1258 | 'request_num': 100,
1259 | 'start': 0,
1260 | 'limit': 100
1261 | }
1262 | result = self.__connect_trade_server(query, payload)
1263 | if result:
1264 | fund_trade_list = list()
1265 | for order in result['data']:
1266 | if 'entrust_no' in order:
1267 | fund_trade_list.append(self.__convert_value_in_dict_to_float(order))
1268 | logging.info('shlof trade list is: ' + str(fund_trade_list))
1269 | return fund_trade_list
1270 | else:
1271 | return None
1272 |
1273 | # 基金查询当日委托
1274 | def fund_entrust_list(self):
1275 | date = strftime('%Y%m%d')
1276 | return self._fund_entrust_list(date, date)
1277 |
1278 | # 基金查询历史委托
1279 | def fund_hist_entrust_list(self, start_date, end_date):
1280 | return self._fund_entrust_list(start_date, end_date)
1281 |
1282 | # 货币基金相关操作
1283 | # 货币基金申购
1284 | def money_fund_apply(self, code, amount, market='sh'):
1285 | """
1286 | 申购货币基金
1287 | :param code: 代码
1288 | :type code: str
1289 | :param amount: 数量
1290 | :type amount: int
1291 | :param market: 市场, 'sh'为上海; 'sz'为深圳
1292 | :type market: str
1293 | :return: 委托号, -1表示失败
1294 | :rtype: int
1295 | """
1296 | if market.lower() == 'sh':
1297 | return self._money_fund_apply_redeem(exchange_type=1, stock_code=code, amount=amount, apply_or_redeem=1)
1298 | elif market.lower() == 'sz':
1299 | return self._money_fund_apply_redeem(exchange_type=2, stock_code=code, amount=amount, apply_or_redeem=1)
1300 | return self.TRADE_FAIL
1301 |
1302 | # 货币基金赎回
1303 | def money_fund_redeem(self, code, amount, market='sh'):
1304 | """
1305 | 赎回场外基金
1306 | :param code: 代码
1307 | :type code: str
1308 | :param amount: 数量
1309 | :type amount: int
1310 | :param market: 市场, 'sh'为上海; 'sz'为深圳
1311 | :type market: str
1312 | :return: 委托号, -1表示失败
1313 | :rtype: int
1314 | """
1315 | if market.lower() == 'sh':
1316 | return self._money_fund_apply_redeem(exchange_type=1, stock_code=code, amount=amount, apply_or_redeem=2)
1317 | elif market.lower() == 'sz':
1318 | return self._money_fund_apply_redeem(exchange_type=2, stock_code=code, amount=amount, apply_or_redeem=2)
1319 | return self.TRADE_FAIL
1320 |
1321 | # 货币基金申赎
1322 | def _money_fund_apply_redeem(self, exchange_type, stock_code, amount, apply_or_redeem):
1323 | """
1324 | 货币基金申购和赎回函数, 不要直接调用
1325 | :param exchange_type: 交易市场, 1为上海; 2为深圳
1326 | :type exchange_type: int
1327 | :param stock_code: 代码
1328 | :type stock_code: str
1329 | :param amount: 数量
1330 | :type amount: int
1331 | :param apply_or_redeem: 交易类型, 1为申购; 2为赎回
1332 | :type apply_or_redeem: int
1333 | :return: 委托号, -1标识失败
1334 | :rtype: int
1335 | """
1336 | query = {
1337 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1338 | 'method': 'HBJJSS',
1339 | }
1340 | payload = {
1341 | 'stock_code': stock_code,
1342 | 'exchange_type': exchange_type,
1343 | 'entrust_amount': amount,
1344 | 'entrust_bs': apply_or_redeem # 1为申购, 2为赎回
1345 | }
1346 | result = self.__connect_trade_server(query, payload)
1347 | if result:
1348 | for order in result['data']:
1349 | if 'entrust_no' in order:
1350 | return int(order['entrust_no'])
1351 | return self.TRADE_FAIL
1352 |
1353 | # 货币基金查委托
1354 | def _money_fund_entrust_list(self):
1355 | query = {
1356 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1357 | 'method': 'HBJJWTCX'
1358 | }
1359 | payload = {
1360 | 'request_num': 100,
1361 | 'position_str': '',
1362 | 'start': 0,
1363 | 'limit': 100
1364 | }
1365 | result = self.__connect_trade_server(query, payload)
1366 | if result:
1367 | fund_trade_list = list()
1368 | for order in result['data']:
1369 | if 'entrust_no' in order:
1370 | fund_trade_list.append(self.__convert_value_in_dict_to_float(order))
1371 | logging.info('shlof trade list is: ' + str(fund_trade_list))
1372 | return fund_trade_list
1373 | else:
1374 | return None
1375 |
1376 | # 货币基金查询当日委托
1377 | def money_fund_entrust_list(self):
1378 | return self.money_fund_apply()
1379 |
1380 | # 货币基金撤单
1381 | def money_fund_cancel(self, code, entrust_nos, market='sh'):
1382 | if market.lower() == 'sh':
1383 | return self.money_fund_cancel(entrust_nos, 1, code)
1384 | elif market.lower() == 'sz':
1385 | return self.money_fund_cancel(entrust_nos, 2, code)
1386 | return self.TRADE_FAIL
1387 |
1388 | def _money_fund_cancel(self, entrust_nos, exchange_types, stock_codes):
1389 | query = {
1390 | 'classname': 'com.gf.etrade.control.StockUF2Control',
1391 | 'method': 'HBJJCD'
1392 | }
1393 | payload = {
1394 | 'entrust_nos': entrust_nos,
1395 | 'exchange_types': exchange_types,
1396 | 'stock_codes': stock_codes,
1397 | 'batch_flag': 0
1398 | }
1399 | result = self.__connect_trade_server(query, payload)
1400 | if result:
1401 | return True
1402 | else:
1403 | return False
1404 |
1405 | # 买入函数,对_buy函数的封装,只需要指定市场,而不需要指定股票账号
1406 | # 市场可选为'sh' 和 'sz' ,默认 'sh'
1407 | def buy(self, code, amount, price, market='sh'):
1408 | if market.lower() == 'sh' and self.__market_account_sh:
1409 | return self._buy(account=self.__market_account_sh, exchange_type=1, code=code, amount=amount, price=price)
1410 | if market.lower() == 'sz' and self.__market_account_sz:
1411 | return self._buy(account=self.__market_account_sz, exchange_type=2, code=code, amount=amount, price=price)
1412 | return self.TRADE_FAIL
1413 |
1414 | # 卖出函数,对_sell函数的封装,只需要指定市场,而不需要指定股票账号
1415 | # 市场可选为'sh' 和 'sz' ,默认 'sh'
1416 | def sell(self, code, amount, price, market='sh'):
1417 | if market.lower() == 'sh' and self.__market_account_sh:
1418 | return self._sell(account=self.__market_account_sh, exchange_type=1, code=code, amount=amount, price=price)
1419 | if market.lower() == 'sz' and self.__market_account_sz:
1420 | return self._sell(account=self.__market_account_sz, exchange_type=2, code=code, amount=amount, price=price)
1421 | return self.TRADE_FAIL
1422 |
1423 | def cancel_all(self):
1424 | return self._cancel_all()
1425 |
1426 | def cancel_buy(self):
1427 | return self._cancel_all(direction='buy')
1428 |
1429 | def cancel_sell(self):
1430 | return self._cancel_all(direction='sell')
1431 |
1432 | if __name__ == '__main__':
1433 | # logging.info("This is a test......\n")
1434 | config = dict()
1435 | with open('../Configs/Socket.config.gf', 'r') as f:
1436 | config = json.loads(f.read().replace(r'\n', ''))
1437 | gf = GFSocket(config['account'], config['password'])
1438 | gf.prepare_login()
1439 | gf.show_verify_code()
1440 | vericode = input("input verify code:")
1441 | gf.enter_verify_code(vericode)
1442 | gf.login()
1443 | gf.prepare_trade()
1444 | print(str(gf.stock_position))
1445 | print(str(gf.entrust_list))
1446 | print(str(gf.trade_list))
1447 | print(str(gf.cancel_list))
1448 | print(str(gf.sh_lof_trade_list))
1449 |
1450 | gf.logout()
1451 |
--------------------------------------------------------------------------------
/Socket/Socket.py:
--------------------------------------------------------------------------------
1 | #
2 | # Created by 'changye' on '15-11-9'
3 | #
4 |
5 | __author__ = 'changye'
6 |
7 |
8 | class Socket(object):
9 |
10 | TRADE_FAIL = -1
11 |
--------------------------------------------------------------------------------
/Socket/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/changye/AutoTrade/c3e77e22217b9fe39f2d20cf6d2aa7dabea1b339/Socket/__init__.py
--------------------------------------------------------------------------------
/Tools/Notifier.py:
--------------------------------------------------------------------------------
1 | #
2 | # Created by 'changye' on '15-9-1'
3 | # 邮件通知系统
4 | #
5 |
6 | __author__ = 'changye'
7 | import smtplib
8 | import poplib
9 | from email.mime.text import MIMEText
10 | from email.mime.multipart import MIMEMultipart
11 | from email.encoders import encode_base64
12 | from email.mime.base import MIMEBase
13 | from email.header import Header, decode_header
14 | from email.utils import parseaddr, formataddr
15 | from email.parser import Parser
16 | import logging
17 | logging.basicConfig(level=logging.WARNING)
18 |
19 |
20 | def _format_addr(s):
21 | name, addr = parseaddr(s)
22 | return formataddr((Header(name, 'utf-8').encode(), addr))
23 |
24 |
25 | def guess_charset(msg):
26 | charset = msg.get_charset()
27 | if charset is None:
28 | content_type = msg.get('Content-Type', '').lower()
29 | pos = content_type.find('charset=')
30 | if pos >= 0:
31 | charset = content_type[pos + 8:].strip()
32 | return charset
33 |
34 |
35 | class Notifier(object):
36 | def __init__(self, from_email, password, to_mail=None, smtp_server=None, pop_server=None):
37 | self.__from = from_email
38 | self.__password = password
39 | self.__smtp_server = smtp_server
40 | self.__pop_server = pop_server
41 |
42 | if to_mail is not None and type(to_mail) is not list:
43 | self.__to = [to_mail]
44 | else:
45 | self.__to = to_mail
46 |
47 | def send(self, subject, content, to=None, image=None):
48 | server = smtplib.SMTP(self.__smtp_server, 25)
49 | server.set_debuglevel(1)
50 | to_list = list()
51 | if to is not None:
52 | if type(to) is list:
53 | to_list = to
54 | else:
55 | to_list.append(to)
56 | elif self.__to is not None:
57 | to_list = self.__to
58 | else:
59 | return False
60 |
61 | server.login(self.__from, self.__password)
62 | msg = MIMEMultipart()
63 | msg['From'] = _format_addr('<%s>' % self.__from)
64 | msg['To'] = _format_addr('<%s>' % ','.join(to_list))
65 | msg['Subject'] = Header(subject, 'utf-8').encode()
66 | # msg.attach(MIMEText(content, 'plain', 'utf-8'))
67 | msg.attach(MIMEText(content, 'html', 'utf-8'))
68 |
69 | if image is not None:
70 | with open(image, 'rb') as f:
71 | mime = MIMEBase('image', 'png', filename='image.png')
72 | # 把附件的内容读进来:
73 | mime.set_payload(f.read())
74 | # 用Base64编码:
75 | encode_base64(mime)
76 | # 加上必要的头信息:
77 | mime.add_header('Content-Disposition', 'attachment', filename='image.png')
78 | mime.add_header('Content-ID', '<0>')
79 | mime.add_header('X-Attachment-Id', '0')
80 | # 添加到MIMEMultipart:
81 | msg.attach(mime)
82 | server.sendmail(self.__from, to_list, msg.as_string())
83 | server.quit()
84 | return True
85 |
86 | def receive(self, subject):
87 | server = poplib.POP3(self.__pop_server)
88 | server.set_debuglevel(1)
89 | server.user(self.__from)
90 | server.pass_(self.__password)
91 | resp, mails, octets = server.list()
92 |
93 | import re
94 | mail_number = len(mails)
95 | for i in range(mail_number, 0, -1):
96 | resp, lines, octets = server.retr(i)
97 |
98 | # 可以获得整个邮件的原始文本:
99 | msg_content = b'\r\n'.join(lines).decode('utf-8')
100 | # 稍后解析出邮件:
101 | msg = Parser().parsestr(msg_content)
102 | msg_subject, charset = decode_header(msg.get('Subject', ''))[0]
103 | if type(msg_subject) is not str:
104 | msg_subject = msg_subject.decode(charset)
105 | # logging.warning(msg_subject)
106 | if msg_subject and msg_subject.find(subject) >= 0:
107 | content_type = msg.get_content_type()
108 | if content_type == 'text/plain' or content_type == 'text/html':
109 | content = msg.get_payload(decode=True)
110 | charset = guess_charset(msg)
111 | content = content.decode(charset)
112 | return content
113 | if content_type == 'multipart/alternative':
114 | parts = msg.get_payload()
115 | for p in parts:
116 | if p.get_content_type() == 'text/plain' or p.get_content_type() == 'text/html':
117 | content = p.get_payload(decode=True)
118 | charset = guess_charset(p)
119 | content = content.decode(charset)
120 | return content
121 | return None
122 | return None
123 |
124 |
--------------------------------------------------------------------------------
/Tools/Ocr.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding:utf-8
3 |
4 | import requests
5 | from hashlib import md5
6 |
7 |
8 | class RClient(object):
9 |
10 | def __init__(self, username, password, soft_id, soft_key):
11 | self.username = username
12 | self.password = md5(password.encode('utf8')).hexdigest()
13 | self.soft_id = soft_id
14 | self.soft_key = soft_key
15 | self.base_params = {
16 | 'username': self.username,
17 | 'password': self.password,
18 | 'softid': self.soft_id,
19 | 'softkey': self.soft_key,
20 | }
21 | self.headers = {
22 | 'Connection': 'Keep-Alive',
23 | 'Expect': '100-continue',
24 | 'User-Agent': 'ben',
25 | }
26 |
27 | def rk_create(self, im, im_type, timeout=60):
28 | """
29 | im: 图片字节
30 | im_type: 题目类型
31 | """
32 | params = {
33 | 'typeid': im_type,
34 | 'timeout': timeout,
35 | }
36 | params.update(self.base_params)
37 | files = {'image': ('a.jpg', im)}
38 | r = requests.post('http://api.ruokuai.com/create.json', data=params, files=files, headers=self.headers)
39 | return r.json()
40 |
41 | def rk_report_error(self, im_id):
42 | """
43 | im_id:报错题目的ID
44 | """
45 | params = {
46 | 'id': im_id,
47 | }
48 | params.update(self.base_params)
49 | r = requests.post('http://api.ruokuai.com/reporterror.json', data=params, headers=self.headers)
50 | return r.json()
51 |
52 |
53 | # 使用www.ruokuai.com提供的服务
54 | class Ocr(RClient):
55 |
56 | def __init__(self, username, password):
57 | super().__init__(username, password, soft_id='46690', soft_key='8b2cb811991046d59c742040a7c18d7f')
58 | self.__last_ocr = None
59 |
60 | def recognize(self, image_file, vericode_length):
61 | mode = 5000 # 任意长度中英文数字
62 | if type(vericode_length) is int and vericode_length > 0:
63 | mode = 3000 + vericode_length * 10 # 特定长度英文+数字
64 | import os
65 | if os.path.isfile(image_file):
66 | im = open(image_file, 'rb').read()
67 | self.__last_ocr = self.rk_create(im, mode)
68 |
69 | if 'Result' not in self.__last_ocr:
70 | if 'Error' in self.__last_ocr:
71 | print(self.__last_ocr['Error'])
72 | return None
73 |
74 | if 0 < vericode_length != len(self.__last_ocr['Result']):
75 | self.rk_report_error(self.__last_ocr['Id'])
76 | return None
77 | else:
78 | return self.__last_ocr['Result']
79 |
80 | def report_error(self):
81 | self.rk_report_error(self.__last_ocr['Id'])
82 |
83 |
84 | if __name__ == '__main__':
85 | ocr = Ocr('changye', '19820928')
86 | result = ocr.recognize('../vericode.jpeg', 5)
87 | print(result)
88 |
89 |
--------------------------------------------------------------------------------
/Tools/SinaApi.py:
--------------------------------------------------------------------------------
1 | __author__ = 'changye'
2 |
3 | from urllib import request
4 | import re
5 | import logging
6 |
7 |
8 | def convert_str_to_number(string):
9 | if type(string) is not str:
10 | return string
11 | m = re.match('^[\d]+[\.][\d]*$', string)
12 | if m:
13 | return float(string)
14 | m = re.match('^[\d]+$', string)
15 | if m:
16 | return int(string)
17 | else:
18 | return string
19 |
20 |
21 | def str_to_number(strings):
22 | if type(strings) is list:
23 | return [convert_str_to_number(x) for x in strings]
24 | else:
25 | return convert_str_to_number(strings)
26 |
27 |
28 | def format_quote(str):
29 | string = str.strip()
30 |
31 | if string == '':
32 | return None
33 |
34 | m = re.match(r'var hq_str_(\S+)="(.*)"', string)
35 | if m and len(m.groups()) > 1:
36 |
37 | stockInfo = dict()
38 | stockInfo['id'] = m.group(1)
39 | logging.info(stockInfo['id'])
40 | stockInfoArray = re.split(r'[\,]+', m.group(2))
41 | if len(stockInfoArray) < 1:
42 | return None
43 | logging.info(stockInfoArray)
44 | keys = ['name', 'open_today', 'close_yesterday', 'quote',
45 | 'highest', 'lowest', 'buy', 'sell', 'deal', 'amount']
46 | for i, value in enumerate(keys):
47 | stockInfo[value] = str_to_number(stockInfoArray[i])
48 |
49 | stockInfo['buy_quote'] = str_to_number(stockInfoArray[11:21:2])
50 | stockInfo['buy_quantity'] = str_to_number(stockInfoArray[10:20:2])
51 | stockInfo['sell_quote'] = str_to_number(stockInfoArray[21:31:2])
52 | stockInfo['sell_quantity'] = str_to_number(stockInfoArray[20:30:2])
53 | stockInfo['date'] = str_to_number(stockInfoArray[30])
54 | stockInfo['time'] = str_to_number(stockInfoArray[31])
55 |
56 | logging.info(stockInfo)
57 |
58 | return stockInfo
59 | else:
60 | return None
61 |
62 |
63 | def get_sina_quote(ids):
64 |
65 | if ids == '' or ids is None or len(ids) == 0:
66 | return None
67 |
68 | quoteId = ','.join(ids)
69 | # url = r'http://hq.sinajs.cn/list=' + quoteId
70 | url = r'http://112.90.6.246/list=' + quoteId
71 | logging.info("connecting:\t" + url)
72 | with request.urlopen(url) as f:
73 | data = f.read()
74 |
75 | quoteMsg = data.decode('gb2312').replace('\n', '')
76 | quotes = [x for x in quoteMsg.split(';') if x != '']
77 | logging.info(quotes)
78 | result = dict()
79 | for x in quotes:
80 | formated_quote = format_quote(x)
81 | result[formated_quote['id']] = formated_quote
82 | # return [format_quote(x) for x in quotes]
83 | return result
84 |
85 | def get_average_price_of_certain_amount_buy(hq, amount):
86 | total = 0
87 | index = 0
88 | total_price = 0
89 | totol_can_buy = hq['sell_quantity'][0] + hq['sell_quantity'][1] + hq['sell_quantity'][2] + hq['sell_quantity'][3]
90 | while total <= amount and index < 4:
91 | volume = hq['sell_quantity'][index]
92 | price = hq['sell_quote'][index]
93 | max_canbuy = total + volume
94 | if max_canbuy > amount:
95 | total_price = total_price + (amount - total) * price
96 | total = amount
97 | else:
98 | total_price = total_price + volume * price
99 | total = total + volume
100 | index += 1
101 | if total == 0:
102 | return (0 , 0, 0, 0)
103 | else:
104 | return (total_price / total, total, totol_can_buy, hq['sell_quote'][3])
105 |
106 |
107 | def get_average_price_of_certain_amount_sell(hq, amount):
108 | total = 0
109 | index = 0
110 | total_price = 0
111 | totol_can_sell = hq['buy_quantity'][0] + hq['buy_quantity'][1] + hq['buy_quantity'][2] + hq['buy_quantity'][3]
112 | while total <= amount and index < 4:
113 | volume = hq['buy_quantity'][index]
114 | price = hq['buy_quote'][index]
115 | max_cansell = total + volume
116 | if max_cansell > amount:
117 | total_price = total_price + (amount - total) * price
118 | total = amount
119 | else:
120 | total_price = total_price + volume * price
121 | total = total + volume
122 | index += 1
123 | if total == 0:
124 | return (0 , 0, 0, 0)
125 | else:
126 | return (total_price / total, total, totol_can_sell, hq['buy_quote'][3])
127 |
128 |
129 | if __name__ == '__main__':
130 | print(get_quote(['sz150209', 'sz150221']))
131 | # print(str_to_number(['hello', '1.23', '3432']))
--------------------------------------------------------------------------------
/Tools/广发证券秘钥生成.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |