├── .gitignore ├── MANIFEST.in ├── README.md ├── requirements.txt ├── rqopen_client ├── __init__.py └── client.py └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | config.py 3 | dist/ 4 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include README.md 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RQOpen-Client - RQOpen客户端 2 | 3 | ## 简介 4 | 通过这个简单的python库,你可以拿到你在 www.ricequant.com 运行的实盘、模拟交易或回测的运行数据。包含交易列表,仓位信息,投资组合信息。 5 | 6 | RQOpen 获取到的信号,会比系统时间稍微延迟一些。Ricequant Gateway 在切分钟线的时候会延迟10多秒,因为数据源的问题。而 RQAlpha 产生信号后,保存在数据库中。由 RQOpen 轮训,轮训的间隔,也是信号的延迟时间。 7 | 8 | ## 安装 9 | ``` 10 | pip install rqopen-client 11 | ``` 12 | 13 | ## 简单的范例 14 | ``` python 15 | from pprint import pprint 16 | from rqopen_client import RQOpenClient 17 | 18 | username = "your ricequant username" 19 | password = "your ricequant password" 20 | run_id = your_run_id 21 | 22 | client = RQOpenClient(username, password) 23 | # client = RQOpenClient(username, password, return_df=Fales) 24 | 25 | pprint(client.trades(run_id)) 26 | pprint(client.current_positions(run_id)) 27 | ``` 28 | 上面代码的一些展示结果(随便一个范例): 29 | ### 初始化&登录: RQOpenClient(username, password, return_df=True) 30 | ``` 31 | :param username: 登录账户 32 | :param password: 密码 33 | :param logger: 日志 34 | :param log_level: 日志级别 35 | :param base_url: 服务地址,默认web端 rqpro2.0需要单独配置 36 | :param timeout: 超时时间 37 | :param return_df: 返回数据是否为DataFrame False返回dict 38 | ``` 39 | 40 | ### 查询当日交易记录: current_trades(run_id) 41 | 42 | 返回示例 - 今天没有任何下单: 43 | ``` 44 | # return_df=False 45 | {'code': 200, 46 | 'resp': {'name': '改进版小盘股 2016-08-28 16:15:06', 'run_id': 968101, 'trades': []}} 47 | 48 | # return_df=True 49 | Empty DataFrame 50 | Columns: [] 51 | Index: [] 52 | ``` 53 | 54 | 返回示例 - 当日有交易: 55 | ``` 56 | # return_df=False 57 | {'code': 200, 58 | 'resp': {'data': [{'commission': 5.0, 59 | 'date': '2019-01-04 09:31:00', 60 | 'exec_id': '15641121150000', 61 | 'last_price': 9.29, 62 | 'last_quantity': 100.0, 63 | 'order_book_id': '000001.XSHE', 64 | 'order_id': 15641121140000, 65 | 'position_effect': 'OPEN', 66 | 'side': 'BUY', 67 | 'tax': 0.0, 68 | 'trade_id': '15641121150000', 69 | 'transaction_cost': 5.0}, 70 | {'commission': 5.0, 71 | 'date': '2019-01-04 09:31:00', 72 | 'exec_id': '15641121150002', 73 | 'last_price': 16.29, 74 | 'last_quantity': 100.0, 75 | 'order_book_id': '000004.XSHE', 76 | 'order_id': 15641121140002, 77 | 'position_effect': 'OPEN', 78 | 'side': 'BUY', 79 | 'tax': 0.0, 80 | 'trade_id': '15641121150002', 81 | 'transaction_cost': 5.0}], 82 | 'name': 'SVM大法好', 83 | 'run_id': 6281}} 84 | 85 | # return_df=True 86 | commission ... transaction_cost 87 | date ... 88 | 2018-05-04 09:31:00 8.568 ... 8.568 89 | 2018-05-04 09:32:00 8.608 ... 8.608 90 | 2018-05-04 09:33:00 8.592 ... 8.592 91 | 2018-05-04 09:34:00 8.600 ... 8.600 92 | 2018-05-04 09:35:00 8.592 ... 8.592 93 | ``` 94 | 95 | ### 查询最新持仓: current_positions(run_id) 96 | 97 | 返回示 - 有持仓: 98 | ``` 99 | # return_df=False 100 | {'code': 200, 101 | 'resp': {'name': 'SVM大法好', 102 | 'run_id': 6281, 103 | 'positions': [{'order_book_id': '000001.XSHE', 104 | 'last_price': 13.86, 105 | 'quantity': 5062.0}, 106 | {'order_book_id': '000002.XSHE', 'last_price': 32.22, 'quantity': 4939.0}, 107 | {'order_book_id': '000004.XSHE', 'last_price': 21.67, 'quantity': 4341.0}, 108 | {'order_book_id': '000005.XSHE', 'last_price': 3.97, 'quantity': 4903.0}}]}} 109 | 110 | # return_df=True 111 | last_price order_book_id quantity 112 | 0 13.86 000001.XSHE 5062.0 113 | 1 32.22 000002.XSHE 4939.0 114 | 2 21.67 000004.XSHE 4341.0 115 | 3 3.97 000005.XSHE 4903.0 116 | 4 7.13 000006.XSHE 5011.0 117 | 5 7.57 000007.XSHE 4955.0 118 | 6 4.97 000008.XSHE 4906.0 119 | 7 7.19 000009.XSHE 4995.0 120 | 8 3.76 000010.XSHE 4996.0 121 | 9 12.19 000011.XSHE 4931.0 122 | 10 5.81 000012.XSHE 4984.0 123 | 11 12.75 000014.XSHE 4974.0 124 | ``` 125 | 126 | ### 查询全部交易: trades(run_id) 127 | 返回示 - 有交易: 128 | ``` 129 | # return_df=False 130 | {'code': 200, 131 | 'resp': {'data': [{'commission': 5.0, 132 | 'date': '2019-01-07 11:05:00', 133 | 'exec_id': '15641121150040', 134 | 'last_price': 3.36, 135 | 'last_quantity': 1.0, 136 | 'order_book_id': '000010.XSHE', 137 | 'order_id': 15641121140202, 138 | 'position_effect': 'CLOSE', 139 | 'side': 'SELL', 140 | 'tax': 0.0033599999999998076, 141 | 'trade_id': '15641121150040', 142 | 'transaction_cost': 5.00336}, 143 | 'name': '横琴比赛', 144 | 'run_id': 6281}} 145 | 146 | # return_df=True 147 | commission ... transaction_cost 148 | date ... 149 | 2019-01-04 09:31:00 5.0 ... 5.00000 150 | 2019-01-04 09:31:00 5.0 ... 5.00000 151 | ... ... ... 152 | 2019-04-04 14:44:00 5.0 ... 5.10176 153 | 2019-04-04 14:54:00 5.0 ... 5.06417 154 | [3102 rows x 11 columns] 155 | 156 | ``` 157 | 158 | 159 | ### 查询全部持仓: positions(run_id) 160 | 返回示 - 有持仓: 161 | ``` 162 | # return_df=False 163 | {'code': 200, 164 | 'resp': {'data': [{'000001.XSHE': 975.0, # market_value 165 | '000002.XSHE': 2493.0, 166 | '000004.XSHE': 1660.0000000000002, 167 | '000005.XSHE': 275.0, 168 | '000006.XSHE': 526.0, 169 | '000007.XSHE': 800.0, 170 | '000008.XSHE': 391.0, 171 | '000009.XSHE': 437.0, 172 | '000010.XSHE': 332.0, 173 | '000011.XSHE': 956.0, 174 | '000012.XSHE': 409.99999999999994, 175 | '000014.XSHE': 969.0, 176 | 'cash': 1990076.0, 177 | 'date': '2019-01-04 00:00:00'}, 178 | 'name': '横琴比赛', 179 | 'run_id': 6281}} 180 | 181 | # return_df=True 182 | 000001.XSHE 000002.XSHE ... 000014.XSHE cash 183 | date ... 184 | 2019-01-04 975.00 2493.00 ... 969.00 1.990076e+06 185 | 2019-01-07 1801.90 4534.05 ... 1664.99 1.981082e+06 186 | 2019-01-08 2675.82 6950.00 ... 2388.66 1.972075e+06 187 | 2019-01-09 3618.16 9144.13 ... 3246.15 1.963209e+06 188 | [4rows x 13 columns] 189 | ``` 190 | 191 | ### 查询投资组合: portfolio(run_id) 192 | ``` 193 | # return_df=False 194 | {'code': 200, 195 | 'resp': {'data': [{'annualized_returns': 0.038520563489603976, 196 | 'benchmark_daily_returns': 0.023957447956719332, 197 | 'benchmark_total_returns': 0.023957447956719332, 198 | 'cash': 1990076.0, 199 | 'daily_pnl': 300.000000000189, 200 | 'daily_returns': 0.0001500000000000945, 201 | 'date': '2019-01-04 00:00:00', 202 | 'market_value': 10224.0, 203 | 'portfolio_value': 2000300.0, 204 | 'starting_cash': 2000000.0, 205 | 'total_returns': 0.0001500000000000945, 206 | 'transaction_cost': 60.0},], 207 | 'name': '横琴比赛', 208 | 'run_id': 6281}} 209 | 210 | # return_df=True 211 | annualized_returns ... transaction_cost 212 | date ... 213 | 2019-01-04 0.038521 ... 60.00000 214 | 2019-01-07 0.022092 ... 226.54735 215 | 2019-01-08 -0.005048 ... 251.59331 216 | 2019-01-09 -0.004138 ... 266.77119= 217 | [4 rows x 11 columns] 218 | ``` -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | pandas -------------------------------------------------------------------------------- /rqopen_client/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | 5 | from .client import RQOpenClient 6 | 7 | 8 | __all__ = [ 9 | "RQOpenClient", 10 | ] 11 | -------------------------------------------------------------------------------- /rqopen_client/client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import datetime 4 | import warnings 5 | from functools import wraps 6 | import re 7 | import logging 8 | import pandas as pd 9 | import requests 10 | 11 | 12 | def return_df(field="data"): 13 | """return DataFrame data""" 14 | 15 | def decorator(func): 16 | @wraps(func) 17 | def wrapper(self, *args, **kwargs): 18 | resp = func(self, *args, **kwargs) 19 | if resp.get("code") == 200 and self.return_df is True: 20 | df = pd.DataFrame(resp["resp"][field]) 21 | if "date" in df.columns: 22 | df['date'] = df['date'].apply(lambda x: datetime.datetime.strptime(x, "%Y-%m-%d %H:%M:%S")) 23 | df = df.set_index("date") 24 | return df 25 | return resp 26 | 27 | return wrapper 28 | 29 | return decorator 30 | 31 | 32 | class RQOpenClient(object): 33 | def __init__(self, username, password, logger=None, log_level=logging.DEBUG, 34 | base_url="https://rqopen.ricequant.com", timeout=(5, 10), return_df=True): 35 | """ 36 | :param username: 登录账户 37 | :param password: 密码 38 | :param logger: 日志 39 | :param log_level: 日志级别 40 | :param base_url: 服务地址,默认web端 rqpro2.0需要单独配置 41 | :param timeout: 超时时间 42 | :param return_df: 返回数据是否为DataFrame False返回dict 43 | """ 44 | self.base_url = base_url 45 | # tel number need "+86" 46 | if re.match(r'^[1]([3-9])[0-9]{9}$', username): 47 | username = "+86" + username 48 | self.username = username 49 | self.password = password 50 | self.client = requests.Session() 51 | self.logger = logger if logger else logging.getLogger("RQOpenClient") 52 | self.logger.setLevel(log_level) 53 | self.timeout = timeout 54 | self.return_df = return_df 55 | 56 | def login(self): 57 | self.logger.info("Try login. Username {}".format(self.username)) 58 | resp = self.client.post("{}/login".format(self.base_url), 59 | {"username": self.username, "password": self.password}, timeout=self.timeout) 60 | ret = resp.json() 61 | self.logger.info("Login response {}".format(ret)) 62 | return ret 63 | 64 | def _do(self, func, *args, **kwargs): 65 | resp = func(*args, **kwargs) 66 | if resp["code"] == 401: 67 | login_resp = self.login() 68 | if login_resp["code"] == 200: 69 | self.logger.info("login success") 70 | else: 71 | return login_resp 72 | elif resp["code"] == 200: 73 | return resp 74 | resp = func(*args, **kwargs) 75 | return resp 76 | 77 | def get_day_trades(self, run_id): 78 | warnings.warn("get_day_trades will be abandoned, please use current_trades", DeprecationWarning) 79 | return self._do(self._get_day_trades, run_id) 80 | 81 | def get_positions(self, run_id): 82 | warnings.warn("current_positions will be abandoned, please use current_positions", DeprecationWarning) 83 | return self._do(self._get_positions, run_id) 84 | 85 | def _get_day_trades(self, run_id): 86 | resp = self.client.get("{}/pt/load_day_trades/{}".format(self.base_url, run_id), timeout=self.timeout) 87 | return resp.json() 88 | 89 | def _get_positions(self, run_id): 90 | resp = self.client.get("{}/pt/load_current_positions/{}".format(self.base_url, run_id), timeout=self.timeout) 91 | return resp.json() 92 | 93 | # base 94 | @return_df() 95 | def trades(self, run_id): 96 | """get all trades""" 97 | return self._do(self._get_base, "trades", run_id) 98 | 99 | @return_df() 100 | def positions(self, run_id): 101 | """get all positions (market_value)""" 102 | return self._do(self._get_base, "positions", run_id) 103 | 104 | @return_df() 105 | def portfolio(self, run_id): 106 | """get all portfolio""" 107 | return self._do(self._get_base, "portfolio", run_id) 108 | 109 | @return_df("positions") 110 | def current_positions(self, run_id): 111 | """get current positions""" 112 | return self._do(self._get_base, "pt/load_current_positions", run_id) 113 | 114 | @return_df("trades") 115 | def current_trades(self, run_id): 116 | """get current positions""" 117 | return self._do(self._get_base, "pt/load_day_trades", run_id) 118 | 119 | def _get_base(self, api_path, run_id): 120 | resp = self.client.get("{}/{}/{}".format(self.base_url, api_path, run_id), timeout=self.timeout) 121 | return resp.json() 122 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | 5 | from setuptools import ( 6 | find_packages, 7 | setup, 8 | ) 9 | 10 | try: # for pip >= 10 11 | from pip._internal.req import parse_requirements 12 | except ImportError: # for pip <= 9.0.3 13 | from pip.req import parse_requirements 14 | 15 | setup( 16 | name='rqopen-client', 17 | version='0.0.6', 18 | description='rqopen-client', 19 | packages=find_packages(exclude=[]), 20 | author='ricequant', 21 | author_email='public@ricequant.com', 22 | url="https://github.com/ricequant/rqopen-client", 23 | package_data={'': ['*.*']}, 24 | install_requires=[ 25 | str(ir.req) 26 | for ir in parse_requirements("requirements.txt", session=False) 27 | ], 28 | zip_safe=False, 29 | ) 30 | --------------------------------------------------------------------------------