Skip to content

Commit

Permalink
Merge pull request #847 from ricequant/develop
Browse files Browse the repository at this point in the history
rqalpha==5.3.5
  • Loading branch information
Cuizi7 authored Jan 22, 2024
2 parents 985c2df + 5d24b84 commit 4252c68
Show file tree
Hide file tree
Showing 34 changed files with 2,052 additions and 1,059 deletions.
507 changes: 321 additions & 186 deletions messages.pot

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ simplejson >=3.10.0
dill ==0.2.5
PyYAML >=3.12
tabulate
rqrisk >=0.0.14
rqrisk >=1.0.8
h5py
matplotlib >=1.5.1 ; python_version >= '3.6'
matplotlib >=1.5.1,<=3.0.3 ; python_version == '3.5'
274 changes: 194 additions & 80 deletions rqalpha/_version.py

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions rqalpha/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ base:
future_info: {}
# 强平
forced_liquidation: true
# 是否开启期货历史交易参数进行回测,默认为 False
futures_time_series_trading_parameters: false


extra:
Expand Down
54 changes: 42 additions & 12 deletions rqalpha/data/base_data_source/data_source.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,31 @@
# 详细的授权流程,请联系 [email protected] 获取。
import os
import pickle
from functools import lru_cache
from datetime import date, datetime, timedelta
from itertools import groupby
from typing import Dict, Iterable, List, Optional, Sequence, Union

import numpy as np
import pandas as pd
import six
from rqalpha.const import INSTRUMENT_TYPE, TRADING_CALENDAR_TYPE
from rqalpha.utils.i18n import gettext as _
from rqalpha.const import INSTRUMENT_TYPE, TRADING_CALENDAR_TYPE, DEFAULT_ACCOUNT_TYPE
from rqalpha.interface import AbstractDataSource
from rqalpha.model.instrument import Instrument
from rqalpha.utils.datetime_func import (convert_date_to_int, convert_int_to_date, convert_int_to_datetime)
from rqalpha.utils.exception import RQInvalidArgument
from rqalpha.utils.exception import RQInvalidArgument, RQDatacVersionTooLow
from rqalpha.utils.functools import lru_cache
from rqalpha.utils.typing import DateLike
from rqalpha.environment import Environment

from rqalpha.data.bundle import update_futures_trading_parameters
from rqalpha.utils.logger import user_system_log
from rqalpha.data.base_data_source.adjust import FIELDS_REQUIRE_ADJUSTMENT, adjust_bars
from rqalpha.data.base_data_source.storage_interface import (AbstractCalendarStore, AbstractDateSet,
AbstractDayBarStore, AbstractDividendStore,
AbstractInstrumentStore)
from rqalpha.data.base_data_source.storages import (DateSet, DayBarStore, DividendStore,
ExchangeTradingCalendarStore, FutureDayBarStore,
FutureInfoStore, InstrumentStore,
FutureInfoStore, FuturesTradingParametersStore,InstrumentStore,
ShareTransformationStore, SimpleFactorStore,
YieldCurveStore)

Expand Down Expand Up @@ -71,7 +72,8 @@ class BaseDataSource(AbstractDataSource):
INSTRUMENT_TYPE.PUBLIC_FUND,
)

def __init__(self, path, custom_future_info):
def __init__(self, path, custom_future_info, futures_time_series_trading_parameters=False, end_date=None):
# type: (str, dict, bool, date) -> None
if not os.path.exists(path):
raise RuntimeError('bundle path {} not exist'.format(os.path.abspath(path)))

Expand All @@ -86,20 +88,26 @@ def _p(name):
INSTRUMENT_TYPE.ETF: funds_day_bar_store,
INSTRUMENT_TYPE.LOF: funds_day_bar_store
} # type: Dict[INSTRUMENT_TYPE, AbstractDayBarStore]


self._futures_trading_parameters_store = None
self._future_info_store = FutureInfoStore(_p("future_info.json"), custom_future_info)

self._instruments_stores = {} # type: Dict[INSTRUMENT_TYPE, AbstractInstrumentStore]
self._ins_id_or_sym_type_map = {} # type: Dict[str, INSTRUMENT_TYPE]
instruments = []

with open(_p('instruments.pk'), 'rb') as f:
for i in pickle.load(f):
if i["type"] == "Future" and Instrument.is_future_continuous_contract(i["order_book_id"]):
i["listed_date"] = datetime(1990, 1, 1)
instruments.append(Instrument(i, lambda i: self._future_info_store.get_future_info(i)["tick_size"]))
instruments.append(Instrument(
i,
lambda i: self._future_info_store.get_tick_size(i),
lambda i, dt: self.get_futures_trading_parameters(i, dt).long_margin_ratio,
lambda i, dt: self.get_futures_trading_parameters(i, dt).short_margin_ratio
))
for ins_type in self.DEFAULT_INS_TYPES:
self.register_instruments_store(InstrumentStore(instruments, ins_type))

dividend_store = DividendStore(_p('dividends.h5'))
self._dividends = {
INSTRUMENT_TYPE.CS: dividend_store,
Expand All @@ -125,6 +133,20 @@ def _p(name):
self._suspend_days = [DateSet(_p('suspended_days.h5'))] # type: List[AbstractDateSet]
self._st_stock_days = DateSet(_p('st_stock_days.h5'))

if futures_time_series_trading_parameters:
try:
import rqdatac
except ImportError:
user_system_log.warn(_("RQDatac is not installed, \"config.base.futures_time_series_trading_parameters\" will be disabled."))
else:
try:
update_futures_trading_parameters(path, end_date)
except (rqdatac.share.errors.PermissionDenied, RQDatacVersionTooLow):
user_system_log.warn(_("RQDatac does not have permission to obtain futures histrical trading parameters, \"config.base.futures_time_series_trading_parameters\" will be disabled."))
else:
file = os.path.join(path, "futures_trading_parameters.h5")
self._futures_trading_parameters_store = FuturesTradingParametersStore(file, custom_future_info)

def register_day_bar_store(self, instrument_type, store):
# type: (INSTRUMENT_TYPE, AbstractDayBarStore) -> None
self._day_bars[instrument_type] = store
Expand Down Expand Up @@ -359,8 +381,16 @@ def available_data_range(self, frequency):
def get_yield_curve(self, start_date, end_date, tenor=None):
return self._yield_curve.get_yield_curve(start_date, end_date, tenor=tenor)

def get_commission_info(self, instrument):
return self._future_info_store.get_future_info(instrument)
@lru_cache(1024)
def get_futures_trading_parameters(self, instrument, dt):
# type: (Instrument, datetime.date) -> FuturesTradingParameters
if self._futures_trading_parameters_store:
trading_parameters = self._futures_trading_parameters_store.get_futures_trading_parameters(instrument, dt)
if trading_parameters is None:
return self._future_info_store.get_future_info(instrument.order_book_id, instrument.underlying_symbol)
return trading_parameters
else:
return self._future_info_store.get_future_info(instrument.order_book_id, instrument.underlying_symbol)

def get_merge_ticks(self, order_book_id_list, trading_date, last_dt=None):
raise NotImplementedError
Expand Down
132 changes: 118 additions & 14 deletions rqalpha/data/base_data_source/storages.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from copy import copy
from itertools import chain
from contextlib import contextmanager
from typing import Dict, Iterable, Optional
from typing import Dict, Iterable, Optional, NamedTuple

import h5py
import numpy as np
Expand All @@ -34,13 +34,26 @@
from rqalpha.model.instrument import Instrument
from rqalpha.utils.datetime_func import convert_date_to_date_int
from rqalpha.utils.i18n import gettext as _
from rqalpha.utils.logger import user_system_log

from .storage_interface import (AbstractCalendarStore, AbstractDateSet,
AbstractDayBarStore, AbstractDividendStore,
AbstractInstrumentStore,
AbstractSimpleFactorStore)


class FuturesTradingParameters(NamedTuple):
"""
数据类,用以存储期货交易参数数据
"""
close_commission_ratio: float
close_commission_today_ratio: float
commission_type: str
open_commission_ratio: float
long_margin_ratio: float
short_margin_ratio: float


class ExchangeTradingCalendarStore(AbstractCalendarStore):
def __init__(self, f):
self._f = f
Expand All @@ -64,27 +77,52 @@ def __init__(self, f, custom_future_info):
) for item in json.load(json_file)
}
self._custom_data = custom_future_info
self._future_info = {}
if "margin_rate" not in self._default_data[next(iter(self._default_data))]:
raise RuntimeError(_("Your bundle data is too old, please use 'rqalpha update-bundle' or 'rqalpha download-bundle' to update it to lastest before using"))

@classmethod
def _process_future_info_item(cls, item):
item["commission_type"] = cls.COMMISSION_TYPE_MAP[item["commission_type"]]
return item

def get_future_info(self, instrument):
# type: (Instrument) -> Dict[str, float]
order_book_id = instrument.order_book_id
@lru_cache(1024)
def get_future_info(self, order_book_id, underlying_symbol):
# type: (str, str) -> FuturesTradingParameters
custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(underlying_symbol)
info = self._default_data.get(order_book_id) or self._default_data.get(underlying_symbol)
if custom_info:
info = copy(info) or {}
info.update(custom_info)
elif not info:
raise NotImplementedError(_("unsupported future instrument {}").format(order_book_id))
info = self._to_namedtuple(info)
return info

def _to_namedtuple(self, info):
# type: (dict) -> FuturesTradingParameters
info['long_margin_ratio'], info['short_margin_ratio'] = info['margin_rate'], info['margin_rate']
del info['margin_rate'], info['tick_size']
try:
return self._future_info[order_book_id]
del info['order_book_id']
except KeyError:
custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(instrument.underlying_symbol)
info = self._default_data.get(order_book_id) or self._default_data.get(instrument.underlying_symbol)
if custom_info:
info = copy(info) or {}
info.update(custom_info)
elif not info:
raise NotImplementedError(_("unsupported future instrument {}").format(order_book_id))
return self._future_info.setdefault(order_book_id, info)
del info['underlying_symbol']
info = FuturesTradingParameters(**info)
return info

@lru_cache(8)
def get_tick_size(self, instrument):
# type: (str, str) -> float
order_book_id = instrument.order_book_id
underlying_symbol = instrument.underlying_symbol
custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(underlying_symbol)
info = self._default_data.get(order_book_id) or self._custom_data.get(underlying_symbol)
if custom_info:
info = copy(info) or {}
info.update(custom_info)
elif not info:
raise NotImplementedError(_("unsupported future instrument {}".format(order_book_id)))
tick_size = info['tick_size']
return tick_size


class InstrumentStore(AbstractInstrumentStore):
Expand Down Expand Up @@ -208,6 +246,72 @@ class FutureDayBarStore(DayBarStore):
DEFAULT_DTYPE = np.dtype(DayBarStore.DEFAULT_DTYPE.descr + [("open_interest", '<f8')])


class FuturesTradingParametersStore(object):
COMMISSION_TYPE_MAP = {
0: COMMISSION_TYPE.BY_MONEY,
1: COMMISSION_TYPE.BY_VOLUME
}

# 历史期货交易参数的数据在2010年4月之后才有
FUTURES_TRADING_PARAMETERS_START_DATE = 20100401

def __init__(self, path, custom_future_info):
self._path = path
self._custom_data = custom_future_info

def get_futures_trading_parameters(self, instrument, dt):
# type: (Instrument, datetime.date) -> FuturesTradingParameters or None
dt = convert_date_to_date_int(dt)
if dt < self.FUTURES_TRADING_PARAMETERS_START_DATE:
return None
order_book_id = instrument.order_book_id
underlying_symbol = instrument.underlying_symbol
data = self.get_futures_trading_parameters_all_time(order_book_id)
if data is None:
return None
else:
arr = data[data['datetime'] == dt]
if len(arr) == 0:
if dt >= convert_date_to_date_int(instrument.listed_date) and dt <= convert_date_to_date_int(instrument.de_listed_date):
user_system_log.info("Historical futures trading parameters are abnormal, the lastst parameters will be used for calculations.\nPlease contract RiceQuant to repair: 0755-26569969")
return None
custom_info = self._custom_data.get(order_book_id) or self._custom_data.get(underlying_symbol)
if custom_info:
arr[0] = self.set_custom_info(arr[0], custom_info)
futures_trading_parameters = self._to_namedtuple(arr[0])
return futures_trading_parameters

@lru_cache(1024)
def get_futures_trading_parameters_all_time(self, order_book_id):
# type: (str) -> numpy.ndarray or None
with h5_file(self._path) as h5:
try:
data = h5[order_book_id][:]
except KeyError:
return None
return data

def set_custom_info(self, arr, custom_info):
for field in custom_info:
if field == "commission_type":
if custom_info[field] == COMMISSION_TYPE.BY_MONEY:
value = 0
elif custom_info[field] == COMMISSION_TYPE.BY_VOLUME:
value = 1
else:
value = custom_info[field]
arr[field] = value
return arr

def _to_namedtuple(self, arr):
# type: (numpy.void) -> FuturesTradingParameters
dic = dict(zip(arr.dtype.names, arr))
del dic['datetime']
dic["commission_type"] = self.COMMISSION_TYPE_MAP[dic['commission_type']]
futures_trading_parameters = FuturesTradingParameters(**dic)
return futures_trading_parameters


class DividendStore(AbstractDividendStore):
def __init__(self, path):
self._path = path
Expand Down
Loading

0 comments on commit 4252c68

Please sign in to comment.