├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── config └── scheduler-example.ini ├── examples ├── __init__.py ├── joinquant │ ├── __init__.py │ └── simple_strategy.py └── ricequant │ ├── __init__.py │ └── simple_strategy.py ├── scripts └── shipane-scheduler.py ├── setup.cfg ├── setup.py ├── shipane_sdk ├── __init__.py ├── ap.py ├── base_quant_client.py ├── client.py ├── jobs │ ├── __init__.py │ ├── new_stock_purchase.py │ └── online_quant_following.py ├── joinquant │ ├── __init__.py │ ├── client.py │ ├── executor.py │ └── transaction.py ├── market_utils.py ├── ricequant │ ├── __init__.py │ ├── client.py │ ├── executor.py │ └── transaction.py ├── scheduler.py ├── stock.py └── transaction.py └── tests ├── __init__.py ├── config └── config.ini.template ├── sample_data ├── rq_client-response.json └── transactionDetail.json └── shipane_sdk ├── __init__.py ├── joinquant ├── __init__.py ├── test_client.py ├── test_executor.py └── test_transaction.py ├── ricequant ├── __init__.py ├── test_client.py └── test_transaction.py ├── test_client.py └── test_stock.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | .idea/ 92 | config/* 93 | !config/*-example.ini 94 | examples/joinquant/config/config.ini 95 | tests/config/config.ini 96 | 97 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 sinall 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | # Include the license file 2 | include LICENSE.txt 3 | 4 | # Include the config files 5 | recursive-include config * 6 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ShiPanE-Python-SDK 2 | ================== 3 | 4 | 实盘易(ShiPanE)Python SDK,通达信自动化交易 API。 5 | 6 | | 实盘易是\ `爱股网 `__\ 旗下的股票自动化解决方案;可管理通达信等交易终端,并为用户提供基于 7 | HTTP 协议的 RESTFul service。 8 | | 详情见:http://www.iguuu.com/e 9 | | 交流QQ群:11527956 |实盘易-股票自动交易| 10 | 11 | 功能介绍 12 | -------- 13 | 14 | - 简单的实盘易 HTTP API 封装,见 shipane_sdk/client.py 15 | - 多账号自动新股申购(自动打新) 16 | - 聚宽(JoinQuant)集成 17 | - 米筐(RiceQuant)集成 18 | 19 | 定时任务调度 20 | -------------- 21 | 22 | - 多账号自动新股申购(自动打新) 23 | - 聚宽(JoinQuant)自动跟单(抓取方式) 24 | - 米筐(RiceQuant)自动跟单(抓取方式) 25 | 26 | Windows 27 | ~~~~~~~ 28 | 29 | 安装 30 | ^^^^ 31 | 32 | - 安装 Python 3.5(建议安装 `Anaconda3 `_) 33 | - cmd 中运行:pip install --no-binary shipane_sdk shipane_sdk 34 | - cmd 中运行:cd %UserProfile%\\.shipane_sdk\\config 35 | - cmd 中运行:echo No | copy /-Y scheduler-example.ini scheduler.ini 36 | 37 | 配置 38 | ^^^^ 39 | 40 | - cmd 中运行:explorer %UserProfile%\\.shipane_sdk\\config 41 | - 修改 scheduler.ini 中的配置(建议使用Notepad++) 42 | 43 | 运行 44 | ^^^^ 45 | 46 | - 找到 python 安装目录,例如:C:\\Program Files\\Anaconda3 47 | - cmd 下执行(具体路径自行修改):python "C:\\Program Files\\Anaconda3\\Scripts\\shipane-scheduler.py" 48 | 49 | 升级 50 | ^^^^ 51 | 52 | - cmd 中运行:pip install --upgrade --no-deps --no-binary shipane_sdk shipane_sdk 53 | - 参考 scheduler-example.ini 修改 scheduler.ini 54 | 55 | Mac/Linux 56 | ~~~~~~~~~ 57 | 58 | 安装 59 | ^^^^ 60 | 61 | - 安装 Python 3.5 62 | - terminal 中运行:pip install --no-binary shipane_sdk shipane_sdk 63 | - terminal 中运行:cp -n ~/.shipane_sdk/config/scheduler-example.ini ~/.shipane_sdk/config/scheduler.ini 64 | 65 | 配置 66 | ^^^^ 67 | 68 | - 修改 ~/.shipane_sdk/config/scheduler.ini 69 | 70 | 运行 71 | ^^^^ 72 | 73 | - terminal 中运行:shipane-scheduler.py 74 | 75 | 升级 76 | ~~~~ 77 | 78 | - terminal 中运行:pip install --upgrade --no-deps --no-binary shipane_sdk shipane_sdk 79 | - 参考 scheduler-example.ini 修改 scheduler.ini 80 | 81 | 聚宽(JoinQuant)集成 82 | --------------------- 83 | 84 | 一. 推送方式 85 | ~~~~~~~~~~~~ 86 | 87 | 适用于云服务器环境,例如阿里云;特点是稳定、高效,集成简单。 88 | 89 | 准备工作 90 | ^^^^^^^^ 91 | 92 | - 部署实盘易成功。 93 | - 手动测试通过。 94 | - 聚宽(公网)可访问实盘易。 95 | 96 | 步骤 97 | ^^^^ 98 | 99 | - 将 shipane\_sdk/client.py 上传至聚宽“投资研究”根目录,并重命名为 shipane\_sdk.py。 100 | - 将 shipane\_sdk/joinquant/executor.py 拷贝粘贴到 shpane\_sdk.py 末尾。 101 | - 用法请参考 examples/joinquant/simple\_strategy.py (注意将其中的 xxx.xxx.xxx.xxx 替换为实际 IP)。 102 | 103 | 二. 抓取方式 104 | ~~~~~~~~~~~~ 105 | 106 | 无需云服务器,采用定时轮询的方式,实时性不如"推送方式"。 107 | 108 | 准备工作 109 | ^^^^^^^^ 110 | 111 | - 部署实盘易成功。 112 | - 手动测试通过。 113 | 114 | 步骤 115 | ^^^^ 116 | 117 | 见 `定时任务调度 <#定时任务调度>`__ 118 | 119 | 米筐(RiceQuant)集成 120 | --------------------- 121 | 122 | 一. 推送方式 123 | ~~~~~~~~~~~~ 124 | 125 | 适用于云服务器环境,例如阿里云;特点是稳定、高效,集成简单。 126 | 127 | 准备工作 128 | ^^^^^^^^ 129 | 130 | - 部署实盘易成功。 131 | - 手动测试通过。 132 | - 米筐(公网)可访问实盘易。 133 | 134 | 步骤 135 | ^^^^ 136 | 137 | - 将 shipane\_sdk/client.py 上传米筐“策略研究”根目录,并重命名为 shipane\_sdk.py。 138 | - 将 shipane\_sdk/ricequant/executor.py 拷贝粘贴到 shpane\_sdk.py 末尾。 139 | - 用法请参考 examples/ricequant/simple\_strategy.py (注意将其中的 xxx.xxx.xxx.xxx 替换为实际 IP)。 140 | 141 | 二. 抓取方式 142 | ~~~~~~~~~~~~ 143 | 144 | 采用定时轮询的方式。 145 | 146 | 准备工作 147 | ^^^^^^^^ 148 | 149 | - 部署实盘易成功。 150 | - 手动测试通过。 151 | 152 | 步骤 153 | ^^^^ 154 | 155 | 见 `定时任务调度 <#定时任务调度>`__ 156 | 157 | 其他语言 SDK 158 | ------------ 159 | 160 | C# SDK 161 | ~~~~~~ 162 | 163 | | 由网友 @YBO(QQ:259219140)开发。 164 | | 见 `ShiPanETradingSDK `_ 165 | 166 | .. |实盘易-股票自动交易| image:: http://pub.idqqimg.com/wpa/images/group.png 167 | :target: http://shang.qq.com/wpa/qunwpa?idkey=1ce867356702f5f7c56d07d5c694e37a3b9a523efce199bb0f6ff30410c6185d%22 168 | -------------------------------------------------------------------------------- /config/scheduler-example.ini: -------------------------------------------------------------------------------- 1 | ; schedule 参数设置,类似于 cron 表达式 2 | ; 3 | ; 格式为:[秒(0-59)] [分(0-59)] [时(0-23)] [星期几(0-6 或英文缩写)] [星期(1-53)] [日(1-31)] [月(1-12)] [年(四位数字)] 4 | ; (星期几英文缩写:mon,tue,wed,thu,fri,sat,sun) 5 | ; 6 | ; 字段支持表达式: 7 | ; - * 为任意单位时间触发 8 | ; - */a 为每 a 个单位时间触发 9 | ; - a-b 为 a 到 b 的区间触发 10 | ; - a-b/c 为 a 到 b 的区间每 c 个单位时间触发 11 | ; 12 | ; 详见:https://apscheduler.readthedocs.io/en/v2.1.2/cronschedule.html 13 | ; 14 | 15 | ; 16 | ; 实盘易配置 17 | ; 18 | [ShiPanE] 19 | host=localhost 20 | port=8888 21 | key= 22 | 23 | ; 24 | ; 交易客户端 client 参数别名列表 25 | ; 26 | [ClientAliases] 27 | ; 值可以参考实盘易安装目录下的《用户手册.txt》 28 | client1=title:monijiaoyi 29 | client2=title:guotai 30 | 31 | ; 32 | ; 自动新股申购配置 33 | ; 34 | [NewStocks] 35 | ; 是否启用? 36 | enabled=false 37 | 38 | ; 默认设置为:星期一至星期五中午十二点 39 | schedule=0 0 12 mon-fri * * * * 40 | 41 | ; 需要自动新股申购的交易客户端列表,以,(半角逗号)分割 42 | ; clients=client1,client2 43 | clients=client1 44 | 45 | [JoinQuant] 46 | username= 47 | password= 48 | 49 | ; 模拟交易 URL 中 backtestId= 后面的那串字符 50 | ; 例如,模拟交易 URL 为:https://www.joinquant.com/algorithm/live/index?backtestId=c215e5e57b30a65df4139bfff8c90e99 51 | ; 聚宽的 backtestId 会经常改变,但是指向的都是同一个模拟交易,该配置无需跟着修改 52 | ; 则此处填写:c215e5e57b30a65df4139bfff8c90e99 53 | backtest_id= 54 | 55 | ; 是否启用? 56 | enabled=false 57 | 58 | ; 59 | ; 默认设置为:星期一至星期五 9:00 到 15:00 每分钟的第 30 秒 60 | schedule=30 */1 9-15 mon-fri * * * * 61 | 62 | ; 需要跟单的交易客户端列表,以,(半角逗号)分割 63 | ; clients=client1,client2 64 | clients=client1 65 | 66 | [RiceQuant] 67 | username= 68 | password= 69 | 70 | ; 模拟交易 URL 中 最后一个 / 后面的那串字符 71 | ; 例如,模拟交易 URL 为:https://www.ricequant.com/pt/454879/1226010 72 | ; 则此处填写:1226010 73 | run_id= 74 | 75 | ; 是否启用? 76 | enabled=false 77 | 78 | ; 79 | ; 默认设置为:星期一至星期五 9:00 到 15:00 每分钟的第 30 秒 80 | schedule=30 */1 9-15 mon-fri * * * * 81 | 82 | ; 需要跟单的交易客户端列表,以,(半角逗号)分割 83 | ; clients=client1,client2 84 | clients=client1 85 | -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/examples/__init__.py -------------------------------------------------------------------------------- /examples/joinquant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/examples/joinquant/__init__.py -------------------------------------------------------------------------------- /examples/joinquant/simple_strategy.py: -------------------------------------------------------------------------------- 1 | import shipane_sdk 2 | 3 | 4 | # 初始化函数,设定要操作的股票、基准等等 5 | def initialize(context): 6 | # 定义一个全局变量, 保存要操作的股票 7 | # 000001(股票:平安银行) 8 | g.security = '000001.XSHE' 9 | # 设定沪深300作为基准 10 | set_benchmark('000300.XSHG') 11 | 12 | 13 | def process_initialize(context): 14 | # 创建 JoinQuantExecutor 对象 15 | # 可选参数包括:host, port, client 等 16 | # 请将下面的 IP 替换为实际 IP 17 | g.__executor = shipane_sdk.JoinQuantExecutor(host='xxx.xxx.xxx.xxx') 18 | 19 | 20 | # 每个单位时间(如果按天回测,则每天调用一次,如果按分钟,则每分钟调用一次)调用一次 21 | def handle_data(context, data): 22 | # 保存 order 对象 23 | order_ = order(g.security, 100) 24 | # 实盘易依据聚宽的 order 对象下单 25 | g.__executor.execute(order_) 26 | 27 | order_ = order(g.security, -100) 28 | g.__executor.execute(order_) 29 | 30 | # 撤单 31 | g.__executor.cancel(order_) 32 | -------------------------------------------------------------------------------- /examples/ricequant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/examples/ricequant/__init__.py -------------------------------------------------------------------------------- /examples/ricequant/simple_strategy.py: -------------------------------------------------------------------------------- 1 | # 可以自己import我们平台支持的第三方python模块,比如pandas、numpy等。 2 | import shipane_sdk 3 | 4 | 5 | # 在这个方法中编写任何的初始化逻辑。context对象将会在你的算法策略的任何方法之间做传递。 6 | def init(context): 7 | context.s1 = "000001.XSHE" 8 | # 实时打印日志 9 | logger.info("Interested at stock: " + str(context.s1)) 10 | 11 | def before_trading(context): 12 | # 创建 RiceQuantExecutor 对象 13 | # 可选参数包括:host, port, client 等 14 | # 请将下面的 IP 替换为实际 IP 15 | context.__executor = shipane_sdk.RiceQuantExecutor(host='xxx.xxx.xxx.xxx') 16 | 17 | # 你选择的证券的数据更新将会触发此段逻辑,例如日或分钟历史数据切片或者是实时数据切片更新 18 | def handle_bar(context, bar_dict): 19 | # 开始编写你的主要的算法逻辑 20 | 21 | # bar_dict[order_book_id] 可以拿到某个证券的bar信息 22 | # context.portfolio 可以拿到现在的投资组合状态信息 23 | 24 | # 使用order_shares(id_or_ins, amount)方法进行落单 25 | 26 | # TODO: 开始编写你的算法吧! 27 | # 保存 order_id 28 | order_id = order_shares(context.s1, 100) 29 | # 实盘易依据 order_id 下单 30 | context.__executor.execute(order_id) 31 | 32 | order_id = order_shares(context.s1, -100) 33 | context.__executor.execute(order_id) 34 | 35 | cancel_order(order_id) 36 | context.__executor.cancel(order_id) 37 | -------------------------------------------------------------------------------- /scripts/shipane-scheduler.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from shipane_sdk.scheduler import Scheduler 4 | 5 | Scheduler().start() 6 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal=1 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from setuptools import setup, find_packages 4 | from codecs import open 5 | from os import path 6 | import os.path 7 | 8 | here = path.abspath(path.dirname(__file__)) 9 | 10 | with open(path.join(here, 'README.rst'), encoding='utf-8') as f: 11 | long_description = f.read() 12 | 13 | setup( 14 | name='shipane_sdk', 15 | 16 | version='1.0.0.a14', 17 | 18 | description=u'实盘易(ShiPanE)Python SDK,通达信自动化交易 API。', 19 | long_description=long_description, 20 | 21 | url='https://github.com/sinall/ShiPanE-Python-SDK', 22 | 23 | author='sinall', 24 | author_email='gaoruinan@163.com', 25 | 26 | license='MIT', 27 | 28 | classifiers=[ 29 | 'Development Status :: 3 - Alpha', 30 | 31 | 'Intended Audience :: Developers', 32 | 'Intended Audience :: Financial and Insurance Industry', 33 | 'Topic :: Office/Business :: Financial :: Investment', 34 | 35 | 'License :: OSI Approved :: MIT License', 36 | 37 | 'Programming Language :: Python :: 2.7', 38 | 'Programming Language :: Python :: 3', 39 | 'Programming Language :: Python :: 3.3', 40 | 'Programming Language :: Python :: 3.4', 41 | 'Programming Language :: Python :: 3.5', 42 | ], 43 | 44 | keywords='ShiPanE SDK 通达信 TDX Automation', 45 | 46 | packages=find_packages(exclude=['contrib', 'docs', 'tests']), 47 | 48 | install_requires=['requests', 'six', 'apscheduler', 'lxml', 'cssselect', 'bs4', 'html5lib', 'pandas', 'rqopen-client'], 49 | 50 | extras_require={ 51 | 'dev': [], 52 | 'test': [], 53 | }, 54 | 55 | package_data={ 56 | }, 57 | 58 | data_files=[(os.path.join(os.path.expanduser('~'), '.shipane_sdk', 'config'), ['config/scheduler-example.ini'])], 59 | 60 | scripts=['scripts/shipane-scheduler.py'], 61 | 62 | entry_points={ 63 | 'console_scripts': [ 64 | ], 65 | }, 66 | ) 67 | -------------------------------------------------------------------------------- /shipane_sdk/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .client import Client 4 | from .joinquant.executor import JoinQuantExecutor 5 | -------------------------------------------------------------------------------- /shipane_sdk/ap.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from apscheduler.triggers.cron import CronTrigger 4 | 5 | 6 | class APCronParser(object): 7 | @classmethod 8 | def parse(cls, expression): 9 | parts = list(reversed(expression.split())) 10 | for i in range(len(parts)): 11 | if parts[i] == '?': 12 | parts[i] = None 13 | 14 | return CronTrigger(parts[0], parts[1], parts[2], parts[3], parts[4], parts[5], parts[6], parts[7]) 15 | -------------------------------------------------------------------------------- /shipane_sdk/base_quant_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import date, timedelta, datetime 4 | 5 | 6 | class BaseQuantClient(object): 7 | def __init__(self, name): 8 | self._name = name 9 | self._last_login_time = datetime.now() - timedelta(1) 10 | 11 | @property 12 | def name(self): 13 | return self._name 14 | 15 | def login(self): 16 | self._last_login_time = datetime.now() 17 | 18 | def is_login(self): 19 | return self._last_login_time >= datetime.combine(date.today(), datetime.min.time()) 20 | 21 | def query(self): 22 | return [] 23 | -------------------------------------------------------------------------------- /shipane_sdk/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import copy 4 | 5 | import pandas as pd 6 | import requests 7 | from requests import Request 8 | from six.moves.urllib.parse import urlencode 9 | 10 | 11 | class Client(object): 12 | def __init__(self, logger=None, **kwargs): 13 | self._logger = logger 14 | self._host = kwargs.pop('host', 'localhost') 15 | self._port = kwargs.pop('port', 8888) 16 | self._key = kwargs.pop('key', '') 17 | self._timeout = kwargs.pop('timeout', (5.0, 10.0)) 18 | 19 | @property 20 | def host(self): 21 | return self._host 22 | 23 | @host.setter 24 | def host(self, value): 25 | self._host = value 26 | 27 | @property 28 | def port(self): 29 | return self._port 30 | 31 | @port.setter 32 | def port(self, value): 33 | self._port = value 34 | 35 | @property 36 | def key(self): 37 | return self._key 38 | 39 | @key.setter 40 | def key(self, value): 41 | self._key = value 42 | 43 | @property 44 | def timeout(self): 45 | return self._timeout 46 | 47 | @timeout.setter 48 | def timeout(self, value): 49 | self._timeout = value 50 | 51 | def get_account(self, client=None): 52 | request = Request('GET', self.__create_url(client, 'accounts')) 53 | response = self.__send_request(request) 54 | return response.json() 55 | 56 | def get_positions(self, client=None): 57 | request = Request('GET', self.__create_url(client, 'positions')) 58 | response = self.__send_request(request) 59 | json = response.json() 60 | sub_accounts = pd.DataFrame(json['subAccounts']).T 61 | positions = pd.DataFrame(json['dataTable']['rows'], columns=json['dataTable']['columns']) 62 | return {'sub_accounts': sub_accounts, 'positions': positions} 63 | 64 | def buy(self, client=None, **kwargs): 65 | kwargs['action'] = 'BUY' 66 | return self.__execute(client, **kwargs) 67 | 68 | def sell(self, client=None, **kwargs): 69 | kwargs['action'] = 'SELL' 70 | return self.__execute(client, **kwargs) 71 | 72 | def execute(self, client=None, **kwargs): 73 | return self.__execute(client, **kwargs) 74 | 75 | def cancel(self, client, order_id): 76 | request = Request('DELETE', self.__create_order_url(client, order_id)) 77 | self.__send_request(request) 78 | 79 | def cancel_all(self, client=None): 80 | request = Request('DELETE', self.__create_order_url(client)) 81 | self.__send_request(request) 82 | 83 | def query(self, client, navigation): 84 | request = Request('GET', self.__create_url(client, '', navigation=navigation)) 85 | response = self.__send_request(request) 86 | json = response.json() 87 | df = pd.DataFrame(json['dataTable']['rows'], columns=json['dataTable']['columns']) 88 | return df 89 | 90 | def __execute(self, client=None, **kwargs): 91 | if not kwargs.get('type'): 92 | kwargs['type'] = 'LIMIT' 93 | request = Request('POST', self.__create_order_url(client), json=kwargs) 94 | response = self.__send_request(request) 95 | return response.json() 96 | 97 | def __create_order_url(self, client=None, order_id=None, **params): 98 | return self.__create_url(client, 'orders', order_id, **params) 99 | 100 | def __create_url(self, client, resource, resource_id=None, **params): 101 | all_params = copy.deepcopy(params) 102 | if client is not None: 103 | all_params.update(client=client) 104 | all_params.update(key=self._key) 105 | if resource_id is None: 106 | path = '/{}'.format(resource) 107 | else: 108 | path = '/{}/{}'.format(resource, resource_id) 109 | 110 | return '{}{}?{}'.format(self.__create_base_url(), path, urlencode(all_params)) 111 | 112 | def __create_base_url(self): 113 | return 'http://' + self._host + ':' + str(self._port) 114 | 115 | def __send_request(self, request): 116 | prepared_request = request.prepare() 117 | self.__log_request(request) 118 | with requests.sessions.Session() as session: 119 | response = session.send(prepared_request, timeout=self._timeout) 120 | self.__log_response(response) 121 | response.raise_for_status() 122 | return response 123 | 124 | def __log_request(self, request): 125 | if self._logger is None: 126 | return 127 | 128 | if request.json is None: 129 | self._logger.info('Request:\n{} {}'.format(request.method, request.url)) 130 | else: 131 | self._logger.info('Request:\n{} {}\n{}'.format(request.method, request.url, request.json)) 132 | 133 | def __log_response(self, response): 134 | if self._logger is None: 135 | return 136 | 137 | message = 'Response:\n{} {}\n{}'.format(response.status_code, response.reason, response.text) 138 | if response.status_code == 200: 139 | self._logger.info(message) 140 | else: 141 | self._logger.error(message) 142 | -------------------------------------------------------------------------------- /shipane_sdk/jobs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/shipane_sdk/jobs/__init__.py -------------------------------------------------------------------------------- /shipane_sdk/jobs/new_stock_purchase.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import logging 4 | from datetime import datetime 5 | 6 | from shipane_sdk.stock import * 7 | 8 | 9 | class NewStockPurchaseJob(object): 10 | def __init__(self, config, client, client_aliases=None): 11 | self._logger = logging.getLogger() 12 | self._config = config 13 | self._client = client 14 | self._client_aliases = client_aliases 15 | 16 | def __call__(self): 17 | today = datetime.strftime(datetime.today(), '%Y-%m-%d') 18 | df = StockUtils.new_stocks() 19 | df = df[(df.ipo_date == today)] 20 | for client_alias in self._client_aliases: 21 | client = self._client_aliases[client_alias] 22 | self._logger.info(u'客户端[%s(%s)]开始新股申购', client_alias, client) 23 | for index, row in df.iterrows(): 24 | try: 25 | order = { 26 | 'symbol': row['xcode'], 'type': 'LIMIT', 'price': row['price'], 'amountProportion': 'ALL' 27 | } 28 | self._logger.info(u'下单:%s', json.dumps(order)) 29 | self._client.buy(client, **order) 30 | except Exception as e: 31 | self._logger.exception('账户[%s(%s)]申购新股[%s(%s)]失败', client_alias, client, row['name'], row['code']) 32 | self._logger.info(u'客户端[%s(%s)]结束新股申购', client_alias, client) 33 | -------------------------------------------------------------------------------- /shipane_sdk/jobs/online_quant_following.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | from datetime import datetime 5 | 6 | from requests import HTTPError 7 | 8 | from shipane_sdk.market_utils import MarketUtils 9 | 10 | 11 | class OnlineQuantFollowingJob(object): 12 | def __init__(self, shipane_client, quant_client, client_aliases=None, name=None): 13 | self._logger = logging.getLogger() 14 | self._shipane_client = shipane_client 15 | self._quant_client = quant_client 16 | self._client_aliases = client_aliases 17 | self._name = name 18 | self._start_datatime = datetime.now() 19 | self._processed_transactions = [] 20 | 21 | def __call__(self): 22 | if MarketUtils.is_closed(): 23 | self._logger.warning("********** 休市期间不跟单 **********") 24 | if self._processed_transactions: 25 | del self._processed_transactions[:] 26 | return 27 | 28 | if not self._quant_client.is_login(): 29 | self._logger.info("登录 %s", self._quant_client.name) 30 | self._quant_client.login() 31 | 32 | self._logger.info("********** 开始跟单 **********") 33 | try: 34 | all_transactions = self._quant_client.query() 35 | self._logger.info("获取到 %d 条委托", len(all_transactions)) 36 | 37 | transactions = [] 38 | for transaction in all_transactions: 39 | if self._is_expired(transaction): 40 | continue 41 | transactions.append(transaction) 42 | self._logger.info("获取到 %d 条有效委托", len(transactions)) 43 | 44 | for client_alias in self._client_aliases: 45 | client = self._client_aliases[client_alias] 46 | for tx in transactions: 47 | try: 48 | self._processed_transactions.append(tx) 49 | self._logger.info("开始在[%s(%s)]以 %f元 %s %d股 %s", 50 | client_alias, client, tx.price, tx.get_cn_action(), tx.amount, tx.symbol) 51 | self._shipane_client.execute(client, 52 | action=tx.action, 53 | symbol=tx.symbol, 54 | type='LIMIT', 55 | price=tx.price, 56 | amount=tx.amount) 57 | except HTTPError as e: 58 | self._logger.exception("下单异常") 59 | except Exception as e: 60 | self._logger.exception("跟单异常") 61 | self._logger.info("********** 结束跟单 **********\n") 62 | 63 | @property 64 | def name(self): 65 | return self._name 66 | 67 | def _is_expired(self, transaction): 68 | if transaction.completed_at < self._start_datatime: 69 | return True 70 | if transaction in self._processed_transactions: 71 | return True 72 | return False 73 | -------------------------------------------------------------------------------- /shipane_sdk/joinquant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/shipane_sdk/joinquant/__init__.py -------------------------------------------------------------------------------- /shipane_sdk/joinquant/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | import requests 6 | 7 | from shipane_sdk.base_quant_client import BaseQuantClient 8 | from shipane_sdk.joinquant.transaction import JoinQuantTransaction 9 | 10 | 11 | class JoinQuantClient(BaseQuantClient): 12 | BASE_URL = 'https://www.joinquant.com' 13 | 14 | def __init__(self, **kwargs): 15 | super(JoinQuantClient, self).__init__('JoinQuant') 16 | 17 | self._session = requests.Session() 18 | self._username = kwargs.pop('username') 19 | self._password = kwargs.pop('password') 20 | self._backtest_id = kwargs.pop('backtest_id') 21 | 22 | def login(self): 23 | self._session.headers = { 24 | 'Accept': 'application/json, text/javascript, */*; q=0.01', 25 | 'Accept-Encoding': 'gzip, deflate, br', 26 | 'Accept-Language': 'en-US,en;q=0.8', 27 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.100 Safari/537.36', 28 | 'Referer': '{}/user/login/index'.format(self.BASE_URL), 29 | 'X-Requested-With': 'XMLHttpRequest', 30 | 'Origin': self.BASE_URL, 31 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 32 | } 33 | self._session.get(self.BASE_URL) 34 | response = self._session.post('{}/user/login/doLogin?ajax=1'.format(self.BASE_URL), data={ 35 | 'CyLoginForm[username]': self._username, 36 | 'CyLoginForm[pwd]': self._password, 37 | 'ajax': 1 38 | }) 39 | self._session.headers.update({ 40 | 'cookie': response.headers['Set-Cookie'] 41 | }) 42 | 43 | super(JoinQuantClient, self).login() 44 | 45 | def query(self): 46 | today_str = datetime.today().strftime('%Y-%m-%d') 47 | response = self._session.get('{}/algorithm/live/transactionDetail'.format(self.BASE_URL), params={ 48 | 'backtestId': self._backtest_id, 49 | 'data': today_str, 50 | 'ajax': 1 51 | }) 52 | transaction_detail = response.json() 53 | raw_transactions = transaction_detail['data']['transaction'] 54 | transactions = [] 55 | for raw_transaction in raw_transactions: 56 | transaction = JoinQuantTransaction(raw_transaction).normalize() 57 | transactions.append(transaction) 58 | 59 | return transactions 60 | -------------------------------------------------------------------------------- /shipane_sdk/joinquant/executor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | 5 | try: 6 | from shipane_sdk.client import Client 7 | except: 8 | pass 9 | 10 | try: 11 | from kuanke.user_space_api import * 12 | except: 13 | pass 14 | 15 | 16 | class JoinQuantExecutor(object): 17 | def __init__(self, **kwargs): 18 | if 'log' in globals(): 19 | self._logger = log 20 | else: 21 | import logging 22 | self._logger = logging.getLogger() 23 | self._client = Client(self._logger, **kwargs) 24 | self._client_param = kwargs.get('client') 25 | self._order_id_map = dict() 26 | self._started_at = datetime.datetime.now() 27 | 28 | @property 29 | def client(self): 30 | return self._client 31 | 32 | @property 33 | def client(self): 34 | return self._client 35 | 36 | def execute(self, order): 37 | if order is None: 38 | self._logger.info('[实盘易] 委托为空,忽略下单请求') 39 | return 40 | 41 | if self.__is_expired(order): 42 | self._logger.info('[实盘易] 委托已过期,忽略下单请求') 43 | return 44 | 45 | try: 46 | action = 'BUY' if order.is_buy else 'SELL' 47 | actual_order = self._client.execute(self._client_param, 48 | action=action, 49 | symbol=order.security, 50 | price=order.price, 51 | amount=order.amount) 52 | self._order_id_map[order.order_id] = actual_order['id'] 53 | return actual_order 54 | except Exception as e: 55 | self._logger.error("[实盘易] 下单异常:" + str(e)) 56 | 57 | def cancel(self, order): 58 | if order is None: 59 | self._logger.info('[实盘易] 委托为空,忽略撤单请求') 60 | return 61 | 62 | try: 63 | order_id = order if isinstance(order, int) else order.order_id 64 | if order_id in self._order_id_map: 65 | self._client.cancel(self._client_param, self._order_id_map[order_id]) 66 | else: 67 | self._logger.warning('[实盘易] 未找到对应的委托编号') 68 | except Exception as e: 69 | self._logger.error("[实盘易] 撤单异常:" + str(e)) 70 | 71 | def __is_expired(self, order): 72 | return order.add_time < self._started_at 73 | -------------------------------------------------------------------------------- /shipane_sdk/joinquant/transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import re 4 | from datetime import datetime 5 | 6 | from shipane_sdk.transaction import Transaction 7 | 8 | 9 | class JoinQuantTransaction(object): 10 | def __init__(self, json): 11 | self.__dict__.update(json) 12 | 13 | def normalize(self): 14 | transaction = Transaction() 15 | transaction.completed_at = datetime.strptime('{} {}'.format(self.date, self.time), '%Y-%m-%d %H:%M') 16 | transaction.action = 'BUY' if self.transaction == u'买' else 'SELL' 17 | transaction.symbol = re.search(".*\\((\\d+)\\..*\\)", self.stock).group(1) 18 | transaction.price = self.price 19 | transaction.amount = int(re.search(u".*>[-]*(\\d+).*", self.amount, re.UNICODE).group(1)) 20 | return transaction 21 | -------------------------------------------------------------------------------- /shipane_sdk/market_utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | from datetime import time 5 | 6 | 7 | class MarketUtils(object): 8 | OPEN_TIME = time(9, 25) 9 | CLOSE_TIME = time(15) 10 | 11 | @classmethod 12 | def is_opening(cls, datetime_=None): 13 | if datetime_ is None: 14 | datetime_ = datetime.now() 15 | 16 | if datetime_.isoweekday() not in range(1, 6): 17 | return False 18 | if datetime_.time() <= cls.OPEN_TIME or datetime_.time() >= cls.CLOSE_TIME: 19 | return False 20 | return True 21 | 22 | @classmethod 23 | def is_closed(cls, datetime_=None): 24 | return not cls.is_opening(datetime_) 25 | -------------------------------------------------------------------------------- /shipane_sdk/ricequant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/shipane_sdk/ricequant/__init__.py -------------------------------------------------------------------------------- /shipane_sdk/ricequant/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from rqopen_client import RQOpenClient 4 | 5 | from shipane_sdk.base_quant_client import BaseQuantClient 6 | from shipane_sdk.ricequant.transaction import RiceQuantTransaction 7 | 8 | 9 | class RiceQuantClient(BaseQuantClient): 10 | def __init__(self, **kwargs): 11 | super(RiceQuantClient, self).__init__('RiceQuant') 12 | 13 | self._run_id = kwargs.pop('run_id') 14 | self._rq_client = RQOpenClient(kwargs.pop('username'), kwargs.pop('password')) 15 | 16 | def login(self): 17 | self._rq_client.login() 18 | super(RiceQuantClient, self).login() 19 | 20 | def query(self): 21 | response = self._rq_client.get_day_trades(self._run_id) 22 | raw_transactions = response['resp']['trades'] 23 | transactions = [] 24 | for raw_transaction in raw_transactions: 25 | transaction = RiceQuantTransaction(raw_transaction).normalize() 26 | transactions.append(transaction) 27 | 28 | return transactions 29 | -------------------------------------------------------------------------------- /shipane_sdk/ricequant/executor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import datetime 4 | 5 | try: 6 | from shipane_sdk.client import Client 7 | except: 8 | pass 9 | 10 | 11 | class RiceQuantExecutor(object): 12 | def __init__(self, **kwargs): 13 | try: 14 | self._logger = logger 15 | except NameError: 16 | import logging 17 | self._logger = logging.getLogger() 18 | self._client = Client(self._logger, **kwargs) 19 | self._client_param = kwargs.get('client') 20 | self._order_id_map = dict() 21 | self._started_at = datetime.datetime.now() 22 | 23 | @property 24 | def client(self): 25 | return self._client 26 | 27 | @property 28 | def client(self): 29 | return self._client 30 | 31 | def execute(self, order_id): 32 | if order_id is None: 33 | self._logger.info('[实盘易] 委托为空,忽略下单请求') 34 | return 35 | 36 | try: 37 | order = get_order(order_id) 38 | if order is None: 39 | self._logger.info('[实盘易] 委托为空,忽略下单请求') 40 | return 41 | 42 | if self.__is_expired(order): 43 | self._logger.info('[实盘易] 委托已过期,忽略下单请求') 44 | return 45 | 46 | price_type = 0 if order.type.name == 'LIMIT' else 4 47 | actual_order = self._client.execute(self._client_param, 48 | action=order.side.name, 49 | symbol=order.order_book_id, 50 | type=order.type.name, 51 | priceType=price_type, 52 | price=order.price, 53 | amount=order.quantity) 54 | self._order_id_map[order_id] = actual_order['id'] 55 | return actual_order 56 | except Exception as e: 57 | self._logger.error("[实盘易] 下单异常:" + str(e)) 58 | 59 | def cancel(self, order_id): 60 | if order_id is None: 61 | self._logger.info('[实盘易] 委托为空,忽略撤单请求') 62 | return 63 | 64 | try: 65 | if order_id in self._order_id_map: 66 | self._client.cancel(self._client_param, self._order_id_map[order_id]) 67 | else: 68 | self._logger.warning('[实盘易] 未找到对应的委托编号') 69 | except Exception as e: 70 | self._logger.error("[实盘易] 撤单异常:" + str(e)) 71 | 72 | def __is_expired(self, order): 73 | return order.datetime < self._started_at 74 | -------------------------------------------------------------------------------- /shipane_sdk/ricequant/transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from datetime import datetime 4 | 5 | from shipane_sdk.transaction import Transaction 6 | 7 | 8 | class RiceQuantTransaction(object): 9 | def __init__(self, json): 10 | self.__dict__.update(json) 11 | 12 | def normalize(self): 13 | transaction = Transaction() 14 | transaction.completed_at = datetime.strptime(self.time, '%Y-%m-%d %H:%M:%S') 15 | transaction.action = 'BUY' if self.quantity > 0 else 'SELL' 16 | transaction.symbol = self.order_book_id 17 | transaction.price = self.price 18 | transaction.amount = abs(self.quantity) 19 | return transaction 20 | -------------------------------------------------------------------------------- /shipane_sdk/scheduler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import codecs 3 | import collections 4 | import logging 5 | import os 6 | import os.path 7 | import time 8 | 9 | import six 10 | from apscheduler.schedulers.background import BackgroundScheduler 11 | from six.moves import configparser 12 | 13 | from shipane_sdk import Client 14 | from shipane_sdk.ap import APCronParser 15 | from shipane_sdk.jobs.new_stock_purchase import NewStockPurchaseJob 16 | from shipane_sdk.jobs.online_quant_following import OnlineQuantFollowingJob 17 | from shipane_sdk.joinquant.client import JoinQuantClient 18 | from shipane_sdk.ricequant.client import RiceQuantClient 19 | 20 | if six.PY2: 21 | ConfigParser = configparser.RawConfigParser 22 | else: 23 | ConfigParser = configparser.ConfigParser 24 | 25 | 26 | class Scheduler(object): 27 | def __init__(self): 28 | logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(levelname)-6s %(message)s') 29 | self._logger = logging.getLogger() 30 | 31 | config_path = os.path.join(os.path.expanduser('~'), '.shipane_sdk', 'config', 'scheduler.ini') 32 | self._logger.info('Config path: %s', config_path) 33 | self._config = ConfigParser() 34 | self._config.readfp(codecs.open(config_path, "r", "utf_8_sig")) 35 | 36 | self._client = Client(self._logger, 37 | host=self._config.get('ShiPanE', 'host'), 38 | port=self._config.get('ShiPanE', 'port'), 39 | key=self._config.get('ShiPanE', 'key')) 40 | self._jq_client = JoinQuantClient(username=self._config.get('JoinQuant', 'username'), 41 | password=self._config.get('JoinQuant', 'password'), 42 | backtest_id=self._config.get('JoinQuant', 'backtest_id')) 43 | self._rq_client = RiceQuantClient(username=self._config.get('RiceQuant', 'username'), 44 | password=self._config.get('RiceQuant', 'password'), 45 | run_id=self._config.get('RiceQuant', 'run_id')) 46 | 47 | self._new_stock_purchase_job = NewStockPurchaseJob(self._config, 48 | self._client, 49 | self.__filter_client_aliases('NewStocks')) 50 | self._jq_following_job = self.__create_following_job('JoinQuant') 51 | self._rq_following_job = self.__create_following_job('RiceQuant') 52 | 53 | def start(self): 54 | scheduler = BackgroundScheduler() 55 | 56 | if self._config.getboolean('NewStocks', 'enabled'): 57 | scheduler.add_job(self._new_stock_purchase_job, 58 | APCronParser.parse(self._config.get('NewStocks', 'schedule')), 59 | misfire_grace_time=None) 60 | else: 61 | self._logger.warning('New stock purchase job is not enabled') 62 | 63 | if self._config.getboolean('JoinQuant', 'enabled'): 64 | scheduler.add_job(self._jq_following_job, 65 | APCronParser.parse(self._config.get('JoinQuant', 'schedule')), 66 | name=self._jq_following_job.name, 67 | misfire_grace_time=None) 68 | else: 69 | self._logger.warning('JoinQuant following job is not enabled') 70 | 71 | if self._config.getboolean('RiceQuant', 'enabled'): 72 | scheduler.add_job(self._rq_following_job, 73 | APCronParser.parse(self._config.get('RiceQuant', 'schedule')), 74 | name=self._rq_following_job.name, 75 | misfire_grace_time=None) 76 | else: 77 | self._logger.warning('RiceQuant following job is not enabled') 78 | 79 | scheduler.start() 80 | print('Press Ctrl+{0} to exit'.format('Break' if os.name == 'nt' else 'C')) 81 | 82 | try: 83 | while True: 84 | time.sleep(1) 85 | except (KeyboardInterrupt, SystemExit): 86 | scheduler.shutdown() 87 | 88 | def __filter_client_aliases(self, section): 89 | all_client_aliases = dict(self._config.items('ClientAliases')) 90 | client_aliases = map(str.strip, self._config.get(section, 'clients').split(',')) 91 | return collections.OrderedDict( 92 | (client_alias, all_client_aliases[client_alias]) for client_alias in client_aliases) 93 | 94 | def __create_following_job(self, section): 95 | client_aliases = self.__filter_client_aliases(section) 96 | return OnlineQuantFollowingJob(self._client, self._jq_client, client_aliases, '{}FollowingJob'.format(section)) 97 | -------------------------------------------------------------------------------- /shipane_sdk/stock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import lxml.html.soupparser 4 | import pandas as pd 5 | import requests 6 | 7 | 8 | class StockUtils(object): 9 | @staticmethod 10 | def new_stocks(): 11 | url = 'http://vip.stock.finance.sina.com.cn/corp/view/vRPD_NewStockIssue.php?page=1&cngem=0&orderBy=NetDate&orderType=desc' 12 | request = requests.get(url) 13 | doc = lxml.html.soupparser.fromstring(request.content, features='html.parser') 14 | table = doc.cssselect('table#NewStockTable')[0] 15 | table.remove(table.cssselect('thead')[0]) 16 | table_html = lxml.html.etree.tostring(table).decode('utf-8') 17 | df = pd.read_html(table_html, skiprows=[0, 1])[0] 18 | df = df.select(lambda x: x in [0, 1, 2, 3, 7], axis=1) 19 | df.columns = ['code', 'xcode', 'name', 'ipo_date', 'price'] 20 | df['code'] = df['code'].map(lambda x: str(x).zfill(6)) 21 | df['xcode'] = df['xcode'].map(lambda x: str(x).zfill(6)) 22 | return df 23 | -------------------------------------------------------------------------------- /shipane_sdk/transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | 4 | class Transaction(object): 5 | def __init__(self, **kwargs): 6 | self._completed_at = kwargs.get('completed_at') 7 | self._action = kwargs.get('action') 8 | self._symbol = kwargs.get('symbol') 9 | self._price = kwargs.get('price') 10 | self._amount = kwargs.get('amount') 11 | 12 | def __eq__(self, other): 13 | if self.completed_at != other.completed_at: 14 | return False 15 | if self.action != other.action: 16 | return False 17 | if self.symbol != other.symbol: 18 | return False 19 | if self.price != other.price: 20 | return False 21 | if self.amount != other.amount: 22 | return False 23 | return True 24 | 25 | def get_cn_action(self): 26 | return u'买入' if self.action == 'BUY' else u'卖出' 27 | 28 | @property 29 | def completed_at(self): 30 | return self._completed_at 31 | 32 | @completed_at.setter 33 | def completed_at(self, value): 34 | self._completed_at = value 35 | 36 | @property 37 | def action(self): 38 | return self._action 39 | 40 | @action.setter 41 | def action(self, value): 42 | self._action = value 43 | 44 | @property 45 | def symbol(self): 46 | return self._symbol 47 | 48 | @symbol.setter 49 | def symbol(self, value): 50 | self._symbol = value 51 | 52 | @property 53 | def price(self): 54 | return self._price 55 | 56 | @price.setter 57 | def price(self, value): 58 | self._price = value 59 | 60 | @property 61 | def amount(self): 62 | return self._amount 63 | 64 | @amount.setter 65 | def amount(self, value): 66 | self._amount = value 67 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/tests/__init__.py -------------------------------------------------------------------------------- /tests/config/config.ini.template: -------------------------------------------------------------------------------- 1 | [ShiPanE] 2 | host=localhost 3 | port=8888 4 | 5 | [JoinQuant] 6 | username= 7 | password= 8 | backtestId= 9 | -------------------------------------------------------------------------------- /tests/sample_data/rq_client-response.json: -------------------------------------------------------------------------------- 1 | {"code": 200, "resp": {"name": "SVM大法好", 2 | "trades": [{"order_book_id": "600216.XSHG", 3 | "price": 12.77, 4 | "quantity": -100.0, 5 | "time": "2016-12-23 09:32:00", 6 | "trade_id": "2", 7 | "transaction_cost": 6.28}]}} 8 | -------------------------------------------------------------------------------- /tests/sample_data/transactionDetail.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "transaction": [ 4 | { 5 | "date": "2016-11-22", 6 | "time": "09:30", 7 | "security": "股票", 8 | "stock": "平安银行(000001.XSHE)", 9 | "transaction": "买", 10 | "type": "市价单", 11 | "amount": "100股", 12 | "price": 9.25, 13 | "total": 925, 14 | "gains": 0, 15 | "commission": 5, 16 | "status": "全部成交" 17 | }, 18 | { 19 | "date": "2016-11-22", 20 | "time": "09:30", 21 | "security": "股票", 22 | "stock": "平安银行(000001.XSHE)", 23 | "transaction": "卖", 24 | "type": "市价单", 25 | "amount": "-100股", 26 | "price": 9.23, 27 | "total": "(923)", 28 | "gains": -0.01, 29 | "commission": 5.92, 30 | "status": "全部成交" 31 | }, 32 | { 33 | "date": "2016-11-22", 34 | "time": "09:31", 35 | "security": "股票", 36 | "stock": "平安银行(000001.XSHE)", 37 | "transaction": "买", 38 | "type": "市价单", 39 | "amount": "100股", 40 | "price": 9.25, 41 | "total": 925, 42 | "gains": 0, 43 | "commission": 5, 44 | "status": "全部成交" 45 | } 46 | ] 47 | }, 48 | "status": "0", 49 | "code": "00000", 50 | "msg": "" 51 | } 52 | -------------------------------------------------------------------------------- /tests/shipane_sdk/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/tests/shipane_sdk/__init__.py -------------------------------------------------------------------------------- /tests/shipane_sdk/joinquant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/tests/shipane_sdk/joinquant/__init__.py -------------------------------------------------------------------------------- /tests/shipane_sdk/joinquant/test_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import unittest 5 | 6 | import six 7 | from six.moves import configparser 8 | 9 | if six.PY2: 10 | ConfigParser = configparser.RawConfigParser 11 | else: 12 | ConfigParser = configparser.ConfigParser 13 | 14 | from shipane_sdk.joinquant.client import JoinQuantClient 15 | 16 | 17 | class JoinQuantClientTest(unittest.TestCase): 18 | def setUp(self): 19 | config = ConfigParser() 20 | dir_path = os.path.dirname(os.path.realpath(__file__)) 21 | config.read('{}/../../config/config.ini'.format(dir_path)) 22 | self._jq_client = JoinQuantClient(username=config.get('JoinQuant', 'username'), 23 | password=config.get('JoinQuant', 'password'), 24 | backtest_id=config.get('JoinQuant', 'backtestId')) 25 | 26 | def test_query(self): 27 | self._jq_client.login() 28 | transactions = self._jq_client.query() 29 | self.assertTrue(isinstance(transactions, list)) 30 | -------------------------------------------------------------------------------- /tests/shipane_sdk/joinquant/test_executor.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import collections 4 | import inspect 5 | import logging 6 | import os 7 | import unittest 8 | 9 | import six 10 | from six.moves import configparser 11 | 12 | if six.PY2: 13 | ConfigParser = configparser.RawConfigParser 14 | else: 15 | ConfigParser = configparser.ConfigParser 16 | 17 | from shipane_sdk import JoinQuantExecutor 18 | 19 | 20 | class JoinQuantExecutorTest(unittest.TestCase): 21 | def setUp(self): 22 | logging.basicConfig(level=logging.INFO) 23 | 24 | config = ConfigParser() 25 | dir_path = os.path.dirname(os.path.realpath(__file__)) 26 | config.read('{}/../../config/config.ini'.format(dir_path)) 27 | self.Order = collections.namedtuple('Order', ['is_buy', 'security', 'price', 'amount']) 28 | self.executor = JoinQuantExecutor(host=config.get('ShiPanE', 'host')) 29 | 30 | def test_buy_stock(self): 31 | mock_order = self.Order 32 | mock_order.is_buy = True 33 | mock_order.order_id = 1 34 | mock_order.security = '000001.XSHE' 35 | mock_order.price = 11.11 36 | mock_order.amount = 100 37 | response = self.executor.execute(mock_order) 38 | print(inspect.stack()[0][3] + ' - ' + response.text) 39 | json = response.json(); 40 | if response.status_code == 200: 41 | self.assertTrue(json['id']) 42 | elif response.status_code == 400: 43 | self.assertTrue(json['message']) 44 | else: 45 | self.fail() 46 | 47 | def test_sell_stock(self): 48 | mock_order = self.Order 49 | mock_order.is_buy = False 50 | mock_order.order_id = 2 51 | mock_order.security = '000001.XSHE' 52 | mock_order.price = 11.11 53 | mock_order.amount = 100 54 | response = self.executor.execute(mock_order) 55 | print(inspect.stack()[0][3] + ' - ' + response.text) 56 | json = response.json(); 57 | if response.status_code == 200: 58 | self.assertTrue(json['id']) 59 | elif response.status_code == 400: 60 | self.assertTrue(json['message']) 61 | else: 62 | self.fail() 63 | 64 | def test_cancel(self): 65 | mock_order = self.Order 66 | mock_order.is_buy = True 67 | mock_order.order_id = 3 68 | mock_order.security = '000001.XSHE' 69 | mock_order.price = 9.01 70 | mock_order.amount = 100 71 | self.executor.execute(mock_order) 72 | 73 | response = self.executor.cancel(mock_order) 74 | print(inspect.stack()[0][3] + ' - ' + response.text) 75 | if response.status_code != 200: 76 | self.fail() 77 | -------------------------------------------------------------------------------- /tests/shipane_sdk/joinquant/test_transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import os 4 | import unittest 5 | from datetime import datetime 6 | 7 | from shipane_sdk.joinquant.transaction import JoinQuantTransaction 8 | 9 | 10 | class TransactionTest(unittest.TestCase): 11 | def setUp(self): 12 | dir_path = os.path.dirname(os.path.realpath(__file__)) 13 | with open('{}/../../sample_data/transactionDetail.json'.format(dir_path)) as data_file: 14 | self._transaction_detail = json.loads(data_file.read()) 15 | 16 | def test_from_raw(self): 17 | jq_transaction = JoinQuantTransaction(self._transaction_detail['data']['transaction'][0]) 18 | transaction = jq_transaction.normalize() 19 | self.assertEqual(transaction.completed_at, datetime.strptime('2016-11-22 09:30', '%Y-%m-%d %H:%M')) 20 | self.assertEqual(transaction.action, 'BUY') 21 | self.assertEqual(transaction.symbol, '000001') 22 | self.assertEqual(transaction.price, 9.25) 23 | self.assertEqual(transaction.amount, 100) 24 | -------------------------------------------------------------------------------- /tests/shipane_sdk/ricequant/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lijielife/ShiPanE-Python-SDK-1/999dc30823efba697c9ee1a421f9cad8ef4a291e/tests/shipane_sdk/ricequant/__init__.py -------------------------------------------------------------------------------- /tests/shipane_sdk/ricequant/test_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import unittest 5 | 6 | import six 7 | from six.moves import configparser 8 | 9 | if six.PY2: 10 | ConfigParser = configparser.RawConfigParser 11 | else: 12 | ConfigParser = configparser.ConfigParser 13 | 14 | from shipane_sdk.ricequant.client import RiceQuantClient 15 | 16 | 17 | class RiceQuantClientTest(unittest.TestCase): 18 | def setUp(self): 19 | config = ConfigParser() 20 | dir_path = os.path.dirname(os.path.realpath(__file__)) 21 | config.read('{}/../../config/config.ini'.format(dir_path)) 22 | self._rq_client = RiceQuantClient(username=config.get('RiceQuant', 'username'), 23 | password=config.get('RiceQuant', 'password'), 24 | run_id=config.get('RiceQuant', 'run_id')) 25 | 26 | def test_query(self): 27 | self._rq_client.login() 28 | transactions = self._rq_client.query() 29 | self.assertTrue(isinstance(transactions, list)) 30 | -------------------------------------------------------------------------------- /tests/shipane_sdk/ricequant/test_transaction.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json 3 | import os 4 | import unittest 5 | from datetime import datetime 6 | 7 | from shipane_sdk.ricequant.transaction import RiceQuantTransaction 8 | 9 | 10 | class TransactionTest(unittest.TestCase): 11 | def setUp(self): 12 | dir_path = os.path.dirname(os.path.realpath(__file__)) 13 | with open('{}/../../sample_data/rq_client-response.json'.format(dir_path)) as data_file: 14 | self._transaction_detail = json.loads(data_file.read()) 15 | 16 | def test_from_raw(self): 17 | rq_transaction = RiceQuantTransaction(self._transaction_detail['resp']['trades'][0]) 18 | transaction = rq_transaction.normalize() 19 | self.assertEqual(transaction.completed_at, datetime.strptime('2016-12-23 09:32:00', '%Y-%m-%d %H:%M:%S')) 20 | self.assertEqual(transaction.action, 'SELL') 21 | self.assertEqual(transaction.symbol, '600216.XSHG') 22 | self.assertEqual(transaction.price, 12.77) 23 | self.assertEqual(transaction.amount, 100) 24 | -------------------------------------------------------------------------------- /tests/shipane_sdk/test_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import logging 4 | import os 5 | import unittest 6 | 7 | import six 8 | from requests import HTTPError 9 | from six.moves import configparser 10 | 11 | from shipane_sdk import Client 12 | 13 | if six.PY2: 14 | ConfigParser = configparser.RawConfigParser 15 | else: 16 | ConfigParser = configparser.ConfigParser 17 | 18 | 19 | class ClientTest(unittest.TestCase): 20 | def setUp(self): 21 | logging.basicConfig(level=logging.DEBUG) 22 | config = ConfigParser() 23 | dir_path = os.path.dirname(os.path.realpath(__file__)) 24 | config.read('{}/../config/config.ini'.format(dir_path)) 25 | self.client = Client(logging.getLogger(), host=config.get('ShiPanE', 'host')) 26 | 27 | def test_get_account(self): 28 | try: 29 | self.client.get_account() 30 | except HTTPError as e: 31 | self.fail() 32 | 33 | def test_get_positions(self): 34 | try: 35 | data = self.client.get_positions() 36 | sub_accounts = data['sub_accounts'] 37 | self.assertGreater(sub_accounts['总资产']['人民币'], 0) 38 | positions = data['positions'] 39 | self.assertIsNotNone(positions['证券代码'][0]) 40 | except HTTPError as e: 41 | self.fail() 42 | 43 | def test_buy_stock(self): 44 | try: 45 | order = self.client.buy(symbol='000001', price=8.11, amount=100) 46 | self.assertIsNotNone(order['id']) 47 | except HTTPError as e: 48 | result = e.response.json() 49 | self.assertIsNotNone(result['message']) 50 | 51 | def test_sell_stock(self): 52 | try: 53 | order = self.client.sell(symbol='000001', price=9.51, amount=100) 54 | self.assertIsNotNone(order['id']) 55 | except HTTPError as e: 56 | result = e.response.json() 57 | self.assertIsNotNone(result['message']) 58 | 59 | def test_cancel_all(self): 60 | try: 61 | self.client.cancel_all() 62 | except HTTPError as e: 63 | self.fail() 64 | 65 | def test_query(self): 66 | try: 67 | df = self.client.query(None, '查询>资金股份') 68 | self.assertIsNotNone(df['证券代码'][0]) 69 | except HTTPError as e: 70 | self.fail() 71 | -------------------------------------------------------------------------------- /tests/shipane_sdk/test_stock.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import unittest 4 | 5 | from shipane_sdk.stock import StockUtils 6 | 7 | 8 | class StockUtilsTest(unittest.TestCase): 9 | def test_new_stocks(self): 10 | df = StockUtils.new_stocks() 11 | self.assertTrue((df.columns == ['code', 'xcode', 'name', 'ipo_date', 'price']).all()) 12 | --------------------------------------------------------------------------------