Skip to content

Commit

Permalink
Merge 75248bd into 93ec38f
Browse files Browse the repository at this point in the history
  • Loading branch information
Cuizi7 committed Mar 6, 2017
2 parents 93ec38f + 75248bd commit d7640f6
Show file tree
Hide file tree
Showing 19 changed files with 205 additions and 57 deletions.
90 changes: 90 additions & 0 deletions docs/source/development/mod.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Hello World
扩展 RQAlpha API
================

如果你想为 RQAlpha 创建自己的 API,你也可以通过 Mod 来注册新的 API。在内建的 mod 中,有一个 FuncatAPIMod ,将通达信、同花顺的公式表达能力移植到 Python 中,扩展了 RQAlpha 的 API。

其中的关键点,是通过了 :code:`register_api` 来注册 API。
Expand Down Expand Up @@ -82,3 +83,92 @@ Hello World
def tear_down(self, code, exception=None):
pass
以独立 Pypi 包作为 Mod
================

RQAlpha 支持安装、卸载、启用、停止第三方Mod。

.. code-block:: bash
# 以名为 "xxx" 的 Mod 为例,介绍RQAlpha 第三方Mod的使用
# 安装
$ rqalpha install xxx
# 卸载
$ rqalpha uninstall xxx
# 启用
$ rqalpha enable xxx
# 关闭
$ rqalpha disable xxx
如果您希望发布自己的Mod并被 RQAlpha 的用户使用,只需要遵循简单的约定即可。

下面为一个 RQAlpha Mod 的模板:

.. code-block:: python3
from rqalpha.interface import AbstractMod
class XXXMod(AbstractMod):
def __init__(self):
pass
def start_up(self, env, mod_config):
pass
def tear_down(self, code, exception=None):
pass
def load_mod():
return XXXMod()
__mod_config__ = """
param1: "111"
param2: "222"
"""
约定如下:

1. 需要定义并实现 :code:`load_mod` 函数, 其返回值为对应的继承自 :code:`AbstractMod` 的类,并且 :code:`load_mod` 所在文件必须按照 :code:`rqalpha_mod_xxx` 规则进行命名。
2. 如果有自定义参数的话,需要实现 :code:`__mod_config__` 变量,其为字符串,配置的具体格式为 `yaml` 格式(支持注释)。RQAlpha 会自动将其扩展到默认配置项中。
3. 当写好 Mod 以后,需要发布到 Pypi 仓库中,并且包名需要如下格式: :code:`rqalpha-mod-xxx`,一下的 setup.py 文件可作参考。

.. code-block:: python3
from pip.req import parse_requirements
from setuptools import (
find_packages,
setup,
)
setup(
name='rqalpha-mod-xxx',
version="0.1.0",
description='RQAlpha Mod XXX',
packages=find_packages(exclude=[]),
author='',
author_email='',
license='Apache License v2',
package_data={'': ['*.*']},
url='',
install_requires=[str(ir.req) for ir in parse_requirements("requirements.txt", session=False)],
zip_safe=False,
classifiers=[
'Programming Language :: Python',
'Operating System :: Microsoft :: Windows',
'Operating System :: Unix',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],
)
按此编写好 Mod 并发布到 Pypi 上以后,就可以直接使用RQAlpha的命令来安装和启用该Mod了。

如果您希望更多人使用您的Mod,您也可以联系我们,我们审核通过后,会在 RQAlpha 项目介绍和文档中增加您的Mod的介绍和推荐。
17 changes: 10 additions & 7 deletions rqalpha/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ def update_bundle(data_bundle_path, locale):

@cli.command()
@click.help_option('-h', '--help')
# -- Base Configuration
@click.option('-d', '--data-bundle-path', 'base__data_bundle_path', type=click.Path(exists=True))
@click.option('-f', '--strategy-file', 'base__strategy_file', type=click.Path(exists=True))
@click.option('-s', '--start-date', 'base__start_date', type=Date())
Expand All @@ -65,25 +66,27 @@ def update_bundle(data_bundle_path, locale):
@click.option('-me', '--match-engine', 'base__matching_type', type=click.Choice(['current_bar', 'next_bar']))
@click.option('-rt', '--run-type', 'base__run_type', type=click.Choice(['b', 'p']), default="b")
@click.option('--resume', 'base__resume_mode', is_flag=True)
@click.option('--handle-split/--not-handle-split', 'base__handle_split', default=None, help="handle split")
# -- Extra Configuration
@click.option('-l', '--log-level', 'extra__log_level', type=click.Choice(['verbose', 'debug', 'info', 'error', 'none']))
@click.option('--locale', 'extra__locale', type=click.Choice(['cn', 'en']), default="cn")
@click.option('--handle-split/--not-handle-split', 'base__handle_split', default=None, help="handle split")
@click.option('--disable-user-system-log', 'extra__user_system_log_disabled', is_flag=True, help='disable user system log')
@click.option('--extra-vars', 'extra__context_vars', type=click.STRING, help="override context vars")
@click.option("--enable-profiler", "extra__enable_profiler", is_flag=True,
help="add line profiler to profile your strategy")
@click.option('--config', 'config_path', type=click.STRING, help="config file path")
# -- Mod Configuration
@click.option('-mc', '--mod-config', 'mod_configs', nargs=2, multiple=True, type=click.STRING, help="mod extra config")
@click.option('-p', '--plot/--no-plot', 'mod__analyser__plot', default=None, help="plot result")
@click.option('--plot-save', 'mod__analyser__plot_save_file', default=None, help="save plot to file")
@click.option('--report', 'mod__analyser__report_save_path', type=click.Path(writable=True), help="save report")
@click.option('-o', '--output-file', 'mod__analyser__output_file', type=click.Path(writable=True),
help="output result pickle file")
@click.option('--progress/--no-progress', 'mod__progress__enabled', default=None, help="show progress bar")
@click.option('--extra-vars', 'extra__context_vars', type=click.STRING, help="override context vars")
@click.option("--enable-profiler", "extra__enable_profiler", is_flag=True,
help="add line profiler to profile your strategy")
@click.option('--config', 'config_path', type=click.STRING, help="config file path")
@click.option('-mc', '--mod-config', 'mod_configs', nargs=2, multiple=True, type=click.STRING, help="mod extra config")
@click.option('--short-stock', 'mod__risk_manager__short_stock', is_flag=True, help="enable stock shorting")
# -- DEPRECATED ARGS && WILL BE REMOVED AFTER VERSION 1.0.0
@click.option('-i', '--init-cash', 'base__stock_starting_cash', type=click.FLOAT)
@click.option('-k', '--kind', 'base__strategy_type', type=click.Choice(['stock', 'future', 'stock_future']))
# -- DEPRECATED ARGS && WILL BE REMOVED AFTER VERSION 1.0.0
def run(**kwargs):
"""
Start to run a strategy
Expand Down
25 changes: 14 additions & 11 deletions rqalpha/api/api_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from ..execution_context import ExecutionContext
from ..const import EXECUTION_PHASE, EXC_TYPE, ORDER_STATUS, SIDE, POSITION_EFFECT, ORDER_TYPE, MATCHING_TYPE, RUN_TYPE
from ..utils import to_industry_code, to_sector_name, unwrapper
from ..utils.exception import patch_user_exc, patch_system_exc, EXC_EXT_NAME
from ..utils.exception import patch_user_exc, patch_system_exc, EXC_EXT_NAME, RQInvalidArgument
from ..utils.i18n import gettext as _
from ..model.snapshot import SnapshotObject
from ..model.order import Order, MarketOrder, LimitOrder
Expand Down Expand Up @@ -83,6 +83,8 @@ def api_exc_patch(func):
def deco(*args, **kwargs):
try:
return func(*args, **kwargs)
except RQInvalidArgument:
raise
except Exception as e:
if isinstance(e, TypeError):
exc_info = sys.exc_info()
Expand Down Expand Up @@ -120,7 +122,7 @@ def assure_order_book_id(id_or_ins):
elif isinstance(id_or_ins, six.string_types):
order_book_id = instruments(id_or_ins).order_book_id
else:
raise patch_user_exc(KeyError(_("unsupported order_book_id type")))
raise RQInvalidArgument(_("unsupported order_book_id type"))

return order_book_id

Expand Down Expand Up @@ -221,7 +223,8 @@ def subscribe(id_or_symbols):
for item in id_or_symbols:
current_universe.add(assure_order_book_id(item))
else:
raise patch_user_exc(KeyError(_("unsupported order_book_id type")))
raise RQInvalidArgument(_("unsupported order_book_id type"))
verify_that('id_or_symbols')._are_valid_instruments("subscribe", id_or_symbols)
Environment.get_instance().update_universe(current_universe)


Expand Down Expand Up @@ -251,7 +254,7 @@ def unsubscribe(id_or_symbols):
i = assure_order_book_id(item)
current_universe.discard(i)
else:
raise patch_user_exc(KeyError(_("unsupported order_book_id type")))
raise RQInvalidArgument(_("unsupported order_book_id type"))

Environment.get_instance().update_universe(current_universe)

Expand Down Expand Up @@ -304,7 +307,7 @@ def get_yield_curve(date=None, tenor=None):
else:
date = pd.Timestamp(date)
if date > yesterday:
raise patch_user_exc(RuntimeError('get_yield_curve: {} >= now({})'.format(date, yesterday)))
raise RQInvalidArgument('get_yield_curve: {} >= now({})'.format(date, yesterday))

return data_proxy.get_yield_curve(start_date=date, end_date=date, tenor=tenor)

Expand Down Expand Up @@ -398,7 +401,7 @@ def history_bars(order_book_id, bar_count, frequency, fields=None, skip_suspende
dt = ExecutionContext.get_current_calendar_dt()

if frequency == '1m' and Environment.get_instance().config.base.frequency == '1d':
raise patch_user_exc(ValueError('can not get minute history in day back test'))
raise RQInvalidArgument('can not get minute history in day back test')

if (Environment.get_instance().config.base.frequency == '1m' and frequency == '1d') or \
(frequency == '1d' and ExecutionContext.get_active().phase == EXECUTION_PHASE.BEFORE_TRADING):
Expand Down Expand Up @@ -663,8 +666,8 @@ def to_date(date):
return date.date()
except AttributeError:
return date

raise patch_user_exc(ValueError('unknown date value: {}'.format(date)))
raise RQInvalidArgument('unknown date value: {}'.format(date))


@export_as_api
Expand All @@ -681,10 +684,10 @@ def get_dividend(order_book_id, start_date, adjusted=True):
dt = ExecutionContext.get_current_trading_dt().date() - datetime.timedelta(days=1)
start_date = to_date(start_date)
if start_date > dt:
raise patch_user_exc(
ValueError(_('in get_dividend, start_date {} is later than the previous test day {}').format(
raise RQInvalidArgument(
_('in get_dividend, start_date {} is later than the previous test day {}').format(
start_date, dt
)))
))
order_book_id = assure_order_book_id(order_book_id)
df = ExecutionContext.data_proxy.get_dividend(order_book_id, adjusted)
return df[start_date:dt]
Expand Down
20 changes: 10 additions & 10 deletions rqalpha/api/api_future.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@

from __future__ import division
import six
import numpy as np

from .api_base import decorate_api_exc, instruments
from ..execution_context import ExecutionContext
from ..model.order import Order, MarketOrder, LimitOrder, OrderStyle
from ..const import EXECUTION_PHASE, SIDE, POSITION_EFFECT, ORDER_TYPE
from ..model.instrument import Instrument
from ..utils.exception import patch_user_exc
from ..utils.exception import patch_user_exc, RQInvalidArgument
from ..utils.logger import user_system_log
from ..utils.i18n import gettext as _
from ..utils.arg_checker import apply_rules, verify_that
Expand Down Expand Up @@ -59,20 +60,19 @@ def order(id_or_ins, amount, side, position_effect, style):
if amount <= 0:
raise RuntimeError
if isinstance(style, LimitOrder) and style.get_limit_price() <= 0:
raise patch_user_exc(ValueError(_("Limit order price should be positive")))
raise RQInvalidArgument(_("Limit order price should be positive"))

order_book_id = assure_future_order_book_id(id_or_ins)
bar_dict = ExecutionContext.get_current_bar_dict()
bar = bar_dict[order_book_id]
price = bar.close

price = ExecutionContext.get_current_close_price(order_book_id)

amount = int(amount)

calendar_dt = ExecutionContext.get_current_calendar_dt()
trading_dt = ExecutionContext.get_current_trading_dt()
r_order = Order.__from_create__(calendar_dt, trading_dt, order_book_id, amount, side, style, position_effect)

if bar.isnan or price == 0:
if np.isnan(price) or price == 0:
user_system_log.warn(_("Order Creation Failed: [{order_book_id}] No market data").format(order_book_id=order_book_id))
r_order._mark_rejected(_("Order Creation Failed: [{order_book_id}] No market data").format(order_book_id=order_book_id))
return r_order
Expand Down Expand Up @@ -173,15 +173,15 @@ def sell_close(id_or_ins, amount, style=MarketOrder()):
def assure_future_order_book_id(id_or_symbols):
if isinstance(id_or_symbols, Instrument):
if id_or_symbols.type != "Future":
raise patch_user_exc(
ValueError(_("{order_book_id} is not supported in current strategy type").format(
order_book_id=id_or_symbols.order_book_id)))
raise RQInvalidArgument(
_("{order_book_id} is not supported in current strategy type").format(
order_book_id=id_or_symbols.order_book_id))
else:
return id_or_symbols.order_book_id
elif isinstance(id_or_symbols, six.string_types):
return assure_future_order_book_id(instruments(id_or_symbols))
else:
raise patch_user_exc(KeyError(_("unsupported order_book_id type")))
raise RQInvalidArgument(_("unsupported order_book_id type"))


@export_as_api
Expand Down
22 changes: 11 additions & 11 deletions rqalpha/api/api_stock.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from ..model.instrument import Instrument
from ..model.order import Order, OrderStyle, MarketOrder, LimitOrder
from ..utils.arg_checker import apply_rules, verify_that
from ..utils.exception import patch_user_exc
from ..utils.exception import patch_user_exc, RQInvalidArgument
from ..utils.i18n import gettext as _
from ..utils.logger import user_system_log
from ..utils.scheduler import market_close, market_open
Expand Down Expand Up @@ -100,10 +100,10 @@ def order_shares(id_or_ins, amount, style=MarketOrder()):
# :return: A unique order id.
# :rtype: int
if not isinstance(style, OrderStyle):
raise patch_user_exc(ValueError(_('style should be OrderStyle')))
raise RQInvalidArgument(_('style should be OrderStyle'))
if isinstance(style, LimitOrder):
if style.get_limit_price() <= 0:
raise patch_user_exc(ValueError(_("Limit order price should be positive")))
raise RQInvalidArgument(_("Limit order price should be positive"))

order_book_id = assure_stock_order_book_id(id_or_ins)
bar_dict = ExecutionContext.get_current_bar_dict()
Expand Down Expand Up @@ -239,10 +239,10 @@ def order_value(id_or_ins, cash_amount, style=MarketOrder()):
# :return: A unique order id.
# :rtype: int
if not isinstance(style, OrderStyle):
raise patch_user_exc(ValueError(_('style should be OrderStyle')))
raise RQInvalidArgument(_('style should be OrderStyle'))
if isinstance(style, LimitOrder):
if style.get_limit_price() <= 0:
raise patch_user_exc(ValueError(_("Limit order price should be positive")))
raise RQInvalidArgument(_("Limit order price should be positive"))

order_book_id = assure_stock_order_book_id(id_or_ins)

Expand Down Expand Up @@ -317,7 +317,7 @@ def order_percent(id_or_ins, percent, style=MarketOrder()):
# :return: A unique order id.
# :rtype: int
if percent < -1 or percent > 1:
raise patch_user_exc(ValueError(_('percent should between -1 and 1')))
raise RQInvalidArgument(_('percent should between -1 and 1'))

account = ExecutionContext.accounts[ACCOUNT_TYPE.STOCK]
portfolio_value = account.portfolio.portfolio_value
Expand Down Expand Up @@ -436,7 +436,7 @@ def order_target_percent(id_or_ins, percent, style=MarketOrder()):
# :return: A unique order id.
# :rtype: int
if percent < 0 or percent > 1:
raise patch_user_exc(ValueError(_('percent should between 0 and 1')))
raise RQInvalidArgument(_('percent should between 0 and 1'))
order_book_id = assure_stock_order_book_id(id_or_ins)

bar_dict = ExecutionContext.get_current_bar_dict()
Expand All @@ -461,13 +461,13 @@ def assure_stock_order_book_id(id_or_symbols):
if "XSHG" in order_book_id or "XSHE" in order_book_id:
return order_book_id
else:
raise patch_user_exc(
ValueError(_("{order_book_id} is not supported in current strategy type").format(
order_book_id=order_book_id)))
raise RQInvalidArgument(
_("{order_book_id} is not supported in current strategy type").format(
order_book_id=order_book_id))
elif isinstance(id_or_symbols, six.string_types):
return assure_stock_order_book_id(instruments(id_or_symbols))
else:
raise patch_user_exc(KeyError(_("unsupported order_book_id type")))
raise RQInvalidArgument(_("unsupported order_book_id type"))


def downsize_amount(amount, position):
Expand Down
4 changes: 3 additions & 1 deletion rqalpha/config_template.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version: 0.1.1
version: 0.1.2

# 白名单,设置可以直接在策略代码中指定哪些模块的配置项目
whitelist: [base, extra, validator, mod]
Expand Down Expand Up @@ -103,6 +103,8 @@ mod:
available_cash: true
# available_position: 检查可平仓位是否充足,默认开启
available_position: true
# short_stock: 允许裸卖空
short_stock: false
analyser:
priority: 100
enabled: true
Expand Down
1 change: 1 addition & 0 deletions rqalpha/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ class EXECUTION_PHASE(CustomEnum):
POSITION_EFFECT = CustomEnum("POSITION_EFFECT", [
"OPEN",
"CLOSE",
"CLOSE_TODAY",
])

EXC_TYPE = CustomEnum("EXC_TYPE", [
Expand Down

0 comments on commit d7640f6

Please sign in to comment.