* add ChangeInstrument to ops

Adding Change instrument OP. This op allows one to use  features of a different instrument.

* Update __init__.py

update parse_field to accommodate ChangeInstrument

* Propose test

* Add test case and fix bug

* Update ops.py

* Update ops.py

* simplify the operator further

* implement abstract method

* fix arg bug

* clean test

Co-authored-by: Young <afe.young@gmail.com>
Co-authored-by: you-n-g <you-n-g@users.noreply.github.com>
This commit is contained in:
Chao Wang 2022-07-03 20:45:26 -04:00 коммит произвёл GitHub
Родитель b655f90511
Коммит 3db22452fb
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
3 изменённых файлов: 128 добавлений и 1 удалений

Просмотреть файл

@ -1,6 +1,6 @@
repos:
- repo: https://github.com/psf/black
rev: 22.1.0
rev: 22.6.0
hooks:
- id: black
args: ["qlib", "-l 120"]

Просмотреть файл

@ -32,6 +32,7 @@ except ValueError:
np.seterr(invalid="ignore")
#################### Element-Wise Operator ####################
@ -62,6 +63,39 @@ class ElemOperator(ExpressionOps):
return self.feature.get_extended_window_size()
class ChangeInstrument(ElemOperator):
"""Change Instrument Operator
In some case, one may want to change to another instrument when calculating, for example, to
calculate beta of a stock with respect to a market index.
This would require changing the calculation of features from the stock (original instrument) to
the index (reference instrument)
Parameters
----------
instrument: new instrument for which the downstream operations should be performed upon.
i.e., SH000300 (CSI300 index), or ^GPSC (SP500 index).
feature: the feature to be calculated for the new instrument.
Returns
----------
Expression
feature operation output
"""
def __init__(self, instrument, feature):
self.instrument = instrument
self.feature = feature
def __str__(self):
return "{}('{}',{})".format(type(self).__name__, self.instrument, self.feature)
def load(self, instrument, start_index, end_index, *args):
# the first `instrument` is ignored
return super().load(self.instrument, start_index, end_index, *args)
def _load_internal(self, instrument, start_index, end_index, *args):
return self.feature.load(instrument, start_index, end_index, *args)
class NpElemOperator(ElemOperator):
"""Numpy Element-wise Operator
@ -1535,6 +1569,7 @@ class TResample(ElemOperator):
TOpsList = [TResample]
OpsList = [
ChangeInstrument,
Rolling,
Ref,
Max,

Просмотреть файл

@ -0,0 +1,92 @@
import unittest
from qlib.data import D
from qlib.data.dataset.loader import QlibDataLoader
from qlib.data.ops import ChangeInstrument, Cov, Feature, Ref, Var
from qlib.tests import TestOperatorData
class TestOperatorDataSetting(TestOperatorData):
def test_setting(self):
# All the query below passes
df = D.features(["SH600519"], ["ChangeInstrument('SH000300', $close)"])
# get market return for "SH600519"
df = D.features(["SH600519"], ["ChangeInstrument('SH000300', Feature('close')/Ref(Feature('close'),1) -1)"])
df = D.features(["SH600519"], ["ChangeInstrument('SH000300', $close/Ref($close,1) -1)"])
# excess return
df = D.features(
["SH600519"], ["($close/Ref($close,1) -1) - ChangeInstrument('SH000300', $close/Ref($close,1) -1)"]
)
print(df)
def test_case2(self):
def test_case(instruments, queries, note=None):
if note:
print(note)
print(f"checking {instruments} with queries {queries}")
df = D.features(instruments, queries)
print(df)
return df
test_case(["SH600519"], ["ChangeInstrument('SH000300', $close)"], "get market index close")
test_case(
["SH600519"],
["ChangeInstrument('SH000300', Feature('close')/Ref(Feature('close'),1) -1)"],
"get market index return with Feature",
)
test_case(
["SH600519"],
["ChangeInstrument('SH000300', $close/Ref($close,1) -1)"],
"get market index return with expression",
)
test_case(
["SH600519"],
["($close/Ref($close,1) -1) - ChangeInstrument('SH000300', $close/Ref($close,1) -1)"],
"get excess return with expression with beta=1",
)
ret = "Feature('close') / Ref(Feature('close'), 1) - 1"
benchmark = "SH000300"
n_period = 252
marketRet = f"ChangeInstrument('{benchmark}', Feature('close') / Ref(Feature('close'), 1) - 1)"
marketVar = f"ChangeInstrument('{benchmark}', Var({marketRet}, {n_period}))"
beta = f"Cov({ret}, {marketRet}, {n_period}) / {marketVar}"
excess_return = f"{ret} - {beta}*({marketRet})"
fields = [
"Feature('close')",
f"ChangeInstrument('{benchmark}', Feature('close'))",
ret,
marketRet,
beta,
excess_return,
]
test_case(["SH600519"], fields[5:], "get market beta and excess_return with estimated beta")
instrument = "sh600519"
ret = Feature("close") / Ref(Feature("close"), 1) - 1
benchmark = "sh000300"
n_period = 252
marketRet = ChangeInstrument(benchmark, Feature("close") / Ref(Feature("close"), 1) - 1)
marketVar = ChangeInstrument(benchmark, Var(marketRet, n_period))
beta = Cov(ret, marketRet, n_period) / marketVar
fields = [
Feature("close"),
ChangeInstrument(benchmark, Feature("close")),
ret,
marketRet,
beta,
ret - beta * marketRet,
]
names = ["close", "marketClose", "ret", "marketRet", f"beta_{n_period}", "excess_return"]
data_loader_config = {"feature": (fields, names)}
data_loader = QlibDataLoader(config=data_loader_config)
df = data_loader.load(instruments=[instrument]) # , start_time=start_time)
print(df)
# test_case(["sh600519"],fields,
# "get market beta and excess_return with estimated beta")
if __name__ == "__main__":
unittest.main()