├── .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 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/start.jpg) 54 | 55 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/run.jpg) 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 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/控件准备.png)

70 | 2. 使用IE 打开Tools目录下的本地文件: "广发证券秘钥生成.html"
71 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/秘钥生成1.png)

72 | 3. 允许本地运行activex, 使控件可以在页面上正常显示
73 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/秘钥生成2.png)

74 | 4. 填写广发证券账号和密码, 点击生成秘钥. 注意, IE可能需要你允许ActiveX交互, 点击是,允许交互
75 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/秘钥生成3.png)

76 | 5. 生成秘钥后存储成文件, 建议不要使用windows自带的notepad, 使用sublime或者其他专业编辑文件, 并保存为utf8格式
77 | ![image](https://github.com/changye/AutoTrade/raw/master/Documents/image/秘钥生成4.png)

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 | 4 | 5 | 广发证券秘钥生成器 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 广发证券秘钥生成器 46 |
47 | 48 | 49 |
50 |
51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 |
75 | 76 |
77 | 78 |
79 |
80 |
81 | 82 |
83 |
84 |
85 |

秘钥未生成

86 |
87 |
88 | 91 |
92 |
93 | 94 |
95 | 96 | 97 |
98 |
99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /Trade/Job.py: -------------------------------------------------------------------------------- 1 | # 2 | # Created by 'changye' on '15-11-2' 3 | # 4 | 5 | __author__ = 'changye' 6 | 7 | 8 | class Dependence(object): 9 | # 已死亡, 若B依赖于A, A死亡则B死亡 10 | DEAD = -1 11 | # 依赖关系尚未满足, 等待 12 | WAIT = 0 13 | # 依赖关系已经实现, 可以发射 14 | READY = 1 15 | 16 | def __init__(self, depend_job, status): 17 | self.__depend_job_module_no = depend_job.module_no 18 | self.__depend_job_serial_no = depend_job.serial_no 19 | self.__depend_job_status = status 20 | 21 | @property 22 | def depend_job_module_no(self): 23 | return self.__depend_job_module_no 24 | 25 | @property 26 | def depend_job_serial_no(self): 27 | return self.__depend_job_serial_no 28 | 29 | @property 30 | def depend_job_status(self): 31 | return self.__depend_job_status 32 | 33 | @property 34 | def depend_job(self): 35 | return self.__depend_job_module_no, self.depend_job_serial_no, self.depend_job_status 36 | 37 | 38 | class Job(object): 39 | 40 | NONE = 0 41 | BUY = 1 42 | SELL = 2 43 | CANCEL = 3 44 | 45 | FUND_APPLY = 11 46 | FUND_REDEEM = 12 47 | FUND_SPLIT = 13 48 | FUND_MERGE = 14 49 | 50 | # 每一个job的状态 51 | # 尝试次数已经超过限额 52 | DEAD = -4 53 | # 失败 54 | FAILED = -3 55 | # 已撤单 56 | CANCELED = -2 57 | # 已撤单, 但部分已经成交 58 | CANCELED_PARTLY = -1 59 | # 挂起, 表示等待执行中 60 | PENDING = 0 61 | # 已委托 62 | ENTRUSTED = 1 63 | # 收市后成交 64 | TRADE_AFTER_MARKET_CLOSE = 2 65 | # 部分成交 66 | TRADED_PARTLY = 3 67 | # 全部成交 68 | TRADED_ALL = 4 69 | 70 | def __init__(self, module_no, serial_no, time_stamp): 71 | self.__module_no = module_no 72 | self.__serial_no = serial_no 73 | # 委托号 74 | self.__entrust_no = -1 75 | # 状态位 76 | self.__status = self.PENDING 77 | # 最大重试次数 78 | self.__allow_retry_times = 3 79 | # 已经尝试的次数 80 | self.__tried_times = 0 81 | # 委托的依赖条件 82 | self.__dependence = list() 83 | # 委托的操作 84 | self.__action = self.NONE 85 | # 委托代码 86 | self.__code = None 87 | # 委托的市场 88 | self.__market = None 89 | # 委托数量 90 | self.__amount = 0 91 | # 委托价格 92 | self.__price = 0 93 | # 需要撤单的序列号, 撤单时使用 94 | self.__cancel_serial_no = -1 95 | # 时间戳 96 | self.__time_stamp = time_stamp 97 | # 附加信息 98 | self.__msg = '' 99 | # 模拟发射 100 | self.__simulate = False 101 | 102 | def set(self, action, code, market, amount, price, depend=None, msg='', cancel_job=None): 103 | # 如果是撤单的话,需要entrust_no大于0 104 | if action == Job.CANCEL: 105 | if cancel_job is not None and isinstance(cancel_job, Job): 106 | self.__action = Job.CANCEL 107 | self.__cancel_serial_no = cancel_job.serial_no 108 | self.add_dependence(depend) 109 | else: 110 | self.__action = action 111 | self.__code = code 112 | self.__market = market 113 | self.__amount = amount 114 | self.__price = price 115 | self.__msg = msg 116 | self.add_dependence(depend) 117 | return self 118 | 119 | def set_cancel(self, job): 120 | self.__action = self.CANCEL 121 | self.__cancel_serial_no = job.serial_no 122 | return self 123 | 124 | def set_message(self, msg): 125 | self.__msg = msg 126 | return self 127 | 128 | def set_none(self): 129 | self.__action = self.NONE 130 | self.__status = self.CANCELED 131 | return self 132 | 133 | def set_simulate(self, simulate): 134 | self.__simulate = simulate 135 | return self 136 | 137 | def get_cancel_serial_no(self): 138 | return self.__cancel_serial_no 139 | 140 | def add_dependence(self, depend): 141 | if depend is not None and isinstance(depend, Dependence): 142 | self.__dependence.append(depend) 143 | 144 | @property 145 | def action(self): 146 | return self.__action 147 | 148 | @property 149 | def action_detail(self): 150 | return self.__market, self.__code, self.__amount, self.__price 151 | 152 | @property 153 | def dependence(self): 154 | return self.__dependence 155 | 156 | @property 157 | def status(self): 158 | return self.__status 159 | 160 | @status.setter 161 | def status(self, value): 162 | if Job.DEAD <= value <= Job.TRADED_ALL: 163 | self.__status = value 164 | 165 | @property 166 | def entrust_no(self): 167 | return self.__entrust_no 168 | 169 | @entrust_no.setter 170 | def entrust_no(self, value): 171 | if type(value) is int: 172 | self.__entrust_no = value 173 | 174 | @property 175 | def is_simulate(self): 176 | return self.__simulate 177 | 178 | @property 179 | def module_no(self): 180 | return self.__module_no 181 | 182 | @property 183 | def serial_no(self): 184 | return self.__serial_no 185 | 186 | @property 187 | def tried_times(self): 188 | return self.__tried_times 189 | 190 | def tried_once(self): 191 | self.__tried_times += 1 192 | 193 | @property 194 | def allow_retry_times(self): 195 | return self.__allow_retry_times 196 | 197 | def set_allow_retry_times(self, value): 198 | if type(value) is int and value > 0: 199 | self.__allow_retry_times = value 200 | 201 | def mark_dead(self): 202 | self.__status = Job.DEAD 203 | 204 | def mark_entrust(self, entrust_no): 205 | self.entrust_no = entrust_no 206 | self.status = Job.ENTRUSTED 207 | 208 | def mark_fail(self): 209 | self.__status = Job.FAILED 210 | 211 | def mark_trade_after_market_close(self): 212 | self.__status = Job.TRADE_AFTER_MARKET_CLOSE 213 | 214 | def mark_traded_all(self): 215 | self.__status = Job.TRADED_ALL 216 | 217 | def mark_traded_partly(self): 218 | self.__status = Job.TRADED_PARTLY 219 | 220 | @property 221 | def already_entrusted(self): 222 | return self.status > Job.PENDING 223 | 224 | @property 225 | def exceed_allow_retry_times(self): 226 | return self.tried_times >= self.allow_retry_times 227 | 228 | @property 229 | def is_none(self): 230 | return self.action == Job.NONE 231 | 232 | @property 233 | def is_dead(self): 234 | return self.status == Job.DEAD 235 | 236 | @property 237 | def is_pending(self): 238 | return self.status == Job.PENDING 239 | 240 | @property 241 | def is_failed(self): 242 | return self.status == Job.FAILED 243 | 244 | @property 245 | def is_canceled(self): 246 | return self.status == Job.CANCELED or self.status == Job.CANCELED_PARTLY 247 | 248 | @property 249 | def need_to_issue(self): 250 | return self.status == Job.PENDING or self.status == Job.FAILED 251 | 252 | @property 253 | def need_to_review(self): 254 | return self.status == Job.ENTRUSTED or self.status == Job.TRADED_PARTLY 255 | 256 | @property 257 | def info(self): 258 | result = '' 259 | if self.__action == self.NONE: 260 | result = '[%d:%d] %s(%s)\nMSG:%s' % \ 261 | (self.__module_no, self.__serial_no, 262 | 'Nothing need to be done!', self.__code, self.__msg) 263 | if self.__action == self.BUY: 264 | result = '[%d:%d] %s %d of \'%s\' @ price $%.3f\nMSG:%s' \ 265 | % (self.__module_no, self.__serial_no, 266 | 'Buy', self.__amount, self.__code, self.__price, self.__msg) 267 | elif self.__action == self.SELL: 268 | result = '[%d:%d] %s %d of \'%s\' @ price $%.3f\nMSG:%s' \ 269 | % (self.__module_no, self.__serial_no, 270 | 'Sell', self.__amount, self.__code, self.__price, self.__msg) 271 | elif self.__action == self.CANCEL: 272 | result = '[%d:%d] %s job(serial no: %s)\nMSG:%s' \ 273 | % (self.__module_no, self.__serial_no, 274 | 'Canceling', self.__entrust_no, self.__cancel_serial_no) 275 | 276 | elif self.__action == self.FUND_APPLY: 277 | result = '[%d:%d] %s %d of \'%s\' @ %.3f(estimate)\nMSG:%s' \ 278 | % (self.__module_no, self.__serial_no, 279 | 'Apply', self.__amount, self.__code, self.__price, self.__msg) 280 | elif self.__action == self.FUND_REDEEM: 281 | result = '[%d:%d] %s %d of \'%s\' # %.3f(estimate) \nMSG:%s' \ 282 | % (self.__module_no, self.__serial_no, 283 | 'Redeem', self.__amount, self.__code, self.__price, self.__msg) 284 | 285 | return result 286 | 287 | 288 | -------------------------------------------------------------------------------- /Trade/Quotation.py: -------------------------------------------------------------------------------- 1 | # 2 | # Created by 'changye' on '16-1-6' 3 | # 4 | from Tools.SinaApi import * 5 | 6 | __author__ = 'changye' 7 | 8 | 9 | def get_quote(focus_list): 10 | return get_sina_quote(focus_list) 11 | 12 | 13 | def get_average_price_of_certain_amount_buy(hq, amount): 14 | total = 0 15 | index = 0 16 | total_price = 0 17 | total_can_buy = hq['sell_quantity'][0] + hq['sell_quantity'][1] + hq['sell_quantity'][2] + hq['sell_quantity'][3] 18 | while total <= amount and index < 4: 19 | volume = hq['sell_quantity'][index] 20 | price = hq['sell_quote'][index] 21 | max_canbuy = total + volume 22 | if max_canbuy > amount: 23 | total_price += (amount - total) * price 24 | total = amount 25 | else: 26 | total_price = total_price + volume * price 27 | total = total + volume 28 | index += 1 29 | if total == 0: 30 | return 0, 0, 0, 0 31 | else: 32 | return total_price / total, total, total_can_buy, hq['sell_quote'][3] 33 | 34 | 35 | def get_average_price_of_certain_amount_sell(hq, amount): 36 | total = 0 37 | index = 0 38 | total_price = 0 39 | total_can_sell = hq['buy_quantity'][0] + hq['buy_quantity'][1] + hq['buy_quantity'][2] + hq['buy_quantity'][3] 40 | while total <= amount and index < 4: 41 | volume = hq['buy_quantity'][index] 42 | price = hq['buy_quote'][index] 43 | max_cansell = total + volume 44 | if max_cansell > amount: 45 | total_price += (amount - total) * price 46 | total = amount 47 | else: 48 | total_price = total_price + volume * price 49 | total = total + volume 50 | index += 1 51 | if total == 0: 52 | return 0, 0, 0, 0 53 | else: 54 | return total_price / total, total, total_can_sell, hq['buy_quote'][3] 55 | -------------------------------------------------------------------------------- /Trade/Trader.py: -------------------------------------------------------------------------------- 1 | from Socket.GFSocket import GFSocket 2 | from multiprocessing import Process, Lock, Value, Manager 3 | from multiprocessing.managers import BaseManager 4 | from time import sleep 5 | from Trade.Job import Job, Dependence 6 | import logging 7 | 8 | # 9 | # Created by 'changye' on '15-11-5' 10 | # 11 | 12 | __author__ = 'changye' 13 | 14 | 15 | class MyManager(BaseManager): 16 | pass 17 | 18 | def SocketManager(): 19 | m = MyManager() 20 | m.start() 21 | return m 22 | 23 | MyManager.register('GFSocket', GFSocket) 24 | 25 | 26 | class Trader(object): 27 | def __init__(self, account, password, notifier, ocr_service, debug_single_step=False): 28 | self.__account = account 29 | self.__password = password 30 | self.__notifier = notifier 31 | self.__ocr_service = ocr_service 32 | 33 | self.__manager = Manager() 34 | 35 | self.__job_list = self.__manager.list() 36 | self.__job_list_lock = Lock() 37 | 38 | self.__map = self.__manager.dict() 39 | self.__entrust_map = self.__manager.dict() 40 | 41 | self.__process = None 42 | self.__keep_working = Value('i', 1) 43 | 44 | if debug_single_step: 45 | self.__debug_single_step = Value('i', 1) 46 | else: 47 | self.__debug_single_step = Value('i', 0) 48 | 49 | self.__debug_single_step_go = Value('i', 0) 50 | self.__debug_single_step_lock = Lock() 51 | 52 | def initial_socket(self): 53 | socket_manager = SocketManager() 54 | self.__socket = socket_manager.GFSocket(self.__account, self.__password) 55 | 56 | def sign_in_socket(self, retry_times=5, fetch_vericode_inteval=60, fetch_vericode_times=3): 57 | # import random 58 | # import string 59 | # from time import sleep 60 | # import re 61 | counter = 0 62 | while counter < retry_times: 63 | # 获取验证码 64 | verify_code = self.__socket.prepare_login() 65 | verify_code = self.__ocr_service.recognize(verify_code, self.__socket.verify_code_length()) 66 | if verify_code is None: 67 | continue 68 | logging.info('verify_code is %s' % (verify_code, )) 69 | self.__socket.enter_verify_code(verify_code) 70 | if self.__socket.login() is True: 71 | self.__socket.prepare_trade() 72 | return True 73 | counter += 1 74 | sleep(fetch_vericode_inteval) 75 | return False 76 | 77 | def sign_out_socket(self): 78 | self.__socket.logout() 79 | 80 | def add_jobs_to_pending_list(self, jobs): 81 | self.__job_list_lock.acquire() 82 | logging.info('add jobs to job_list') 83 | start_index = len(self.__job_list) 84 | for j in jobs: 85 | self.__job_list.append(j) 86 | 87 | # 将job的位置加入查找表之中 88 | # logging.warning('add job to map %d,%d,%d' % (j.module_no, j.serial_no, start_index)) 89 | self.job_map(j.module_no, j.serial_no, start_index) 90 | start_index += 1 91 | logging.info('release job_list') 92 | self.__job_list_lock.release() 93 | 94 | def job_map(self, module_no, serial_no, index): 95 | key = '%d-%d' % (module_no, serial_no) 96 | self.__map[key] = index 97 | 98 | def job_find(self, module_no, serial_no): 99 | key = '%d-%d' % (module_no, serial_no) 100 | index = self.__map.get(key, -1) 101 | # logging.warning('find %d,%d,%d' % (module_no, serial_no, index)) 102 | return index 103 | 104 | def start(self): 105 | self.__process = Process(target=self.__issue_cmd) 106 | self.__process.start() 107 | 108 | def exit(self): 109 | self.__keep_working.value = 0 110 | self.__process.join() 111 | 112 | def __issue_cmd(self): 113 | start_issue_index = 0 114 | start_review_index = 0 115 | 116 | while self.__keep_working.value == 1: 117 | # 如果处于单步测试模式, 则只有single_step_go不为0时才能继续运行 118 | if self.__debug_single_step.value == 1: 119 | if self.__debug_single_step_go.value == 0: 120 | continue 121 | else: 122 | self.__debug_single_step_lock.acquire() 123 | self.__debug_single_step_go.value = 0 124 | self.__debug_single_step_lock.release() 125 | 126 | # 发射交易命令 127 | # 获得job_list的锁, 以防止在查询job_list长度的时候 job_list加入新的命令 128 | self.__job_list_lock.acquire() 129 | last_job_index = len(self.__job_list) 130 | # 释放锁 131 | self.__job_list_lock.release() 132 | # logging.warning('start is %d' % (start_issue_index, )) 133 | pass 134 | # 开始发射指令 135 | for index in range(start_issue_index, last_job_index): 136 | self._do_issue(index) 137 | 138 | # 查找下一次发射的起始值 以及下一次review的起始值 139 | start_search_index = min(start_issue_index, start_review_index) 140 | for index in range(start_search_index, last_job_index): 141 | job = self.__job_list[index] 142 | # 下一次发射的起始值确定的条件: 143 | # 在第0条指令到下一次发射的起始值之间(不含下一次发射的起始值), 所有的job必须need_to_issue为False 144 | if not job.need_to_issue: 145 | if start_issue_index == index: 146 | start_issue_index = index + 1 147 | # 下一次review起始值确定条件: 148 | # 在第0条指令到下一次review的起始值间(不含下一次review的起始值), 所有的job必须need_to_review为False 149 | if not job.need_to_review: 150 | if start_review_index == index: 151 | start_review_index = index + 1 152 | 153 | # 如果start_review_index大于等于job_list的长度,说明所有的Job都不需要更新状态 154 | # 否则说明存在已委托但未成交或未撤单的指令,这些指令需要定期访问券商以更新他们的状态 155 | # print('start issue index is %d, and start review index is %d' % (start_issue_index, start_review_index)) 156 | if start_review_index < last_job_index: 157 | sleep(0.5) 158 | self.refresh_job_status() 159 | 160 | def _do_issue(self, index): 161 | job = self.__job_list[index] 162 | ready_to_issue = True 163 | need_to_write_back = False 164 | logging.warning('issuing job[%d:%d]' % (job.module_no, job.serial_no)) 165 | # 查看job是否为simulate job, 如果是则进打印job的msg, 如果否才是真正需要发射的job 166 | if job.is_simulate is True: 167 | logging.warning('[Simulate]\t%s' % (job.info,)) 168 | job.mark_dead() 169 | self.__job_list_lock.acquire() 170 | self.__job_list[index] = job 171 | self.__job_list_lock.release() 172 | return 173 | 174 | # 查看job是否需要发射, 已经发射过的以及已经死亡的job都不需要发射 175 | if not job.need_to_issue: 176 | logging.warning('已经发射过了,已经成交了或者已经死亡了') 177 | return 178 | 179 | # 查看job是否已经超过了允许发送的次数 180 | if job.exceed_allow_retry_times: 181 | logging.warning('发射次数超过了%d次' % (job.allow_retry_times, )) 182 | job.mark_dead() 183 | need_to_write_back = True 184 | ready_to_issue = False 185 | 186 | # 查看job的依赖条件是否满足 187 | if ready_to_issue: 188 | for d in job.dependence: 189 | one_dependence_ready = self.check_depend(d) 190 | if one_dependence_ready == Dependence.WAIT: 191 | ready_to_issue = False 192 | elif one_dependence_ready == Dependence.DEAD: 193 | # 如果发现依赖的job已经死亡,则本job也标记为死亡 194 | job.mark_dead() 195 | need_to_write_back = True 196 | ready_to_issue = False 197 | break 198 | 199 | # 满足发射的条件,则发出交易指令 200 | if ready_to_issue: 201 | # 具体的发射指令 202 | self.__do_issue(index, job) 203 | sleep(0.05) 204 | need_to_write_back = True 205 | logging.info(job.info) 206 | 207 | # 下面非常重要, 如果job的状态发生了变化,需要将job拷贝回list中,否则list中的内容是不会改变的 208 | if need_to_write_back: 209 | self.__job_list_lock.acquire() 210 | self.__job_list[index] = job 211 | self.__job_list_lock.release() 212 | 213 | def check_depend(self, depend): 214 | """ 215 | 查询Job的依赖关系是否实现 216 | :param depend: 依赖条件 217 | :type depend: Trade.Job.Dependence 218 | :return: 219 | """ 220 | index = self.job_find(depend.depend_job_module_no, depend.depend_job_serial_no) 221 | job = self.__job_list[index] 222 | if job.status >= depend.depend_job_status: 223 | return Dependence.READY 224 | elif job.status == Job.DEAD: 225 | return Dependence.DEAD 226 | else: 227 | return Dependence.WAIT 228 | 229 | def __do_issue(self, index, job): 230 | """ 231 | 具体的交易发射在这里实现 232 | :param index: 发射的指令在self.__job_list中的位置 233 | :type index: int 234 | :param job: 发射的命令 235 | :type job: Trade.Job.Job 236 | :return: 237 | :rtype: 238 | """ 239 | # 发射次数加1 240 | job.tried_once() 241 | (market, code, amount, price) = job.action_detail 242 | 243 | # 买入操作 244 | if job.action == Job.BUY: 245 | entrust_no = self.__socket.buy(code, amount, price, market=market) 246 | if entrust_no >= 0: 247 | job.mark_entrust(entrust_no) 248 | self.__entrust_map[entrust_no] = index 249 | else: 250 | job.mark_fail() 251 | return 252 | 253 | # 卖出操作 254 | if job.action == Job.SELL: 255 | entrust_no = self.__socket.sell(code, amount, price, market=market) 256 | if entrust_no >= 0: 257 | job.mark_entrust(entrust_no) 258 | self.__entrust_map[entrust_no] = index 259 | else: 260 | job.mark_fail() 261 | return 262 | 263 | # 撤单操作 264 | if job.action == Job.CANCEL: 265 | job_need_to_cancel_index = self.job_find(job.module_no, job.get_cancel_serial_no()) 266 | if job_need_to_cancel_index != -1: 267 | job_need_to_cancel = self.__job_list[job_need_to_cancel_index] 268 | if job_need_to_cancel.entrust_no >= 0 and Job.PENDING < job_need_to_cancel.status < Job.TRADED_ALL: 269 | result = self.__socket.cancel(job_need_to_cancel.entrust_no) 270 | if result is True: 271 | job.mark_traded_all() 272 | else: 273 | job.mark_fail() 274 | else: 275 | job.mark_fail() 276 | else: 277 | job.mark_dead() 278 | return 279 | 280 | # 场内基金申赎及分拆合并操作(不包括上海lof基金) 281 | if job.action == Job.FUND_APPLY: 282 | entrust_no = self.__socket.fund_apply(code, amount, market) 283 | if entrust_no >= 0: 284 | job.mark_entrust(entrust_no) 285 | job.mark_trade_after_market_close() 286 | self.__entrust_map[entrust_no] = index 287 | else: 288 | job.mark_fail() 289 | return 290 | 291 | if job.action == Job.FUND_REDEEM: 292 | entrust_no = self.__socket.fund_redeem(code, amount, market) 293 | if entrust_no >= 0: 294 | job.mark_entrust(entrust_no) 295 | job.mark_trade_after_market_close() 296 | self.__entrust_map[entrust_no] = index 297 | else: 298 | job.mark_fail() 299 | return 300 | 301 | if job.action == Job.FUND_SPLIT: 302 | entrust_no = self.__socket.fund_split(code, amount) 303 | if entrust_no >= 0: 304 | job.mark_entrust(entrust_no) 305 | job.mark_trade_after_market_close() 306 | self.__entrust_map[entrust_no] = index 307 | else: 308 | job.mark_fail() 309 | return 310 | 311 | if job.action == Job.FUND_MERGE: 312 | entrust_no = self.__socket.fund_merge(code, amount) 313 | if entrust_no >= 0: 314 | job.mark_entrust(entrust_no) 315 | job.mark_trade_after_market_close() 316 | self.__entrust_map[entrust_no] = index 317 | else: 318 | job.mark_fail() 319 | return 320 | 321 | # 如果如不在上述操作实现的范围内,说明该操作不存在或尚未实现,该任务认为是死任务 322 | job.mark_dead() 323 | return 324 | 325 | def refresh_job_status(self): 326 | entrusted_jobs = self.__socket.entrust_list() 327 | for j in entrusted_jobs: 328 | # Socket返回的值是字符串,而trader存入的值是一个数字,这个地方需要特别注意 329 | entrust_no = j['entrust_no'] 330 | job_status = self.entrust_status_to_job_status(j['entrust_status'], j['entrust_status_dict']) 331 | index = self.__entrust_map.get(entrust_no, -1) 332 | # print(entrust_no, index, job_status) 333 | if index >= 0: 334 | job = self.__job_list[index] 335 | # 无变化则不需要更新 336 | if job.status == job_status: 337 | continue 338 | # 如果是收盘后才能成交的交易(例如深市分级基金的申购,在收市之前始终处于已报状态) 339 | # 在这种情况下, 已报状态即等价为TRADE_AFTER_MARKET_CLOSE 340 | elif job.status == Job.TRADE_AFTER_MARKET_CLOSE and job_status == Job.ENTRUSTED: 341 | continue 342 | else: 343 | # print(index, job_status) 344 | job.status = job_status 345 | self.__job_list_lock.acquire() 346 | self.__job_list[index] = job 347 | self.__job_list_lock.release() 348 | else: 349 | continue 350 | 351 | @staticmethod 352 | def entrust_status_to_job_status(entrust_status, entrust_status_dict): 353 | """ 354 | 将券商的entrust_status映射到Job类的Job.status 355 | :param entrust_status: 券商返回的委托状态,8为已成,9为废单,6为已撤,2为已报,7为部成,5为部撤,3为已报待撤 356 | :type entrust_status: int 357 | :return: Job类的状态 358 | :rtype: int 359 | """ 360 | if entrust_status == 2 or entrust_status == 3: 361 | return Job.ENTRUSTED 362 | 363 | if entrust_status == 5: 364 | return Job.CANCELED_PARTLY 365 | 366 | if entrust_status == 6: 367 | return Job.CANCELED 368 | 369 | if entrust_status == 9: 370 | return Job.DEAD 371 | 372 | if entrust_status == 7: 373 | return Job.TRADED_PARTLY 374 | 375 | if entrust_status == 8: 376 | return Job.TRADED_ALL 377 | 378 | logging.error('ERROR!!\t返回委托状态: %d (%s) 未知, 请确认!!' % (entrust_status, entrust_status_dict)) 379 | # 该返回值需要再考虑,如果券商返回了一个未知的值,如何避免出现误操作. 380 | return Job.ENTRUSTED 381 | 382 | def get_stock_position(self, force_refresh=False): 383 | """ 384 | 获取股票持仓 385 | :param force_refresh: 强制刷新,暂未开放,默认既为强制刷新 386 | :type force_refresh: bool 387 | :return: 返回股票持仓 388 | :rtype: list 389 | """ 390 | return self.__socket.stock_position() 391 | 392 | def get_balance(self, force_refresh=False): 393 | """ 394 | 获取资金账户信息 395 | :param force_refresh: 强制刷新,暂未开放,默认既为强制刷新 396 | :type force_refresh: bool 397 | :return: 返回股票持仓 398 | :rtype: list 399 | """ 400 | return self.__socket.balance() 401 | 402 | def check_job_status(self, job): 403 | index = self.job_find(job.module_no, job.serial_no) 404 | try: 405 | return self.__job_list[index].status 406 | except IndexError: 407 | return Job.DEAD 408 | 409 | def debug_single_step_go(self): 410 | self.__debug_single_step_lock.acquire() 411 | self.__debug_single_step_go.value = 1 412 | self.__debug_single_step_lock.release() 413 | 414 | if __name__ == '__main__': 415 | 416 | import json 417 | # 配置校验码识别系统的用户名和密码(校验码识别由ruokuai.com提供, 请注册该网站的用户(非开发者)) 418 | config_ocr = dict() 419 | with open('../Configs/Tools.config.ocr', 'r') as f: 420 | config_ocr = json.loads(f.read().replace(r'\n', '')) 421 | from Tools.Ocr import Ocr 422 | my_ocr = Ocr(config_ocr['account'], config_ocr['password']) 423 | 424 | config = dict() 425 | with open('../Configs/Socket.config.gf', 'r') as f: 426 | config = json.loads(f.read().replace(r'\n', '')) 427 | trader = Trader(config['account'], config['password'], notifier=notifier, ocr_service=ocr) 428 | 429 | trader.initial_socket() 430 | trader.sign_in_socket() 431 | print(trader.get_stock_position()) 432 | print(trader.get_balance()) 433 | 434 | 435 | 436 | --------------------------------------------------------------------------------