Unverified Commit f4ddddae authored by iLampard's avatar iLampard Committed by GitHub

Merge pull request #11 from alpha-miner/master

merge update
parents 469fe61f 48fc6a88
......@@ -28,6 +28,7 @@ from alphamind.data.standardize import standardize
from alphamind.data.standardize import projection
from alphamind.data.neutralize import neutralize
from alphamind.data.rank import rank
from alphamind.data.rank import percentile
from alphamind.data.engines.sqlengine import factor_tables
from alphamind.data.engines.utilities import industry_list
......@@ -80,6 +81,7 @@ __all__ = [
'projection',
'neutralize',
'rank',
'percentile',
'factor_tables',
'industry_list',
'fetch_data_package',
......
......@@ -12,6 +12,19 @@ Base = declarative_base()
metadata = Base.metadata
class Categories(Base):
__tablename__ = 'categories'
__table_args__ = (
Index('categories_pk', 'trade_date', 'code', unique=True),
)
trade_date = Column(DateTime, primary_key=True, nullable=False)
code = Column(BigInteger, primary_key=True, nullable=False)
sw1 = Column(Integer)
sw1_adj = Column(Integer)
class DailyPortfolios(Base):
__tablename__ = 'daily_portfolios'
__table_args__ = (
......@@ -68,6 +81,7 @@ class Experimental(Base):
pure_liq_2 = Column(Float(53))
pure_liq_3 = Column(Float(53))
pure_liq_4 = Column(Float(53))
pe_hist60 = Column(Float(53))
class FactorMaster(Base):
......@@ -137,82 +151,6 @@ class Industry(Base):
IndustryName4 = Column(String(50))
class LegacyFactor(Base):
__tablename__ = 'legacy_factor'
__table_args__ = (
Index('legacy_factor_idx', 'trade_date', 'code', unique=True),
)
trade_date = Column(DateTime, primary_key=True, nullable=False)
code = Column(Integer, primary_key=True, nullable=False)
ROEAfterNonRecurring = Column(Float(53))
EPSAfterNonRecurring = Column(Float(53))
EODPrice = Column(Float(53))
LogFloatCap = Column(Float(53))
BPS = Column(Float(53))
SPS = Column(Float(53))
DebtToAsset = Column(Float(53))
STOM = Column(Float(53))
DROEAfterNonRecurring = Column(Float(53))
LogTotalCap = Column(Float(53))
BP = Column(Float(53))
SP = Column(Float(53))
EPAfterNonRecurring = Column(Float(53))
DivToB = Column(Float(53))
DivP = Column(Float(53))
EBITToSales = Column(Float(53))
EBITAToSales = Column(Float(53))
EVToSales = Column(Float(53))
EVToEBIT = Column(Float(53))
EVToEBITDA = Column(Float(53))
EVToNOPLAT = Column(Float(53))
EVToIC = Column(Float(53))
ROIC = Column(Float(53))
FCFFPS = Column(Float(53))
FCFFToEarningAfterNonRecurring = Column(Float(53))
FCFFP = Column(Float(53))
ProfitToAsset = Column(Float(53))
GrossProfitRatio = Column(Float(53))
NetProfitRatio = Column(Float(53))
LATO = Column(Float(53))
FATO = Column(Float(53))
TATO = Column(Float(53))
EquityTO = Column(Float(53))
PayableTO = Column(Float(53))
RecievableTO = Column(Float(53))
RevenueGrowth = Column(Float(53))
GrossProfitGrowth = Column(Float(53))
NetProfitGrowth = Column(Float(53))
GrossCFToRevenue = Column(Float(53))
CFToRevenue = Column(Float(53))
CFToProfit = Column(Float(53))
CFToAsset = Column(Float(53))
GrossCFGrowth = Column(Float(53))
CFGrowth = Column(Float(53))
ICFGrowth = Column(Float(53))
AveAmount60 = Column(Float(53))
PeriodReturn60 = Column(Float(53))
AmountRatio60to250 = Column(Float(53))
CFPS = Column(Float(53))
CFP = Column(Float(53))
NetCFGrowth = Column(Float(53))
NetCFGrowthP = Column(Float(53))
NetCash = Column(Float(53))
NetCashP = Column(Float(53))
BVPSGrowth = Column(Float(53))
EquityPSGrowth = Column(Float(53))
WholeSales = Column(Float(53))
WholeProfitAfterNonRecurring = Column(Float(53))
ExpenseRatio = Column(Float(53))
CurrentRatio = Column(Float(53))
QuickRatio = Column(Float(53))
AcidTestRatio = Column(Float(53))
TimeInterestEarnedRatio = Column(Float(53))
DepositReceivedVsSale = Column(Float(53))
DebtRatioExcemptDepRec = Column(Float(53))
SNBARatio = Column(Float(53))
class Market(Base):
__tablename__ = 'market'
__table_args__ = (
......@@ -301,21 +239,6 @@ class PortfolioSettings(Base):
weight = Column(Float(53), nullable=False)
class Positions(Base):
__tablename__ = 'positions'
__table_args__ = (
Index('positions_idx', 'trade_date', 'source', 'universe', 'benchmark', 'portfolio', 'type', unique=True),
)
source = Column(String(50), primary_key=True, nullable=False)
universe = Column(String(50), primary_key=True, nullable=False)
benchmark = Column(Integer, primary_key=True, nullable=False)
trade_date = Column(DateTime, primary_key=True, nullable=False)
portfolio = Column(String(50), primary_key=True, nullable=False)
type = Column(String(50), primary_key=True, nullable=False)
weight = Column(JSON)
class RebalanceLog(Base):
__tablename__ = 'rebalance_log'
__table_args__ = (
......@@ -718,30 +641,53 @@ class Strategy(Base):
source = Column(String(20), primary_key=True, nullable=False)
class Tiny(Base):
__tablename__ = 'tiny'
__table_args__ = (
Index('tiny_idx', 'trade_date', 'code', unique=True),
)
trade_date = Column(DateTime, primary_key=True, nullable=False)
code = Column(Integer, primary_key=True, nullable=False)
CFinc1 = Column(Float(53))
BDTO = Column(Float(53))
RVOL = Column(Float(53))
CHV = Column(Float(53))
VAL = Column(Float(53))
class Universe(Base):
__tablename__ = 'universe'
__table_args__ = (
Index('universe_idx', 'trade_date', 'universe', 'code', unique=True),
Index('universe_trade_date_code_uindex', 'trade_date', 'code', unique=True),
)
trade_date = Column(DateTime, primary_key=True, nullable=False)
code = Column(Integer, primary_key=True, nullable=False)
universe = Column(String(20), primary_key=True, nullable=False)
code = Column(BigInteger, primary_key=True, nullable=False)
aerodef = Column(Integer)
agriforest = Column(Integer)
auto = Column(Integer)
bank = Column(Integer)
builddeco = Column(Integer)
chem = Column(Integer)
conmat = Column(Integer)
commetrade = Column(Integer)
computer = Column(Integer)
conglomerates = Column(Integer)
eleceqp = Column(Integer)
electronics = Column(Integer)
foodbever = Column(Integer)
health = Column(Integer)
houseapp = Column(Integer)
ironsteel = Column(Integer)
leiservice = Column(Integer)
lightindus = Column(Integer)
machiequip = Column(Integer)
media = Column(Integer)
mining = Column(Integer)
nonbankfinan = Column(Integer)
nonfermetal = Column(Integer)
realestate = Column(Integer)
telecom = Column(Integer)
textile = Column(Integer)
transportation = Column(Integer)
utilities = Column(Integer)
ashare = Column(Integer)
ashare_ex = Column(Integer)
cyb = Column(Integer)
hs300 = Column(Integer)
sh50 = Column(Integer)
zxb = Column(Integer)
zz1000 = Column(Integer)
zz500 = Column(Integer)
zz800 = Column(Integer)
hs300_adj = Column(Integer)
zz500_adj = Column(Integer)
class Uqer(Base):
......@@ -1316,5 +1262,5 @@ class Outright(Base):
if __name__ == '__main__':
from sqlalchemy import create_engine
engine = create_engine('postgres+psycopg2://postgres:we083826@192.168.0.102/alpha')
engine = create_engine('postgresql+psycopg2://postgres:we083826@101.132.104.118/alpha')
Base.metadata.create_all(engine)
......@@ -14,7 +14,7 @@ import numpy as np
import pandas as pd
import sqlalchemy as sa
import sqlalchemy.orm as orm
from sqlalchemy import select, and_, outerjoin, join, delete, insert
from sqlalchemy import select, and_, outerjoin, join, delete, insert, column
from sqlalchemy.sql import func
from sqlalchemy.sql.expression import bindparam
from alphamind.data.engines.universe import Universe
......@@ -32,7 +32,6 @@ from alphamind.data.dbmodel.models import Universe as UniverseTable
from alphamind.data.dbmodel.models import Formulas
from alphamind.data.dbmodel.models import DailyPortfoliosSchedule
from alphamind.data.dbmodel.models import Performance
from alphamind.data.dbmodel.models import Positions
from alphamind.data.dbmodel.models import Outright
from alphamind.data.dbmodel.models import RiskExposure
from alphamind.data.transformer import Transformer
......@@ -231,7 +230,7 @@ class SqlEngine(object):
risk_factors=df[neutralized_risks].values,
post_process=post_process)
return df[['code', 'dx']]
return df[['code', 'dx']].drop_duplicates(['code'])
def fetch_dx_return_range(self,
universe,
......@@ -250,28 +249,23 @@ class SqlEngine(object):
stats = self._create_stats(Market, horizon, offset)
cond = universe._query_statements(start_date, end_date, None)
codes = universe.query(self.engine, start_date, end_date, dates)
t = select([Market.trade_date, Market.code, stats]).where(
Market.trade_date.between(start_date, end_date)
).alias('t')
big_table = join(t, UniverseTable,
and_(
t.columns['trade_date'] == UniverseTable.trade_date,
t.columns['code'] == UniverseTable.code,
cond
)
)
query = select([t]).select_from(big_table)
df = pd.read_sql(query, self.session.bind).dropna()
and_(
Market.trade_date.between(start_date, end_date),
Market.code.in_(codes.code.unique().tolist())
)
).cte('t')
if universe.is_filtered:
codes = universe.query(self, start_date, end_date, dates)
df = pd.merge(df, codes, how='inner', on=['trade_date', 'code'])
cond = universe._query_statements(start_date, end_date, dates)
query = select([t]).where(
and_(t.columns['trade_date'] == UniverseTable.trade_date,
t.columns['code'] == UniverseTable.code,
cond)
)
if dates:
df = df[df.trade_date.isin(dates)]
df = pd.read_sql(query, self.session.bind).dropna()
return df.sort_values(['trade_date', 'code'])
def fetch_dx_return_index(self,
......@@ -364,7 +358,7 @@ class SqlEngine(object):
joined_tables.add(t.__table__.name)
query = select(
[Market.trade_date, Market.code, Market.isOpen] + list(factor_cols.keys())) \
[Market.trade_date, Market.code, Market.chgPct] + list(factor_cols.keys())) \
.select_from(big_table).where(and_(Market.trade_date.between(start_date, end_date),
Market.code.in_(codes)))
......@@ -374,15 +368,10 @@ class SqlEngine(object):
.set_index('trade_date')
res = transformer.transform('code', df).replace([-np.inf, np.inf], np.nan)
for col in res.columns:
if col not in set(['code', 'isOpen']) and col not in df.columns:
df[col] = res[col].values
df.dropna(inplace=True)
df['isOpen'] = df.isOpen.astype(bool)
df = df.loc[ref_date]
df.index = list(range(len(df)))
return df
res['chgPct'] = df.chgPct
res = res.loc[ref_date]
res.index = list(range(len(res)))
return res.drop_duplicates(['trade_date', 'code'])
def fetch_factor_range(self,
universe: Universe,
......@@ -424,7 +413,7 @@ class SqlEngine(object):
universe_df = universe.query(self, start_date, end_date, dates)
query = select(
[Market.trade_date, Market.code, Market.isOpen] + list(factor_cols.keys())) \
[Market.trade_date, Market.code, Market.chgPct] + list(factor_cols.keys())) \
.select_from(big_table).where(
and_(
Market.code.in_(universe_df.code.unique().tolist()),
......@@ -433,8 +422,6 @@ class SqlEngine(object):
).distinct()
df = pd.read_sql(query, self.engine).replace([-np.inf, np.inf], np.nan)
if universe.is_filtered:
df = pd.merge(df, universe_df, how='inner', on=['trade_date', 'code'])
if external_data is not None:
df = pd.merge(df, external_data, on=['trade_date', 'code']).dropna()
......@@ -443,14 +430,9 @@ class SqlEngine(object):
df.set_index('trade_date', inplace=True)
res = transformer.transform('code', df).replace([-np.inf, np.inf], np.nan)
for col in res.columns:
if col not in set(['code', 'isOpen']) and col not in df.columns:
df[col] = res[col].values
df.dropna(inplace=True)
df['isOpen'] = df.isOpen.astype(bool)
df = df.reset_index()
return pd.merge(df, universe_df[['trade_date', 'code']], how='inner')
res['chgPct'] = df.chgPct
res = res.reset_index()
return pd.merge(res, universe_df[['trade_date', 'code']], how='inner').drop_duplicates(['trade_date', 'code'])
def fetch_factor_range_forward(self,
universe: Universe,
......@@ -490,7 +472,7 @@ class SqlEngine(object):
partition_by=Market.code,
order_by=Market.trade_date).label('dx')
query = select([Market.trade_date, Market.code, stats]).select_from(big_table).where(
query = select([Market.trade_date, Market.code, Market.chgPct, stats]).select_from(big_table).where(
and_(
Market.trade_date.in_(total_dates),
Market.code.in_(total_codes)
......@@ -499,9 +481,8 @@ class SqlEngine(object):
df = pd.read_sql(query, self.engine) \
.replace([-np.inf, np.inf], np.nan) \
.dropna() \
.sort_values(['trade_date', 'code'])
return pd.merge(df, codes[['trade_date', 'code']], how='inner')
return pd.merge(df, codes[['trade_date', 'code']], how='inner').drop_duplicates(['trade_date', 'code'])
def fetch_benchmark(self,
ref_date: str,
......@@ -575,7 +556,7 @@ class SqlEngine(object):
risk_exp = pd.read_sql(query, self.engine).dropna()
return risk_cov, risk_exp
return risk_cov, risk_exp.drop_duplicates(['code'])
def fetch_risk_model_range(self,
universe: Universe,
......@@ -630,11 +611,6 @@ class SqlEngine(object):
risk_exp = pd.read_sql(query, self.engine).sort_values(['trade_date', 'code']).dropna()
if universe.is_filtered:
codes = universe.query(self, start_date, end_date, dates)
risk_exp = pd.merge(risk_exp, codes, how='inner', on=['trade_date', 'code']).sort_values(
['trade_date', 'code'])
return risk_cov, risk_exp
def fetch_industry(self,
......@@ -657,7 +633,7 @@ class SqlEngine(object):
)
).distinct()
return pd.read_sql(query, self.engine).dropna()
return pd.read_sql(query, self.engine).dropna().drop_duplicates(['code'])
def fetch_industry_matrix(self,
ref_date: str,
......@@ -667,19 +643,7 @@ class SqlEngine(object):
df = self.fetch_industry(ref_date, codes, category, level)
df['industry_name'] = df['industry']
df = pd.get_dummies(df, columns=['industry'], prefix="", prefix_sep="")
industries = industry_list(category, level)
in_s = []
out_s = []
for i in industries:
if i in df:
in_s.append(i)
else:
out_s.append(i)
res = df[['code', 'industry_code', 'industry_name'] + in_s]
res = res.assign(**dict(zip(out_s, [0] * len(out_s))))
return res
return df.drop('industry_code', axis=1)
def fetch_industry_range(self,
universe: Universe,
......@@ -705,13 +669,10 @@ class SqlEngine(object):
query = select([Industry.trade_date,
Industry.code,
getattr(Industry, code_name).label('industry_code'),
getattr(Industry, category_name).label('industry')]).select_from(big_table).distinct()
getattr(Industry, category_name).label('industry')]).select_from(big_table)\
.order_by(Industry.trade_date, Industry.code)
df = pd.read_sql(query, self.engine).dropna()
if universe.is_filtered:
codes = universe.query(self, start_date, end_date, dates)
df = pd.merge(df, codes, how='inner', on=['trade_date', 'code']).sort_values(['trade_date', 'code'])
return df
return pd.read_sql(query, self.engine).dropna()
def fetch_industry_matrix_range(self,
universe: Universe,
......@@ -724,33 +685,62 @@ class SqlEngine(object):
df = self.fetch_industry_range(universe, start_date, end_date, dates, category, level)
df['industry_name'] = df['industry']
df = pd.get_dummies(df, columns=['industry'], prefix="", prefix_sep="")
industries = industry_list(category, level)
in_s = []
out_s = []
for i in industries:
if i in df:
in_s.append(i)
else:
out_s.append(i)
res = df[['trade_date', 'code', 'industry_code', 'industry_name'] + in_s]
res = res.assign(**dict(zip(out_s, [0] * len(out_s))))
return res
return df.drop('industry_code', axis=1).drop_duplicates(['trade_date', 'code'])
def fetch_trade_status(self,
ref_date: str,
codes: Iterable[int]):
codes: Iterable[int],
offset=0):
query = select([Market.code, Market.isOpen]).where(
target_date = advanceDateByCalendar('china.sse', ref_date,
str(offset) + 'b').strftime('%Y%m%d')
stats = func.lead(Market.isOpen, 1).over(
partition_by=Market.code,
order_by=Market.trade_date).label('is_open')
cte = select([Market.trade_date, Market.code, stats]).where(
and_(
Market.trade_date == ref_date,
Market.trade_date.in_([ref_date, target_date]),
Market.code.in_(codes)
)
)
).cte('cte')
query = select([column('code'), column('is_open')]).select_from(cte).where(
column('trade_date') == ref_date
).order_by(column('code'))
return pd.read_sql(query, self.engine).sort_values(['code'])
def fetch_trade_status_range(self,
universe: Universe,
start_date: str = None,
end_date: str = None,
dates: Iterable[str] = None,
offset=0):
codes = universe.query(self, start_date, end_date, dates)
if dates:
start_date = dates[0]
end_date = dates[-1]
end_date = advanceDateByCalendar('china.sse', end_date,
str(offset) + 'b').strftime('%Y-%m-%d')
stats = func.lead(Market.isOpen, offset).over(
partition_by=Market.code,
order_by=Market.trade_date).label('is_open')
cte = select([Market.trade_date, Market.code, stats]).where(
and_(
Market.trade_date.between(start_date, end_date),
Market.code.in_(codes.code.unique().tolist())
)
).cte('cte')
query = select([cte]).select_from(cte).order_by(cte.columns['trade_date'], cte.columns['code'])
df = pd.read_sql(query, self.engine)
return pd.merge(df, codes[['trade_date', 'code']], on=['trade_date', 'code'])
def fetch_data(self,
ref_date: str,
factors: Iterable[str],
......@@ -964,31 +954,6 @@ class SqlEngine(object):
self.engine.execute(query)
df.to_sql(Performance.__table__.name, self.engine, if_exists='append', index=False)
def upsert_positions(self, ref_date, df):
universes = df.universe.unique().tolist()
benchmarks = df.benchmark.unique().tolist()
build_types = df.type.unique().tolist()
sources = df.source.unique().tolist()
portfolios = df.portfolio.unique().tolist()
query = delete(Positions).where(
and_(
Positions.trade_date == ref_date,
Positions.type.in_(build_types),
Positions.universe.in_(universes),
Positions.benchmark.in_(benchmarks),
Positions.source.in_(sources),
Positions.portfolio.in_(portfolios)
)
)
self.engine.execute(query)
df.to_sql(Positions.__table__.name,
self.engine,
if_exists='append',
index=False,
dtype={'weight': sa.types.JSON})
def fetch_outright_status(self, ref_date: str, is_open=True, ignore_internal_borrow=False):
table = Outright
if is_open:
......@@ -1052,10 +1017,12 @@ if __name__ == '__main__':
from PyFin.api import *
engine = SqlEngine()
ref_date = '2017-06-29'
universe = Universe('', ['zz800'])
ref_date = '2017-05-03'
universe = Universe('custon', ['zz800'])
codes = engine.fetch_codes(ref_date, universe)
dates = makeSchedule('2018-01-01', '2018-02-01', '10b', 'china.sse')
factor_data = engine.fetch_dx_return('2018-01-30', codes, neutralized_risks=risk_styles+industry_styles)
print(factor_data)
# df = engine.fetch_trade_status(ref_date, codes, offset=1)
dates = ['2017-05-02', '2017-05-03', '2017-05-04']
df = engine.fetch_trade_status_range(universe, dates=dates, offset=1)
print(df)
\ No newline at end of file
......@@ -5,135 +5,175 @@ Created on 2017-7-7
@author: cheng.li
"""
from typing import Iterable
import sys
import abc
import pandas as pd
from simpleutils.miscellaneous import list_eq
from sqlalchemy import and_
from sqlalchemy import or_
from sqlalchemy import not_
from sqlalchemy import select
from sqlalchemy import join
from sqlalchemy import outerjoin
from alphamind.data.dbmodel.models import Universe as UniverseTable
from alphamind.data.engines.utilities import _map_factors
from alphamind.data.dbmodel.models import Market
from alphamind.data.engines.utilities import factor_tables
from alphamind.data.transformer import Transformer
from alphamind.utilities import encode
from alphamind.utilities import decode
class Universe(object):
def __init__(self,
name: str,
base_universe: Iterable,
exclude_universe: Iterable = None,
special_codes: Iterable = None,
filter_cond=None):
self.name = name
self.base_universe = sorted(base_universe) if base_universe else None
self.exclude_universe = sorted(exclude_universe) if exclude_universe else None
self.special_codes = sorted(special_codes) if special_codes else None
self.filter_cond = filter_cond
def __eq__(self, rhs):
return self.name == rhs.name \
and list_eq(self.base_universe, rhs.base_universe) \
and list_eq(self.exclude_universe, rhs.exclude_universe) \
and list_eq(self.special_codes, rhs.special_codes) \
and str(self.filter_cond) == str(rhs.filter_cond)
@property
def is_filtered(self):
return True if self.filter_cond is not None else False
def _query_statements(self, start_date, end_date, dates):
or_conditions = []
if self.special_codes:
or_conditions.append(UniverseTable.code.in_(self.special_codes))
query = or_(
UniverseTable.universe.in_(self.base_universe),
*or_conditions
)
and_conditions = []
if self.exclude_universe:
and_conditions.append(UniverseTable.universe.notin_(self.exclude_universe))
class BaseUniverse(metaclass=abc.ABCMeta):
@abc.abstractmethod
def condition(self):
pass
def __add__(self, rhs):
return OrUniverse(self, rhs)
def __sub__(self, rhs):
return XorUniverse(self, rhs)
def __and__(self, rhs):
return AndUniverse(self, rhs)
def __or__(self, rhs):
return OrUniverse(self, rhs)
def isin(self, rhs):
return AndUniverse(self, rhs)
@abc.abstractmethod
def save(self):
pass
@classmethod
def load(cls, u_desc: dict):
pass
def query(self, engine, start_date: str = None, end_date: str = None, dates=None):
query = select([UniverseTable.trade_date, UniverseTable.code]).where(
self._query_statements(start_date, end_date, dates)
)
return pd.read_sql(query, engine.engine)
def _query_statements(self, start_date: str = None, end_date: str = None, dates=None):
return and_(
query,
UniverseTable.trade_date.in_(dates) if dates else UniverseTable.trade_date.between(start_date, end_date),
*and_conditions
self.condition(),
UniverseTable.trade_date.in_(dates) if dates else UniverseTable.trade_date.between(start_date, end_date)
)
def query(self, engine, start_date: str = None, end_date: str = None, dates=None) -> pd.DataFrame:
universe_cond = self._query_statements(start_date, end_date, dates)
if self.filter_cond is None:
# simple case
query = select([UniverseTable.trade_date, UniverseTable.code]).where(
universe_cond
).distinct()
return pd.read_sql(query, engine.engine)
else:
if isinstance(self.filter_cond, Transformer):
transformer = self.filter_cond
else:
transformer = Transformer(self.filter_cond)
dependency = transformer.dependency
factor_cols = _map_factors(dependency, factor_tables)
big_table = Market
for t in set(factor_cols.values()):
if t.__table__.name != Market.__table__.name:
big_table = outerjoin(big_table, t, and_(Market.trade_date == t.trade_date,
Market.code == t.code,
Market.trade_date.in_(
dates) if dates else Market.trade_date.between(
start_date, end_date)))
big_table = join(big_table, UniverseTable,
and_(Market.trade_date == UniverseTable.trade_date,
Market.code == UniverseTable.code,
universe_cond))
query = select(
[Market.trade_date, Market.code] + list(factor_cols.keys())) \
.select_from(big_table).distinct()
df = pd.read_sql(query, engine.engine).sort_values(['trade_date', 'code']).dropna()
df.set_index('trade_date', inplace=True)
filter_fields = transformer.names
pyFinAssert(len(filter_fields) == 1, ValueError, "filter fields can only be 1")
df = transformer.transform('code', df)
df = df[df[filter_fields[0]] == 1].reset_index()[['trade_date', 'code']]
return df
class Universe(BaseUniverse):
def __init__(self, u_name: str):
self.u_name = u_name
def condition(self):
return getattr(UniverseTable, self.u_name) == 1
def save(self):
return dict(
name=self.name,
base_universe=self.base_universe,
exclude_universe=self.exclude_universe,
special_codes=self.special_codes,
filter_cond=encode(self.filter_cond)
u_type=self.__class__.__name__,
u_name=self.u_name
)
@classmethod
def load(cls, universe_desc: dict):
name = universe_desc['name']
base_universe = universe_desc['base_universe']
exclude_universe = universe_desc['exclude_universe']
special_codes = universe_desc['special_codes']
filter_cond = decode(universe_desc['filter_cond'])
def load(cls, u_desc: dict):
return cls(u_name=u_desc['u_name'])
def __eq__(self, other):
return self.u_name == other.u_name
class OrUniverse(BaseUniverse):
def __init__(self, lhs: BaseUniverse, rhs: BaseUniverse):
self.lhs = lhs
self.rhs = rhs
def condition(self):
return or_(self.lhs.condition(), self.rhs.condition())
def save(self):
return dict(
u_type=self.__class__.__name__,
lhs=self.lhs.save(),
rhs=self.rhs.save()
)
@classmethod
def load(cls, u_desc: dict):
lhs = u_desc['lhs']
rhs = u_desc['rhs']
return cls(
lhs=getattr(sys.modules[__name__], lhs['u_type']).load(lhs),
rhs=getattr(sys.modules[__name__], rhs['u_type']).load(rhs),
)
def __eq__(self, other):
return self.lhs == other.lhs and self.rhs == other.rhs and isinstance(other, OrUniverse)
class AndUniverse(BaseUniverse):
def __init__(self, lhs: BaseUniverse, rhs: BaseUniverse):
self.lhs = lhs
self.rhs = rhs
def condition(self):
return and_(self.lhs.condition(), self.rhs.condition())
def save(self):
return dict(
u_type=self.__class__.__name__,
lhs=self.lhs.save(),
rhs=self.rhs.save()
)
@classmethod
def load(cls, u_desc: dict):
lhs = u_desc['lhs']
rhs = u_desc['rhs']
return cls(
lhs=getattr(sys.modules[__name__], lhs['u_type']).load(lhs),
rhs=getattr(sys.modules[__name__], rhs['u_type']).load(rhs),
)
def __eq__(self, other):
return self.lhs == other.lhs and self.rhs == other.rhs and isinstance(other, AndUniverse)
class XorUniverse(BaseUniverse):
def __init__(self, lhs: BaseUniverse, rhs: BaseUniverse):
self.lhs = lhs
self.rhs = rhs
def condition(self):
return and_(self.lhs.condition(), not_(self.rhs.condition()))
def save(self):
return dict(
u_type=self.__class__.__name__,
lhs=self.lhs.save(),
rhs=self.rhs.save()
)
@classmethod
def load(cls, u_desc: dict):
lhs = u_desc['lhs']
rhs = u_desc['rhs']
return cls(
lhs=getattr(sys.modules[__name__], lhs['u_type']).load(lhs),
rhs=getattr(sys.modules[__name__], rhs['u_type']).load(rhs),
)
def __eq__(self, other):
return self.lhs == other.lhs and self.rhs == other.rhs and isinstance(other, XorUniverse)
return cls(name=name,
base_universe=base_universe,
exclude_universe=exclude_universe,
special_codes=special_codes,
filter_cond=filter_cond)
def load_universe(u_desc: dict):
u_name = u_desc['u_type']
if u_name == 'Universe':
return Universe.load(u_desc)
elif u_name == 'OrUniverse':
return OrUniverse.load(u_desc)
elif u_name == 'AndUniverse':
return AndUniverse.load(u_desc)
elif u_name == 'XorUniverse':
return XorUniverse.load(u_desc)
if __name__ == '__main__':
......@@ -141,10 +181,10 @@ if __name__ == '__main__':
from alphamind.data.engines.sqlengine import SqlEngine
engine = SqlEngine()
universe = Universe('ss', ['ashare_ex'], exclude_universe=['hs300', 'zz500'], special_codes=[603138])
universe = Universe('custom', ['zz800'], exclude_universe=['Bank'])
print(universe.query(engine,
start_date='2017-12-21',
end_date='2017-12-25'))
start_date='2018-04-26',
end_date='2018-04-26'))
print(universe.query(engine,
dates=['2017-12-21', '2017-12-25']))
......@@ -17,13 +17,12 @@ from alphamind.data.dbmodel.models import SpecificRiskLong
from alphamind.data.dbmodel.models import Uqer
from alphamind.data.dbmodel.models import Gogoal
from alphamind.data.dbmodel.models import Experimental
from alphamind.data.dbmodel.models import LegacyFactor
from alphamind.data.dbmodel.models import Tiny
from alphamind.data.dbmodel.models import RiskExposure
from alphamind.data.dbmodel.models import Categories
from alphamind.data.engines.industries import INDUSTRY_MAPPING
factor_tables = [Market, RiskExposure, Uqer, Gogoal, Experimental, LegacyFactor, Tiny]
factor_tables = [Market, RiskExposure, Uqer, Gogoal, Experimental, Categories]
def _map_risk_model_table(risk_model: str) -> tuple:
......
......@@ -7,6 +7,7 @@ Created on 2017-8-8
from typing import Optional
import numpy as np
from scipy.stats import rankdata
import alphamind.utilities as utils
......@@ -22,8 +23,30 @@ def rank(x: np.ndarray, groups: Optional[np.ndarray]=None) -> np.ndarray:
start = 0
for diff_loc in index_diff:
curr_idx = order[start:diff_loc + 1]
res[curr_idx] = x[curr_idx].argsort(axis=0)
res[curr_idx] = rankdata(x[curr_idx]).astype(float) - 1.
start = diff_loc + 1
return res
else:
return x.argsort(axis=0).argsort(axis=0)
return (rankdata(x).astype(float) - 1.).reshape((-1, 1))
def percentile(x: np.ndarray, groups: Optional[np.ndarray]=None) -> np.ndarray:
if x.ndim == 1:
x = x.reshape((-1, 1))
if groups is not None:
res = np.zeros(x.shape, dtype=int)
index_diff, order = utils.groupby(groups)
start = 0
for diff_loc in index_diff:
curr_idx = order[start:diff_loc + 1]
curr_values = x[curr_idx]
length = len(curr_values) - 1. if len(curr_values) > 1 else 1.
res[curr_idx] = (rankdata(curr_values).astype(float) - 1.) / length
start = diff_loc + 1
return res
else:
length = len(x) - 1. if len(x) > 1 else 1.
return ((rankdata(x).astype(float) - 1.) / length).reshape((-1, 1))
......@@ -5,41 +5,100 @@ Created on 2017-9-5
@author: cheng.li
"""
import sqlalchemy as sa
import arrow
import numpy as np
import math
import pandas as pd
import numpy as np
from PyFin.api import *
from alphamind.api import *
from alphamind.data.dbmodel.models import Models
from alphamind.model.linearmodel import LinearRegression
engine = SqlEngine('postgresql+psycopg2://postgres:A12345678!@10.63.6.220/alpha')
factor = 'ROE'
universe = Universe('custom', ['zz800'])
start_date = '2010-01-01'
end_date = '2018-04-26'
freq = '10b'
category = 'sw_adj'
level = 1
horizon = map_freq(freq)
ref_dates = makeSchedule(start_date, end_date, freq, 'china.sse')
def factor_analysis(factor):
engine = SqlEngine()
factors = {
'f1': CSQuantiles(factor),
'f2': CSQuantiles(factor, groups='sw1_adj'),
'f3': LAST(factor)
}
total_factor = engine.fetch_factor_range(universe, factors, dates=ref_dates)
_, risk_exp = engine.fetch_risk_model_range(universe, dates=ref_dates)
industry = engine.fetch_industry_range(universe, dates=ref_dates, category=category, level=level)
rets = engine.fetch_dx_return_range(universe, horizon=horizon, offset=1, dates=ref_dates)
total_factor = pd.merge(total_factor, industry[['trade_date', 'code', 'industry']], on=['trade_date', 'code'])
total_factor = pd.merge(total_factor, risk_exp, on=['trade_date', 'code'])
total_factor = pd.merge(total_factor, rets, on=['trade_date', 'code']).dropna()
df_ret = pd.DataFrame(columns=['f1', 'f2', 'f3'])
df_ic = pd.DataFrame(columns=['f1', 'f2', 'f3'])
total_factor_groups = total_factor.groupby('trade_date')
for date, this_factors in total_factor_groups:
raw_factors = this_factors['f3'].values
industry_exp = this_factors[industry_styles + ['COUNTRY']].values.astype(float)
processed_values = factor_processing(raw_factors, pre_process=[], risk_factors=industry_exp,
post_process=[percentile])
this_factors['f3'] = processed_values
factor_values = this_factors[['f1', 'f2', 'f3']].values
positions = (factor_values >= 0.8) * 1.
positions[factor_values <= 0.2] = -1
positions /= np.abs(positions).sum(axis=0)
ret_values = this_factors.dx.values @ positions
df_ret.loc[date] = ret_values
ic_values = this_factors[['dx', 'f1', 'f2', 'f3']].corr().values[0, 1:]
df_ic.loc[date] = ic_values
x = np.random.randn(1000, 3)
y = np.random.randn(1000)
print(f"{factor} is finished")
model = LinearRegression(['a', 'b', 'c'])
model.fit(x, y)
return {'ic': (df_ic.mean(axis=0), df_ic.std(axis=0) / math.sqrt(len(df_ic))),
'ret': (df_ret.mean(axis=0), df_ret.std(axis=0) / math.sqrt(len(df_ic))),
'factor': factor}
model_desc = model.save()
df = pd.DataFrame()
if __name__ == '__main__':
new_row = dict(trade_date='2017-09-05',
portfolio_name='test',
model_type='LinearRegression',
version=1,
model_desc=model_desc,
update_time=arrow.now().format())
from dask.distributed import Client
df = df.append([new_row])
try:
client = Client("10.63.6.176:8786")
cols = pd.MultiIndex.from_product([['mean', 'std'], ['raw', 'peer', 'neutralized']])
factors_ret = pd.DataFrame(columns=cols)
factors_ic = pd.DataFrame(columns=cols)
df.to_sql(Models.__table__.name, engine.engine,
if_exists='append',
index=False,
dtype={'model_desc': sa.types.JSON})
factors = ['ep_q',
'roe_q',
'SGRO',
'GREV',
'IVR',
'ILLIQUIDITY',
'con_target_price',
'con_pe_rolling_order',
'DividendPaidRatio']
l = client.map(factor_analysis, factors)
results = client.gather(l)
model_in_db = engine.fetch_model('2017-09-05')
for res in results:
factor = res['factor']
factors_ret.loc[factor, 'mean'] = res['ret'][0].values
factors_ret.loc[factor, 'std'] = res['ret'][1].values
print(model_in_db)
factors_ic.loc[factor, 'mean'] = res['ic'][0].values
factors_ic.loc[factor, 'std'] = res['ic'][1].values
print(factors_ret)
finally:
client.close()
......@@ -20,6 +20,7 @@ from alphamind.data.winsorize import winsorize_normal
from alphamind.data.rank import rank
from alphamind.data.standardize import standardize
from alphamind.model.loader import load_model
from alphamind.model.linearmodel import ConstLinearModel
PROCESS_MAPPING = {
'winsorize_normal': winsorize_normal,
......@@ -48,7 +49,6 @@ class DataMeta(object):
warm_start: int = 0,
data_source: str = None):
self.data_source = data_source
self.engine = SqlEngine(self.data_source)
self.freq = freq
self.universe = universe
self.batch = batch
......@@ -107,7 +107,7 @@ class DataMeta(object):
def fetch_train_data(self,
ref_date,
alpha_model: ModelBase):
return fetch_train_phase(self.engine,
return fetch_train_phase(SqlEngine(self.data_source),
alpha_model.formulas,
ref_date,
self.freq,
......@@ -123,7 +123,7 @@ class DataMeta(object):
def fetch_predict_data(self,
ref_date: str,
alpha_model: ModelBase):
return fetch_predict_phase(self.engine,
return fetch_predict_phase(SqlEngine(self.data_source),
alpha_model.formulas,
ref_date,
self.freq,
......@@ -144,11 +144,13 @@ def train_model(ref_date: str,
x_values: pd.DataFrame = None,
y_values: pd.DataFrame = None):
base_model = copy.deepcopy(alpha_model)
if x_values is None:
train_data = data_meta.fetch_train_data(ref_date, alpha_model)
x_values = train_data['train']['x']
y_values = train_data['train']['y']
base_model.fit(x_values, y_values)
if not isinstance(alpha_model, ConstLinearModel):
if x_values is None:
train_data = data_meta.fetch_train_data(ref_date, alpha_model)
x_values = train_data['train']['x']
y_values = train_data['train']['y']
base_model.fit(x_values, y_values)
return base_model
......@@ -238,19 +240,38 @@ class Composer(object):
if __name__ == '__main__':
from PyFin.api import LAST
from alphamind.data.engines.sqlengine import risk_styles, industry_styles
from alphamind.model.linearmodel import LinearRegression
from alphamind.api import (risk_styles,
industry_styles,
standardize,
winsorize_normal,
DataMeta,
LinearRegression,
fetch_data_package,
map_freq)
from PyFin.api import LAST, SHIFT
freq = '60b'
universe = Universe('custom', ['ashare_ex'])
freq = '20b'
batch = 0
neutralized_risk = risk_styles + industry_styles
batch = 1
neutralized_risk = industry_styles
risk_model = 'short'
pre_process = [winsorize_normal, standardize]
post_process = [standardize]
warm_start = 0
data_source = "postgres+psycopg2://postgres:we083826@localhost/alpha"
warm_start = 3
data_source = None
horizon = map_freq(freq)
engine = SqlEngine(data_source)
fit_intercept = True
kernal_feature = 'roe_q'
regress_features = {kernal_feature: LAST(kernal_feature),
kernal_feature + '_l1': SHIFT(kernal_feature, 1),
kernal_feature + '_l2': SHIFT(kernal_feature, 2),
kernal_feature + '_l3': SHIFT(kernal_feature, 3)
}
const_features = {kernal_feature: LAST(kernal_feature)}
fit_target = [kernal_feature]
data_meta = DataMeta(freq=freq,
universe=universe,
......@@ -262,9 +283,28 @@ if __name__ == '__main__':
warm_start=warm_start,
data_source=data_source)
alpha_model = LinearRegression({'roe_q': LAST('roe_q')}, fit_target='roe_q')
alpha_model = LinearRegression(features=regress_features, fit_intercept=True, fit_target=fit_target)
composer = Composer(alpha_model=alpha_model, data_meta=data_meta)
ref_date = '2018-01-30'
composer.train(ref_date)
res = composer.predict(ref_date)
\ No newline at end of file
start_date = '2014-01-01'
end_date = '2016-01-01'
regression_model = LinearRegression(features=regress_features, fit_intercept=fit_intercept, fit_target=fit_target)
regression_composer = Composer(alpha_model=regression_model, data_meta=data_meta)
#regression_composer.train('2010-07-07')
data_package1 = fetch_data_package(engine,
alpha_factors=[kernal_feature],
start_date=start_date,
end_date=end_date,
frequency=freq,
universe=universe,
benchmark=906,
warm_start=warm_start,
batch=1,
neutralized_risk=neutralized_risk,
pre_process=pre_process,
post_process=post_process,
fit_target=fit_target)
pass
\ No newline at end of file
......@@ -30,7 +30,7 @@ def _merge_df(engine, names, factor_df, target_df, universe, dates, risk_model,
risk_df = engine.fetch_risk_model_range(universe, dates=dates, risk_model=risk_model)[1]
used_neutralized_risk = list(set(total_risk_factors).difference(names))
risk_df = risk_df[['trade_date', 'code'] + used_neutralized_risk].dropna()
target_df = pd.merge(target_df, risk_df, on=['trade_date', 'code'])
target_df = pd.merge(target_df, risk_df, on=['trade_date', 'code']).dropna()
if neutralized_risk:
train_x = pd.merge(factor_df, risk_df, on=['trade_date', 'code'])
......@@ -220,7 +220,7 @@ def fetch_data_package(engine: SqlEngine,
frequency,
universe,
benchmark,
warm_start,
warm_start + batch,
fit_target=fit_target)
target_df, dates, date_label, risk_exp, x_values, y_values, train_x, train_y, codes = \
......@@ -255,7 +255,17 @@ def fetch_data_package(engine: SqlEngine,
ret = dict()
ret['x_names'] = names
ret['settlement'] = target_df
ret['settlement'] = target_df[target_df.trade_date >= start_date]
train_x_buckets = {k: train_x_buckets[k] for k in train_x_buckets if k.strftime('%Y-%m-%d') >= start_date}
train_y_buckets = {k: train_y_buckets[k] for k in train_y_buckets if k.strftime('%Y-%m-%d') >= start_date}
train_risk_buckets = {k: train_risk_buckets[k] for k in train_risk_buckets if k.strftime('%Y-%m-%d') >= start_date}
predict_x_buckets = {k: predict_x_buckets[k] for k in predict_x_buckets if k.strftime('%Y-%m-%d') >= start_date}
predict_y_buckets = {k: predict_y_buckets[k] for k in predict_y_buckets if k.strftime('%Y-%m-%d') >= start_date}
predict_risk_buckets = {k: predict_risk_buckets[k] for k in predict_risk_buckets if k.strftime('%Y-%m-%d') >= start_date}
predict_codes_bucket = {k: predict_codes_bucket[k] for k in predict_codes_bucket if k.strftime('%Y-%m-%d') >= start_date}
ret['train'] = {'x': train_x_buckets, 'y': train_y_buckets, 'risk': train_risk_buckets}
ret['predict'] = {'x': predict_x_buckets, 'y': predict_y_buckets, 'risk': predict_risk_buckets,
'code': predict_codes_bucket}
......@@ -403,7 +413,7 @@ def fetch_predict_phase(engine,
train_x = pd.merge(factor_df, target_df, on=['trade_date', 'code'], how='left')
risk_exp = None
train_x.dropna(inplace=True)
train_x.dropna(inplace=True, subset=train_x.columns[:-1])
x_values = train_x[names].values.astype(float)
y_values = train_x[['dx']].values.astype(float)
......@@ -456,18 +466,3 @@ def fetch_predict_phase(engine,
return ret
if __name__ == '__main__':
from alphamind.api import risk_styles, industry_styles, standardize
engine = SqlEngine('postgresql+psycopg2://postgres:A12345678!@10.63.6.220/alpha')
universe = Universe('zz500', ['hs300', 'zz500'])
neutralized_risk = risk_styles + industry_styles
res = fetch_train_phase(engine, ['ep_q'],
'2012-01-05',
'5b',
universe,
2,
neutralized_risk=neutralized_risk,
post_process=[standardize],
fit_target='closePrice')
print(res)
......@@ -22,7 +22,7 @@ class ConstLinearModelImpl(object):
self.weights = weights.flatten()
def fit(self, x: np.ndarray, y: np.ndarray):
pass
raise NotImplementedError("Const linear model doesn't offer fit methodology")
def predict(self, x: np.ndarray):
return x @ self.weights
......
......@@ -27,6 +27,8 @@ class BoundaryDirection(IntEnum):
class BoundaryType(IntEnum):
ABSOLUTE = 0
RELATIVE = 1
MAXABSREL = 2
MINABSREL = 3
class BoundaryImpl(object):
......@@ -34,14 +36,14 @@ class BoundaryImpl(object):
def __init__(self,
direction: BoundaryDirection,
b_type: BoundaryType,
val: float):
val):
self.direction = direction
self.b_type = b_type
self.val = val
self._validation()
def _validation(self):
pyFinAssert(self.b_type == BoundaryType.ABSOLUTE or self.b_type == BoundaryType.RELATIVE,
pyFinAssert(self.b_type in [BoundaryType.ABSOLUTE, BoundaryType.RELATIVE, BoundaryType.MAXABSREL, BoundaryType.MINABSREL],
ValueError,
"Boundary Type {0} is not recognized".format(self.b_type))
......@@ -52,6 +54,28 @@ class BoundaryImpl(object):
def __call__(self, center: float):
if self.b_type == BoundaryType.ABSOLUTE:
return self.val + center
elif self.b_type == BoundaryType.MAXABSREL:
abs_threshold = self.val[0]
rel_threshold = self.val[1]
if self.direction == BoundaryDirection.LOWER:
rel_bound = center - abs(center) * rel_threshold
abs_bound = center - abs_threshold
return min(rel_bound, abs_bound)
elif self.direction == BoundaryDirection.UPPER:
rel_bound = center + abs(center) * rel_threshold
abs_bound = center + abs_threshold
return max(rel_bound, abs_bound)
elif self.b_type == BoundaryType.MINABSREL:
abs_threshold = self.val[0]
rel_threshold = self.val[1]
if self.direction == BoundaryDirection.LOWER:
rel_bound = center - abs(center) * rel_threshold
abs_bound = center - abs_threshold
return max(rel_bound, abs_bound)
elif self.direction == BoundaryDirection.UPPER:
rel_bound = center + abs(center) * rel_threshold
abs_bound = center + abs_threshold
return min(rel_bound, abs_bound)
else:
pyFinAssert(center >= 0., ValueError, "relative bounds only support positive back bone value")
return self.val * center
......@@ -106,9 +130,7 @@ class LinearConstraints(object):
bounds: Dict[str, BoxBoundary],
cons_mat: pd.DataFrame,
backbone: np.ndarray=None):
pyFinAssert(len(bounds) == cons_mat.shape[1], "Number of bounds should be same as number of col of cons_mat")
self.names = list(bounds.keys())
self.names = list(set(bounds.keys()).intersection(set(cons_mat.columns)))
self.bounds = bounds
self.cons_mat = cons_mat
self.backbone = backbone
......
......@@ -35,7 +35,7 @@ def linear_builder(er: np.ndarray,
if isinstance(ubound, float):
ubound = np.ones(n) * ubound
if not turn_over_target:
if not turn_over_target or current_position is None:
cons_matrix = np.concatenate((risk_constraints.T, risk_lbound, risk_ubound), axis=1)
opt = LPOptimizer(cons_matrix, lbound, ubound, -er, method)
......
# -*- coding: utf-8 -*-
"""
Created on 2017-9-14
Created on 2018-5-3
@author: cheng.li
"""
import datetime as dt
import copy
import numpy as np
import pandas as pd
import alphamind.data as data_module
import alphamind.model as model_module
from alphamind.data.engines.universe import Universe
from alphamind.model.modelbase import ModelBase
from alphamind.data.engines.sqlengine import industry_styles
from PyFin.api import makeSchedule
from PyFin.api import advanceDateByCalendar
from alphamind.utilities import map_freq
from alphamind.utilities import alpha_logger
from alphamind.model.composer import train_model
from alphamind.portfolio.constraints import LinearConstraints
from alphamind.portfolio.constraints import BoundaryType
from alphamind.portfolio.constraints import create_box_bounds
from alphamind.execution.naiveexecutor import NaiveExecutor
from alphamind.data.engines.sqlengine import SqlEngine
from alphamind.data.engines.sqlengine import risk_styles
from alphamind.model.data_preparing import fetch_data_package
from alphamind.model.data_preparing import fetch_predict_phase
from alphamind.model.data_preparing import fetch_train_phase
def load_process(names: list) -> list:
return [getattr(data_module, name) for name in names]
def load_neutralize_risks(names: list) -> list:
risks = []
for name in names:
if name == 'industry_styles':
risks.extend(industry_styles)
elif name == 'risk_styles':
risks.extend(risk_styles)
else:
risks.append(name)
return risks
from alphamind.data.engines.sqlengine import industry_styles
from alphamind.data.engines.sqlengine import macro_styles
from alphamind.data.processing import factor_processing
from alphamind.analysis.factoranalysis import er_portfolio_analysis
all_styles = risk_styles + industry_styles + macro_styles
def load_model_meta(name: str) -> ModelBase:
return getattr(model_module, name)
class RunningSetting(object):
def load_universe(universe: list) -> Universe:
return Universe(universe[0], universe[1])
def __init__(self,
lbound=None,
ubound=None,
weights_bandwidth=None,
rebalance_method='risk_neutral',
bounds=None,
**kwargs):
self.lbound = lbound
self.ubound = ubound
self.weights_bandwidth = weights_bandwidth
self.executor = NaiveExecutor()
self.rebalance_method = rebalance_method
self.bounds = bounds
self.more_opts = kwargs
class Strategy(object):
def __init__(self,
data_source,
strategy_desc: dict,
cache_start_date=None,
cache_end_date=None):
self.data_source = data_source
self.strategy_name = strategy_desc['strategy_name']
self.pre_process = load_process(strategy_desc['data_process']['pre_process'])
self.post_process = load_process(strategy_desc['data_process']['pre_process'])
self.neutralize_risk = load_neutralize_risks(strategy_desc['data_process']['neutralize_risk'])
self.risk_model = strategy_desc['risk_model']
self.model_type = load_model_meta(strategy_desc['alpha_model'])
self.parameters = strategy_desc['parameters']
self.features = strategy_desc['features']
self.model = self.model_type(features=self.features, **self.parameters)
self.is_const_model = isinstance(self.model, model_module.ConstLinearModel)
if self.is_const_model:
self.weights = strategy_desc['weights']
self.freq = strategy_desc['freq']
self.universe = load_universe(strategy_desc['universe'])
self.benchmark = strategy_desc['benchmark']
self.batch = strategy_desc['batch']
self.warm_start = strategy_desc['warm_start']
if cache_start_date and cache_end_date:
self.cached_data = fetch_data_package(self.data_source,
self.features,
cache_start_date,
cache_end_date,
self.freq,
self.universe,
self.benchmark,
self.warm_start,
self.batch,
self.neutralize_risk,
self.risk_model,
self.pre_process,
self.post_process)
# some cached data to fast processing
settlement_data = self.cached_data['settlement']
self.settle_dfs = settlement_data.set_index('code').groupby('trade_date')
self.scheduled_dates = set(k.strftime('%Y-%m-%d') for k in self.cached_data['train']['x'].keys())
alpha_model,
data_meta,
universe,
start_date,
end_date,
freq,
benchmark=905,
industry_cat='sw_adj',
industry_level=1,
dask_client=None):
self.alpha_model = alpha_model
self.data_meta = data_meta
self.universe = universe
self.benchmark = benchmark
self.dates = makeSchedule(start_date, end_date, freq, 'china.sse')
self.dates = [d.strftime('%Y-%m-%d') for d in self.dates]
self.industry_cat = industry_cat
self.industry_level = industry_level
self.freq = freq
self.horizon = map_freq(freq)
self.engine = SqlEngine(self.data_meta.data_source)
self.dask_client = dask_client
def prepare_backtest_data(self):
total_factors = self.engine.fetch_factor_range(self.universe,
self.alpha_model.formulas,
dates=self.dates)
alpha_logger.info("alpha factor data loading finished ...")
total_industry = self.engine.fetch_industry_matrix_range(self.universe,
dates=self.dates,
category=self.industry_cat,
level=self.industry_level)
alpha_logger.info("industry data loading finished ...")
total_benchmark = self.engine.fetch_benchmark_range(dates=self.dates,
benchmark=self.benchmark)
alpha_logger.info("benchmark data loading finished ...")
total_risk_cov, total_risk_exposure = self.engine.fetch_risk_model_range(
self.universe,
dates=self.dates,
risk_model=self.data_meta.risk_model
)
alpha_logger.info("risk_model data loading finished ...")
total_returns = self.engine.fetch_dx_return_range(self.universe,
dates=self.dates,
horizon=self.horizon,
offset=1)
alpha_logger.info("returns data loading finished ...")
total_data = pd.merge(total_factors, total_industry, on=['trade_date', 'code'])
total_data = pd.merge(total_data, total_benchmark, on=['trade_date', 'code'], how='left')
total_data.fillna({'weight': 0.}, inplace=True)
total_data = pd.merge(total_data, total_returns, on=['trade_date', 'code'])
total_data = pd.merge(total_data, total_risk_exposure, on=['trade_date', 'code'])
is_in_benchmark = (total_data.weight > 0.).astype(float).values.reshape((-1, 1))
total_data.loc[:, 'benchmark'] = is_in_benchmark
total_data.loc[:, 'total'] = np.ones_like(is_in_benchmark)
total_data.sort_values(['trade_date', 'code'], inplace=True)
self.index_return = self.engine.fetch_dx_return_index_range(self.benchmark,
dates=self.dates,
horizon=self.horizon,
offset=1).set_index('trade_date')
self.total_data = total_data
self.total_risk_cov = total_risk_cov
def _create_lu_bounds(self, running_setting, codes, benchmark_w):
codes = np.array(codes)
if running_setting.weights_bandwidth:
lbound = np.maximum(0., benchmark_w - running_setting.weights_bandwidth)
ubound = running_setting.weights_bandwidth + benchmark_w
lb = running_setting.lbound
ub = running_setting.ubound
if lb or ub:
if not isinstance(lb, dict):
lbound = np.ones_like(benchmark_w) * lb
else:
lbound = np.zeros_like(benchmark_w)
for c in lb:
lbound[codes == c] = lb[c]
if 'other' in lb:
for i, c in enumerate(codes):
if c not in lb:
lbound[i] = lb['other']
if not isinstance(ub, dict):
ubound = np.ones_like(benchmark_w) * ub
else:
ubound = np.ones_like(benchmark_w)
for c in ub:
ubound[codes == c] = ub[c]
if 'other' in ub:
for i, c in enumerate(codes):
if c not in ub:
ubound[i] = ub['other']
return lbound, ubound
def run(self, running_setting):
alpha_logger.info("starting backting ...")
total_data_groups = self.total_data.groupby('trade_date')
rets = []
turn_overs = []
leverags = []
previous_pos = pd.DataFrame()
executor = copy.deepcopy(running_setting.executor)
positions = pd.DataFrame()
if self.dask_client is None:
models = {}
for ref_date, _ in self.total_data_groups:
models[ref_date] = train_model(ref_date.strftime('%Y-%m-%d'), self.alpha_model, self.data_meta)
else:
self.cached_data = None
self.scheduled_dates = None
def worker(parameters):
new_model = train_model(parameters[0].strftime('%Y-%m-%d'), parameters[1], parameters[2])
return parameters[0], new_model
def cached_dates(self):
return sorted(self.scheduled_dates)
l = self.dask_client.map(worker, [(d[0], self.alpha_model, self.data_meta) for d in total_data_groups])
results = self.dask_client.gather(l)
models = dict(results)
def model_train(self, ref_date: str):
for ref_date, this_data in total_data_groups:
new_model = models[ref_date]
codes = this_data.code.values.tolist()
if not self.is_const_model:
if self.cached_data and ref_date in self.scheduled_dates:
ref_date = dt.datetime.strptime(ref_date, '%Y-%m-%d')
ne_x = self.cached_data['train']['x'][ref_date]
ne_y = self.cached_data['train']['y'][ref_date]
if previous_pos.empty:
current_position = None
else:
data = fetch_train_phase(self.data_source,
self.features,
ref_date,
self.freq,
self.universe,
self.batch,
self.neutralize_risk,
self.risk_model,
self.pre_process,
self.post_process,
self.warm_start)
ne_x = data['train']['x']
ne_y = data['train']['y']
self.model.fit(ne_x, ne_y)
def model_predict(self, ref_date: str) -> pd.DataFrame:
if self.cached_data and ref_date in self.scheduled_dates:
ref_date = dt.datetime.strptime(ref_date, '%Y-%m-%d')
ne_x = self.cached_data['predict']['x'][ref_date]
settlement_data = self.cached_data['settlement']
codes = settlement_data.loc[settlement_data.trade_date == ref_date, 'code'].values
else:
data = fetch_predict_phase(self.data_source,
self.features,
ref_date,
self.freq,
self.universe,
self.batch,
self.neutralize_risk,
self.risk_model,
self.pre_process,
self.post_process,
self.warm_start)
previous_pos.set_index('code', inplace=True)
remained_pos = previous_pos.loc[codes]
ne_x = data['predict']['x']
codes = data['predict']['code']
remained_pos.fillna(0., inplace=True)
current_position = remained_pos.weight.values
prediction = self.model.predict(ne_x).flatten()
return pd.DataFrame({'prediction': prediction,
'code': codes})
def settlement(self, ref_date: str, prediction: pd.DataFrame) -> float:
settlement_data = self.settle_dfs.get_group(ref_date)[['dx', 'weight']]
if running_setting.rebalance_method == 'tv':
risk_cov = self.total_risk_cov[self.total_risk_cov.trade_date == ref_date]
sec_cov = self._generate_sec_cov(this_data, risk_cov)
else:
sec_cov = None
benchmark_w = this_data.weight.values
constraints = LinearConstraints(running_setting.bounds,
this_data,
benchmark_w)
lbound, ubound = self._create_lu_bounds(running_setting, codes, benchmark_w)
features = new_model.features
dfs = []
for name in features:
data_cleaned = this_data.dropna(subset=[name])
raw_factors = data_cleaned[[name]].values
new_factors = factor_processing(raw_factors,
pre_process=self.data_meta.pre_process,
risk_factors=data_cleaned[
self.data_meta.neutralized_risk].values.astype(
float) if self.data_meta.neutralized_risk else None,
post_process=self.data_meta.post_process)
df = pd.DataFrame(new_factors, columns=[name], index=data_cleaned.code)
dfs.append(df)
new_factors = pd.concat(dfs, axis=1)
new_factors = new_factors.loc[codes].fillna(new_factors.median())
er = new_model.predict(new_factors).astype(float)
alpha_logger.info('{0} re-balance: {1} codes'.format(ref_date, len(er)))
target_pos = self._calculate_pos(running_setting,
er,
this_data,
constraints,
benchmark_w,
lbound,
ubound,
sec_cov=sec_cov,
current_position=current_position)
target_pos['code'] = codes
target_pos['trade_date'] = ref_date
turn_over, executed_pos = executor.execute(target_pos=target_pos)
leverage = executed_pos.weight.abs().sum()
ret = executed_pos.weight.values @ (np.exp(this_data.dx.values) - 1.)
rets.append(np.log(1. + ret))
executor.set_current(executed_pos)
turn_overs.append(turn_over)
leverags.append(leverage)
positions = positions.append(executed_pos)
previous_pos = executed_pos
positions['benchmark_weight'] = self.total_data['weight'].values
positions['dx'] = self.total_data.dx.values
trade_dates = positions.trade_date.unique()
ret_df = pd.DataFrame({'returns': rets, 'turn_over': turn_overs, 'leverage': leverags}, index=trade_dates)
ret_df['benchmark_returns'] = self.index_return['dx']
ret_df.loc[advanceDateByCalendar('china.sse', ret_df.index[-1], self.freq)] = 0.
ret_df = ret_df.shift(1)
ret_df.iloc[0] = 0.
ret_df['excess_return'] = ret_df['returns'] - ret_df['benchmark_returns'] * ret_df['leverage']
return ret_df, positions
@staticmethod
def _generate_sec_cov(current_data, risk_cov):
risk_exposure = current_data[all_styles].values
risk_cov = risk_cov[all_styles].values
special_risk = current_data['srisk'].values
sec_cov = risk_exposure @ risk_cov @ risk_exposure.T / 10000 + np.diag(special_risk ** 2) / 10000
return sec_cov
def _calculate_pos(self, running_setting, er, data, constraints, benchmark_w, lbound, ubound, sec_cov,
current_position):
more_opts = running_setting.more_opts
target_pos, _ = er_portfolio_analysis(er,
industry=data.industry_name.values,
dx_return=None,
constraints=constraints,
detail_analysis=False,
benchmark=benchmark_w,
method=running_setting.rebalance_method,
lbound=lbound,
ubound=ubound,
current_position=current_position,
target_vol=more_opts.get('target_vol'),
cov=sec_cov,
turn_over_target=more_opts.get('turn_over_target'))
return target_pos
if __name__ == '__main__':
import json
import pprint
from alphamind.data.engines.sqlengine import SqlEngine
from PyFin.api import makeSchedule
engine = SqlEngine()
start_date = '2017-06-01'
end_date = '2017-09-14'
with open("sample_strategy.json", 'r') as fp:
strategy_desc = json.load(fp)
strategy = Strategy(engine, strategy_desc, start_date, end_date)
dates = strategy.cached_dates()
print(dates)
for date in dates:
strategy.model_train(date)
prediction = strategy.model_predict(date)
strategy.settlement(date, prediction)
\ No newline at end of file
from matplotlib import pyplot as plt
from dask.distributed import Client
from PyFin.api import CSQuantiles
from PyFin.api import CSMeanAdjusted
from PyFin.api import LAST
from alphamind.api import Universe
from alphamind.api import ConstLinearModel
from alphamind.api import XGBTrainer
from alphamind.api import DataMeta
from alphamind.api import industry_list
from alphamind.api import winsorize_normal
from alphamind.api import standardize
from matplotlib import pyplot as plt
from matplotlib.pylab import mpl
plt.style.use('seaborn-whitegrid')
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
start_date = '2017-01-01'
end_date = '2018-05-17'
freq = '10b'
neutralized_risk = None
universe = Universe('zz800')
dask_client = Client('10.63.6.176:8786')
alpha_factors = {
'f1': CSQuantiles(LAST('ILLIQUIDITY') * LAST('NegMktValue'), groups='sw1_adj'),
'f2': CSQuantiles('con_pe', groups='sw1_adj')
}
weights = {'f1': 1., 'f2': 0.}
# alpha_model = XGBTrainer(objective='reg:linear',
# booster='gbtree',
# n_estimators=300,
# eval_sample=0.25,
# features=alpha_factors)
alpha_model = ConstLinearModel(features=alpha_factors, weights=weights)
data_meta = DataMeta(freq=freq,
universe=universe,
batch=1,
neutralized_risk=neutralized_risk,
pre_process=None, # [winsorize_normal, standardize],
post_process=None, # [standardize],
warm_start=1)
industries = industry_list('sw_adj', 1)
total_risk_names = ['total', 'benchmark'] + industries
b_type = []
l_val = []
u_val = []
for name in total_risk_names:
if name == 'total':
b_type.append(BoundaryType.ABSOLUTE)
l_val.append(.0)
u_val.append(.0)
elif name == 'benchmark':
b_type.append(BoundaryType.RELATIVE)
l_val.append(0.8)
u_val.append(1.0)
else:
b_type.append(BoundaryType.MAXABSREL)
l_val.append((0.00, 0.0))
u_val.append((0.00, 0.0))
bounds = create_box_bounds(total_risk_names, b_type, l_val, u_val)
running_setting = RunningSetting(lbound=None,
ubound=None,
weights_bandwidth=0.01,
rebalance_method='risk_neutral',
bounds=bounds,
target_vol=0.05,
turn_over_target=0.4)
strategy = Strategy(alpha_model,
data_meta,
universe=universe,
start_date=start_date,
end_date=end_date,
freq=freq,
benchmark=906,
dask_client=dask_client)
strategy.prepare_backtest_data()
ret_df, positions = strategy.run(running_setting)
ret_df.rename(columns={'excess_return': '超额收益', 'turn_over': '换手率'}, inplace=True)
ret_df[['超额收益', '换手率']].cumsum().plot(secondary_y='换手率')
plt.title("原始ILLIQUIDITY因子")
plt.show()
positions.to_csv('d:/positions.csv', encoding='gbk')
......@@ -5,55 +5,75 @@ Created on 2018-4-17
@author: cheng.li
"""
import random
import unittest
import numpy as np
import pandas as pd
from sqlalchemy import select, and_
from PyFin.api import adjustDateByCalendar
from scipy.stats import rankdata
from sqlalchemy import select, and_, or_
from PyFin.api import makeSchedule
from PyFin.api import advanceDateByCalendar
from PyFin.api import bizDatesList
from PyFin.api import CSRank
from PyFin.api import CSQuantiles
from alphamind.tests.test_suite import SKIP_ENGINE_TESTS
from alphamind.tests.test_suite import DATA_ENGINE_URI
from alphamind.data.dbmodel.models import Universe as UniverseTable
from alphamind.data.dbmodel.models import Market
from alphamind.data.dbmodel.models import IndexMarket
from alphamind.data.dbmodel.models import IndexComponent
from alphamind.data.dbmodel.models import Uqer
from alphamind.data.dbmodel.models import RiskCovShort
from alphamind.data.dbmodel.models import RiskExposure
from alphamind.data.dbmodel.models import Industry
from alphamind.data.engines.sqlengine import SqlEngine
from alphamind.data.engines.universe import Universe
from alphamind.utilities import alpha_logger
@unittest.skipIf(SKIP_ENGINE_TESTS, "Omit sql engine tests")
class TestSqlEngine(unittest.TestCase):
def setUp(self):
self.engine = SqlEngine()
self.engine = SqlEngine(DATA_ENGINE_URI)
dates_list = bizDatesList('china.sse', '2010-10-01', '2018-04-27')
self.ref_date = random.choice(dates_list).strftime('%Y-%m-%d')
alpha_logger.info("Test date: {0}".format(self.ref_date))
def test_sql_engine_fetch_codes(self):
ref_date = adjustDateByCalendar('china.sse', '2017-01-31')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
query = select([UniverseTable.code]).where(
and_(
UniverseTable.trade_date == ref_date,
UniverseTable.universe.in_(['zz500', 'zz1000'])
or_(
UniverseTable.zz500 == 1,
UniverseTable.zz1000 == 1
)
)
).distinct()
)
df = pd.read_sql(query, con=self.engine.engine).sort_values('code')
self.assertListEqual(codes, list(df.code.values))
def test_sql_engine_fetch_codes_range(self):
ref_dates = makeSchedule('2017-01-01', '2017-06-30', '60b', 'china.sse')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', self.ref_date, '-6m'),
self.ref_date,
'60b', 'china.sse')
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes_range(universe, dates=ref_dates)
query = select([UniverseTable.trade_date, UniverseTable.code]).where(
and_(
UniverseTable.trade_date.in_(ref_dates),
UniverseTable.universe.in_(['zz500', 'zz1000'])
or_(
UniverseTable.zz500 == 1,
UniverseTable.zz1000 == 1
)
)
).distinct()
)
df = pd.read_sql(query, con=self.engine.engine).sort_values('code')
......@@ -62,11 +82,27 @@ class TestSqlEngine(unittest.TestCase):
expected_codes = list(sorted(df[df.trade_date == ref_date].code.values))
self.assertListEqual(calculated_codes, expected_codes)
def test_sdl_engine_fetch_codes_with_exclude_universe(self):
ref_date = self.ref_date
universe = Universe('zz500') - Universe('cyb')
codes = self.engine.fetch_codes(ref_date, universe)
query = select([UniverseTable.code]).where(
and_(
UniverseTable.trade_date == ref_date,
UniverseTable.zz500 == 1,
UniverseTable.cyb == 0
)
)
df = pd.read_sql(query, con=self.engine.engine).sort_values('code')
self.assertListEqual(codes, list(df.code.values))
def test_sql_engine_fetch_dx_return(self):
horizon = 4
offset = 1
ref_date = adjustDateByCalendar('china.sse', '2017-01-31')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
dx_return = self.engine.fetch_dx_return(ref_date, codes, horizon=horizon, offset=offset)
......@@ -86,8 +122,8 @@ class TestSqlEngine(unittest.TestCase):
horizon = 4
offset = 0
ref_date = adjustDateByCalendar('china.sse', '2017-01-31')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
dx_return = self.engine.fetch_dx_return(ref_date, codes, horizon=horizon, offset=offset)
......@@ -106,8 +142,10 @@ class TestSqlEngine(unittest.TestCase):
np.testing.assert_array_almost_equal(dx_return.dx.values, res.chgPct.values)
def test_sql_engine_fetch_dx_return_range(self):
ref_dates = makeSchedule('2017-01-01', '2017-06-30', '60b', 'china.sse')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', self.ref_date, '-6m'),
self.ref_date,
'60b', 'china.sse')
universe = Universe('zz500') + Universe('zz1000')
dx_return = self.engine.fetch_dx_return_range(universe,
dates=ref_dates,
......@@ -133,10 +171,40 @@ class TestSqlEngine(unittest.TestCase):
calculated_return = dx_return[dx_return.trade_date == ref_date]
np.testing.assert_array_almost_equal(calculated_return.dx.values, res.chgPct.values)
def test_sql_engine_fetch_dx_return_with_universe_adjustment(self):
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', '2017-01-26', '-6m'),
'2017-01-26',
'60b', 'china.sse')
universe = Universe('zz500')
dx_return = self.engine.fetch_dx_return_range(universe,
dates=ref_dates,
horizon=4,
offset=1)
codes = self.engine.fetch_codes_range(universe, dates=ref_dates)
groups = codes.groupby('trade_date')
for ref_date, g in groups:
start_date = advanceDateByCalendar('china.sse', ref_date, '2b')
end_date = advanceDateByCalendar('china.sse', ref_date, '6b')
query = select([Market.code, Market.chgPct]).where(
and_(
Market.trade_date.between(start_date, end_date),
Market.code.in_(g.code.unique().tolist())
)
)
df = pd.read_sql(query, con=self.engine.engine)
res = df.groupby('code').apply(lambda x: np.log(1. + x).sum())
calculated_return = dx_return[dx_return.trade_date == ref_date]
np.testing.assert_array_almost_equal(calculated_return.dx.values, res.chgPct.values)
def test_sql_engine_fetch_dx_return_index(self):
horizon = 4
offset = 1
ref_date = adjustDateByCalendar('china.sse', '2017-01-31')
ref_date = self.ref_date
dx_return = self.engine.fetch_dx_return_index(ref_date,
905,
horizon=horizon,
......@@ -157,7 +225,9 @@ class TestSqlEngine(unittest.TestCase):
np.testing.assert_array_almost_equal(dx_return.dx.values, res.chgPct.values)
def test_sql_engine_fetch_dx_return_index_range(self):
ref_dates = makeSchedule('2017-01-01', '2017-06-30', '60b', 'china.sse')
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', self.ref_date, '-6m'),
self.ref_date,
'60b', 'china.sse')
index_code = 906
dx_return = self.engine.fetch_dx_return_index_range(index_code,
......@@ -182,8 +252,8 @@ class TestSqlEngine(unittest.TestCase):
np.testing.assert_array_almost_equal(calculated_return.dx.values, res.chgPct.values)
def test_sql_engine_fetch_factor(self):
ref_date = adjustDateByCalendar('china.sse', '2017-01-31')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
factor = 'ROE'
......@@ -196,12 +266,14 @@ class TestSqlEngine(unittest.TestCase):
)
)
df = pd.read_sql(query, con=self.engine.engine).sort_values('code').dropna()
df = pd.read_sql(query, con=self.engine.engine).sort_values('code')
np.testing.assert_array_almost_equal(factor_data.ROE.values, df.ROE.values)
def test_sql_engine_fetch_factor_range(self):
ref_dates = makeSchedule('2017-01-01', '2017-06-30', '60b', 'china.sse')
universe = Universe('custom', ['zz500', 'zz1000'])
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', self.ref_date, '-6m'),
self.ref_date,
'60b', 'china.sse')
universe = Universe('zz500') + Universe('zz1000')
factor = 'ROE'
factor_data = self.engine.fetch_factor_range(universe, factor, dates=ref_dates)
......@@ -217,14 +289,16 @@ class TestSqlEngine(unittest.TestCase):
)
)
df = pd.read_sql(query, con=self.engine.engine).dropna()
df = pd.read_sql(query, con=self.engine.engine)
calculated_factor = factor_data[factor_data.trade_date == ref_date]
np.testing.assert_array_almost_equal(calculated_factor.ROE.values, df.ROE.values)
def test_sql_engine_fetch_factor_range_forward(self):
ref_dates = makeSchedule('2017-01-01', '2017-09-30', '60b', 'china.sse')
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', self.ref_date, '-6m'),
self.ref_date,
'60b', 'china.sse')
ref_dates = ref_dates + [advanceDateByCalendar('china.sse', ref_dates[-1], '60b').strftime('%Y-%m-%d')]
universe = Universe('custom', ['zz500', 'zz1000'])
universe = Universe('zz500') + Universe('zz1000')
factor = 'ROE'
factor_data = self.engine.fetch_factor_range_forward(universe, factor, dates=ref_dates)
......@@ -241,12 +315,14 @@ class TestSqlEngine(unittest.TestCase):
)
)
df = pd.read_sql(query, con=self.engine.engine).dropna()
df = pd.read_sql(query, con=self.engine.engine)
calculated_factor = factor_data[factor_data.trade_date == ref_date]
calculated_factor.set_index('code', inplace=True)
calculated_factor = calculated_factor.loc[df.code]
np.testing.assert_array_almost_equal(calculated_factor.dx.values, df.ROE.values)
def test_sql_engine_fetch_benchmark(self):
ref_date = adjustDateByCalendar('china.sse', '2017-01-31')
ref_date = self.ref_date
benchmark = 906
index_data = self.engine.fetch_benchmark(ref_date, benchmark)
......@@ -262,7 +338,9 @@ class TestSqlEngine(unittest.TestCase):
np.testing.assert_array_almost_equal(df.weight.values, index_data.weight.values)
def test_sql_engine_fetch_benchmark_range(self):
ref_dates = makeSchedule('2017-01-01', '2017-09-30', '60b', 'china.sse')
ref_dates = makeSchedule(advanceDateByCalendar('china.sse', self.ref_date, '-9m'),
self.ref_date,
'60b', 'china.sse')
benchmark = 906
index_data = self.engine.fetch_benchmark_range(benchmark, dates=ref_dates)
......@@ -280,4 +358,86 @@ class TestSqlEngine(unittest.TestCase):
np.testing.assert_array_almost_equal(calculated_data.weight.values, expected_data.weight.values)
def test_sql_engine_fetch_risk_model(self):
pass
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
risk_cov, risk_exp = self.engine.fetch_risk_model(ref_date, codes, risk_model='short')
self.assertListEqual(risk_cov.Factor.tolist(), risk_cov.columns[2:].tolist())
query = select([RiskCovShort]).where(
RiskCovShort.trade_date == ref_date
)
cov_df = pd.read_sql(query, con=self.engine.engine).sort_values('FactorID')
factors = cov_df.Factor.tolist()
np.testing.assert_array_almost_equal(
risk_cov[factors].values, cov_df[factors].values
)
query = select([RiskExposure]).where(
and_(
RiskExposure.trade_date == ref_date,
RiskExposure.code.in_(codes)
)
)
exp_df = pd.read_sql(query, con=self.engine.engine)
np.testing.assert_array_almost_equal(
exp_df[factors].values, risk_exp[factors].values
)
def test_sql_engine_fetch_industry_matrix(self):
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
ind_matrix = self.engine.fetch_industry_matrix(ref_date, codes, 'sw', 1)
query = select([Industry.code, Industry.industryName1]).where(
and_(
Industry.trade_date == ref_date,
Industry.industry == '申万行业分类',
Industry.code.in_(codes)
)
)
df = pd.read_sql(query, con=self.engine.engine)
df = pd.get_dummies(df, prefix="", prefix_sep="")
self.assertEqual(len(ind_matrix), len(df))
np.testing.assert_array_almost_equal(
df[ind_matrix.columns[2:]].values, ind_matrix.iloc[:, 2:].values
)
def test_sql_engine_fetch_factor_by_categories(self):
ref_date = self.ref_date
universe = Universe('zz500') + Universe('zz1000')
codes = self.engine.fetch_codes(ref_date, universe)
factor1 = {'f': CSRank('ROE', groups='sw1')}
factor2 = {'f': CSQuantiles('ROE', groups='sw1')}
raw_factor = 'ROE'
df1 = self.engine.fetch_factor(ref_date, factor1, codes)
df2 = self.engine.fetch_factor(ref_date, factor2, codes)
df3 = self.engine.fetch_factor(ref_date, raw_factor, codes)
ind_matrix = self.engine.fetch_industry_matrix(ref_date, codes, 'sw', 1)
cols = sorted(ind_matrix.columns[2:].tolist())
series = (ind_matrix[cols] * np.array(range(1, len(cols)+1))).sum(axis=1)
df3['cat'] = series.values
expected_rank = df3[['ROE', 'cat']].groupby('cat').transform(lambda x: rankdata(x.values) - 1.)
expected_rank[np.isnan(df3.ROE)] = np.nan
df3['rank'] = expected_rank['ROE'].values
np.testing.assert_array_almost_equal(df3['rank'].values,
df1['f'].values)
expected_quantile = df3[['ROE', 'cat']].groupby('cat').transform(lambda x: (rankdata(x.values) - 1.) / ((len(x) - 1.) if len(x) > 1 else 1.))
expected_quantile[np.isnan(df3.ROE)] = np.nan
df3['quantile'] = expected_quantile['ROE'].values
np.testing.assert_array_almost_equal(df3['quantile'].values,
df2['f'].values)
......@@ -6,42 +6,24 @@ Created on 2018-2-9
"""
import unittest
from PyFin.api import LAST
from alphamind.data.engines.universe import Universe
from alphamind.data.engines.universe import load_universe
class TestUniverse(unittest.TestCase):
def test_universe_equal(self):
universe1 = Universe('custom', ['zz500'])
universe2 = Universe('custom', ['zz500'])
universe1 = Universe('zz500')
universe2 = Universe('zz500')
self.assertEqual(universe1, universe2)
universe1 = Universe('custom', ['zz500'])
universe2 = Universe('custom', ['zz800'])
self.assertNotEqual(universe1, universe2)
filter_cond = LAST('x') > 1.
universe1 = Universe('custom', ['zz500'], filter_cond=filter_cond)
universe2 = Universe('custom', ['zz500'], filter_cond=filter_cond)
self.assertEqual(universe1, universe2)
universe1 = Universe('custom', ['zz500'], filter_cond=LAST('x') > 1.)
universe2 = Universe('custom', ['zz500'], filter_cond=LAST('x') > 2.)
universe1 = Universe('zz500')
universe2 = Universe('zz800')
self.assertNotEqual(universe1, universe2)
def test_universe_persistence(self):
universe = Universe('custom', ['zz500'])
univ_desc = universe.save()
loaded_universe = Universe.load(univ_desc)
self.assertEqual(universe.name, loaded_universe.name)
self.assertListEqual(universe.base_universe, loaded_universe.base_universe)
universe = Universe('custom', ['zz500'], filter_cond=LAST('x') > 1.)
universe = Universe('zz500')
univ_desc = universe.save()
loaded_universe = Universe.load(univ_desc)
loaded_universe = load_universe(univ_desc)
self.assertEqual(universe.name, loaded_universe.name)
self.assertListEqual(universe.base_universe, loaded_universe.base_universe)
self.assertEqual(str(universe.filter_cond), str(loaded_universe.filter_cond))
self.assertEqual(universe, loaded_universe)
\ No newline at end of file
......@@ -21,7 +21,7 @@ class TestComposer(unittest.TestCase):
def test_data_meta_persistence(self):
freq = '5b'
universe = Universe('custom', ['zz800'])
universe = Universe('zz800')
batch = 4
neutralized_risk = ['SIZE']
risk_model = 'long'
......@@ -55,7 +55,7 @@ class TestComposer(unittest.TestCase):
def test_composer_persistence(self):
freq = '5b'
universe = Universe('custom', ['zz800'])
universe = Universe('zz800')
batch = 4
neutralized_risk = ['SIZE']
risk_model = 'long'
......
......@@ -78,6 +78,34 @@ class TestConstraints(unittest.TestCase):
self.assertAlmostEqual(l, 1.76)
self.assertAlmostEqual(u, 2.42)
def test_max_abs_relative_boundary(self):
lower = BoundaryImpl(BoundaryDirection.LOWER,
BoundaryType.MAXABSREL,
(0.02, 0.2))
upper = BoundaryImpl(BoundaryDirection.UPPER,
BoundaryType.MAXABSREL,
(0.02, 0.2))
bound = BoxBoundary(lower, upper)
center = 2.2
l, u = bound.bounds(center)
self.assertAlmostEqual(l, 1.76)
self.assertAlmostEqual(u, 2.64)
def test_min_abs_relative_boundary(self):
lower = BoundaryImpl(BoundaryDirection.LOWER,
BoundaryType.MINABSREL,
(0.02, 0.2))
upper = BoundaryImpl(BoundaryDirection.UPPER,
BoundaryType.MINABSREL,
(0.02, 0.2))
bound = BoxBoundary(lower, upper)
center = 2.2
l, u = bound.bounds(center)
self.assertAlmostEqual(l, 2.18)
self.assertAlmostEqual(u, 2.22)
def test_create_box_bounds_single_value(self):
names = ['a', 'b', 'c']
b_type = BoundaryType.RELATIVE
......
......@@ -5,9 +5,15 @@ Created on 2017-4-25
@author: cheng.li
"""
import os
SKIP_ENGINE_TESTS = True
if not SKIP_ENGINE_TESTS:
DATA_ENGINE_URI = os.environ['DB_URI']
else:
DATA_ENGINE_URI = None
if __name__ == '__main__':
from simpleutils import add_parent_path
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": null,
......@@ -8,6 +15,7 @@
"source": [
"%matplotlib inline\n",
"\n",
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"from matplotlib import pyplot as plt\n",
......@@ -42,12 +50,12 @@
"horizon = map_freq(frequency)\n",
"weight_gap = 0.01\n",
"benchmark_code = 905\n",
"universe_name = ['zz800']\n",
"universe = Universe('custom', universe_name)\n",
"universe_name = 'zz800'\n",
"universe = Universe(universe_name)\n",
"ref_dates = makeSchedule(start_date, end_date, frequency, 'china.sse')\n",
"\n",
"executor = NaiveExecutor()\n",
"data_source = 'postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha'\n",
"data_source = os.environ['DB_URI']\n",
"engine = SqlEngine(data_source)"
]
},
......@@ -271,7 +279,7 @@
"metadata": {},
"outputs": [],
"source": [
"with pd.ExcelWriter(f'{universe_name[0]}_{benchmark_code}.xlsx', engine='xlsxwriter') as writer:\n",
"with pd.ExcelWriter(f'{universe_name}_{benchmark_code}.xlsx', engine='xlsxwriter') as writer:\n",
" factor_df.to_excel(writer, sheet_name='ret')\n",
" ic_df.to_excel(writer, sheet_name='ic')\n",
" factor_res.to_excel(writer, sheet_name='ret_stat')\n",
......@@ -309,7 +317,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"from cvxpy import *\n",
......@@ -31,7 +39,7 @@
"risk_penlty = 0.5\n",
"ref_date = '2018-02-08'\n",
"\n",
"engine = SqlEngine()\n",
"engine = SqlEngine(os.environ['DB_URI'])\n",
"universe = Universe('custom', ['ashare_ex'])\n",
"codes = engine.fetch_codes(ref_date, universe)\n",
"\n",
......@@ -199,8 +207,8 @@
"source": [
"def time_function(py_callable, n):\n",
" start = dt.datetime.now()\n",
" py_callable(n)\n",
" return (dt.datetime.now() - start).total_seconds()"
" val = py_callable(n)\n",
" return (dt.datetime.now() - start).total_seconds(), val"
]
},
{
......@@ -221,7 +229,8 @@
" sum_entries(w) == 1,]\n",
"\n",
" prob = Problem(objective, constraints)\n",
" prob.solve(verbose=False, solver='CVXOPT', display=False)"
" prob.solve(verbose=False, solver='CVXOPT', display=False)\n",
" return prob.value"
]
},
{
......@@ -252,7 +261,8 @@
" b = matrix(b)\n",
" \n",
" solvers.options['show_progress'] = False\n",
" sol = solvers.qp(P, q, G, h, A, b)"
" sol = solvers.qp(P, q, G, h, A, b)\n",
" return sol['primal objective']"
]
},
{
......@@ -268,7 +278,7 @@
" clb = np.ones(1)\n",
" cub = np.ones(1)\n",
" qpopt = QPOptimizer(signal, sec_cov_values, lbound, ubound, cons_matrix, clb, cub, 1.)\n",
" qpopt.feval()"
" return qpopt.feval()"
]
},
{
......@@ -286,27 +296,16 @@
"for i, n in enumerate(n_steps):\n",
" sec_cov_values = sec_cov_values_full[:n, :n]\n",
" signal = signal_full[:n]\n",
" cvxpy_times[i] = time_function(cvxpy, n) * 1000\n",
" cvxopt_times[i] = time_function(cvxopt, n) * 1000\n",
" ipopt_times[i] = time_function(ipopt, n) * 1000\n",
" cvxpy_times[i], val1 = time_function(cvxpy, n)\n",
" cvxopt_times[i], val2 = time_function(cvxopt, n)\n",
" ipopt_times[i], val3 = time_function(ipopt, n)\n",
" \n",
" print(\"{0:<8}{1:>12.2f}{2:>12.2f}{3:>12.2f}\".format(n, cvxpy_times[i], cvxopt_times[i], ipopt_times[i]))"
" np.testing.assert_almost_equal(val1, val2, 4)\n",
" np.testing.assert_almost_equal(val2, val3, 4)\n",
" \n",
" print(\"{0:<8}{1:>12.4f}{2:>12.4f}{3:>12.4f}\".format(n, cvxpy_times[i], cvxopt_times[i], ipopt_times[i]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
......@@ -331,7 +330,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"datetime.datetime(2018, 4, 16, 9, 33, 24, 663186)"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"%matplotlib inline\n",
"import os\n",
"import datetime as dt\n",
"import numpy as np\n",
"import pandas as pd\n",
......@@ -31,7 +28,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -42,10 +39,10 @@
"horizon = map_freq(freq)\n",
"neutralized_risk = risk_styles + industry_styles\n",
"universe = Universe(\"custom\", ['zz500'])\n",
"data_source = None\n",
"data_source = os.environ['DB_URI']\n",
"offset = 1\n",
"method = 'ls'\n",
"industry_name = 'sw_adj'\n",
"industry_name = 'sw'\n",
"industry_level = 1\n",
"\n",
"risk_model = 'short'\n",
......@@ -56,23 +53,17 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"alpha_factors = {\n",
" 'f01': LAST('ep_q'),\n",
" 'f02': LAST('roe_q'),\n",
" 'f03': LAST('GREV'),\n",
" 'f04': LAST('SGRO'),\n",
" 'f05': LAST('ILLIQUIDITY'),\n",
" 'f01': LAST('EPS'),\n",
" 'f02': LAST('ROE')\n",
" }\n",
"\n",
"weights = dict(f01=0.5,\n",
" f02=0.5,\n",
" f03=0.5,\n",
" f04=0.5,\n",
" f05=0.5,\n",
"weights = dict(f01=1.0,\n",
" f02=1.0,\n",
" )\n",
"\n",
"alpha_model = ConstLinearModel(features=alpha_factors, weights=weights)\n",
......@@ -80,7 +71,7 @@
"def predict_worker(params):\n",
" data_meta = DataMeta(freq=freq,\n",
" universe=universe,\n",
" batch=0,\n",
" batch=1,\n",
" neutralized_risk=neutralized_risk,\n",
" risk_model='short',\n",
" pre_process=[winsorize_normal, standardize],\n",
......@@ -94,17 +85,9 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Wall time: 18.6 s\n"
]
}
],
"outputs": [],
"source": [
"%%time\n",
"predicts = [predict_worker((d.strftime('%Y-%m-%d'), alpha_model)) for d in ref_dates]"
......@@ -112,114 +95,9 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 09:33:44,891 - ALPHA_MIND - INFO - 2017-04-10 full re-balance: 500\n",
"2018-04-16 09:33:45,185 - ALPHA_MIND - INFO - 2017-04-10 is finished\n",
"2018-04-16 09:33:45,190 - ALPHA_MIND - INFO - 2017-04-17 full re-balance: 500\n",
"2018-04-16 09:33:45,616 - ALPHA_MIND - INFO - 2017-04-17 is finished\n",
"2018-04-16 09:33:45,621 - ALPHA_MIND - INFO - 2017-04-24 full re-balance: 500\n",
"2018-04-16 09:33:45,748 - ALPHA_MIND - INFO - 2017-04-24 is finished\n",
"2018-04-16 09:33:45,753 - ALPHA_MIND - INFO - 2017-05-02 full re-balance: 500\n",
"2018-04-16 09:33:45,877 - ALPHA_MIND - INFO - 2017-05-02 is finished\n",
"2018-04-16 09:33:45,882 - ALPHA_MIND - INFO - 2017-05-09 full re-balance: 500\n",
"2018-04-16 09:33:46,010 - ALPHA_MIND - INFO - 2017-05-09 is finished\n",
"2018-04-16 09:33:46,015 - ALPHA_MIND - INFO - 2017-05-16 full re-balance: 500\n",
"2018-04-16 09:33:46,229 - ALPHA_MIND - INFO - 2017-05-16 is finished\n",
"2018-04-16 09:33:46,234 - ALPHA_MIND - INFO - 2017-05-23 full re-balance: 500\n",
"2018-04-16 09:33:46,363 - ALPHA_MIND - INFO - 2017-05-23 is finished\n",
"2018-04-16 09:33:46,368 - ALPHA_MIND - INFO - 2017-06-01 full re-balance: 500\n",
"2018-04-16 09:33:46,499 - ALPHA_MIND - INFO - 2017-06-01 is finished\n",
"2018-04-16 09:33:46,504 - ALPHA_MIND - INFO - 2017-06-08 full re-balance: 500\n",
"2018-04-16 09:33:46,635 - ALPHA_MIND - INFO - 2017-06-08 is finished\n",
"2018-04-16 09:33:46,641 - ALPHA_MIND - INFO - 2017-06-15 full re-balance: 500\n",
"2018-04-16 09:33:46,772 - ALPHA_MIND - INFO - 2017-06-15 is finished\n",
"2018-04-16 09:33:46,777 - ALPHA_MIND - INFO - 2017-06-22 full re-balance: 500\n",
"2018-04-16 09:33:46,908 - ALPHA_MIND - INFO - 2017-06-22 is finished\n",
"2018-04-16 09:33:46,913 - ALPHA_MIND - INFO - 2017-06-29 full re-balance: 500\n",
"2018-04-16 09:33:47,043 - ALPHA_MIND - INFO - 2017-06-29 is finished\n",
"2018-04-16 09:33:47,048 - ALPHA_MIND - INFO - 2017-07-06 full re-balance: 500\n",
"2018-04-16 09:33:47,177 - ALPHA_MIND - INFO - 2017-07-06 is finished\n",
"2018-04-16 09:33:47,183 - ALPHA_MIND - INFO - 2017-07-13 full re-balance: 500\n",
"2018-04-16 09:33:47,313 - ALPHA_MIND - INFO - 2017-07-13 is finished\n",
"2018-04-16 09:33:47,318 - ALPHA_MIND - INFO - 2017-07-20 full re-balance: 500\n",
"2018-04-16 09:33:47,450 - ALPHA_MIND - INFO - 2017-07-20 is finished\n",
"2018-04-16 09:33:47,455 - ALPHA_MIND - INFO - 2017-07-27 full re-balance: 500\n",
"2018-04-16 09:33:47,585 - ALPHA_MIND - INFO - 2017-07-27 is finished\n",
"2018-04-16 09:33:47,590 - ALPHA_MIND - INFO - 2017-08-03 full re-balance: 500\n",
"2018-04-16 09:33:47,719 - ALPHA_MIND - INFO - 2017-08-03 is finished\n",
"2018-04-16 09:33:47,725 - ALPHA_MIND - INFO - 2017-08-10 full re-balance: 500\n",
"2018-04-16 09:33:47,853 - ALPHA_MIND - INFO - 2017-08-10 is finished\n",
"2018-04-16 09:33:47,857 - ALPHA_MIND - INFO - 2017-08-17 full re-balance: 500\n",
"2018-04-16 09:33:47,989 - ALPHA_MIND - INFO - 2017-08-17 is finished\n",
"2018-04-16 09:33:47,994 - ALPHA_MIND - INFO - 2017-08-24 full re-balance: 500\n",
"2018-04-16 09:33:48,129 - ALPHA_MIND - INFO - 2017-08-24 is finished\n",
"2018-04-16 09:33:48,135 - ALPHA_MIND - INFO - 2017-08-31 full re-balance: 500\n",
"2018-04-16 09:33:48,266 - ALPHA_MIND - INFO - 2017-08-31 is finished\n",
"2018-04-16 09:33:48,271 - ALPHA_MIND - INFO - 2017-09-07 full re-balance: 500\n",
"2018-04-16 09:33:48,402 - ALPHA_MIND - INFO - 2017-09-07 is finished\n",
"2018-04-16 09:33:48,408 - ALPHA_MIND - INFO - 2017-09-14 full re-balance: 500\n",
"2018-04-16 09:33:48,536 - ALPHA_MIND - INFO - 2017-09-14 is finished\n",
"2018-04-16 09:33:48,541 - ALPHA_MIND - INFO - 2017-09-21 full re-balance: 500\n",
"2018-04-16 09:33:48,673 - ALPHA_MIND - INFO - 2017-09-21 is finished\n",
"2018-04-16 09:33:48,678 - ALPHA_MIND - INFO - 2017-09-28 full re-balance: 500\n",
"2018-04-16 09:33:48,812 - ALPHA_MIND - INFO - 2017-09-28 is finished\n",
"2018-04-16 09:33:48,817 - ALPHA_MIND - INFO - 2017-10-12 full re-balance: 500\n",
"2018-04-16 09:33:48,948 - ALPHA_MIND - INFO - 2017-10-12 is finished\n",
"2018-04-16 09:33:48,953 - ALPHA_MIND - INFO - 2017-10-19 full re-balance: 500\n",
"2018-04-16 09:33:49,086 - ALPHA_MIND - INFO - 2017-10-19 is finished\n",
"2018-04-16 09:33:49,091 - ALPHA_MIND - INFO - 2017-10-26 full re-balance: 500\n",
"2018-04-16 09:33:49,228 - ALPHA_MIND - INFO - 2017-10-26 is finished\n",
"2018-04-16 09:33:49,233 - ALPHA_MIND - INFO - 2017-11-02 full re-balance: 500\n",
"2018-04-16 09:33:49,362 - ALPHA_MIND - INFO - 2017-11-02 is finished\n",
"2018-04-16 09:33:49,367 - ALPHA_MIND - INFO - 2017-11-09 full re-balance: 500\n",
"2018-04-16 09:33:49,503 - ALPHA_MIND - INFO - 2017-11-09 is finished\n",
"2018-04-16 09:33:49,509 - ALPHA_MIND - INFO - 2017-11-16 full re-balance: 500\n",
"2018-04-16 09:33:49,643 - ALPHA_MIND - INFO - 2017-11-16 is finished\n",
"2018-04-16 09:33:49,648 - ALPHA_MIND - INFO - 2017-11-23 full re-balance: 500\n",
"2018-04-16 09:33:49,782 - ALPHA_MIND - INFO - 2017-11-23 is finished\n",
"2018-04-16 09:33:49,787 - ALPHA_MIND - INFO - 2017-11-30 full re-balance: 500\n",
"2018-04-16 09:33:49,920 - ALPHA_MIND - INFO - 2017-11-30 is finished\n",
"2018-04-16 09:33:49,925 - ALPHA_MIND - INFO - 2017-12-07 full re-balance: 500\n",
"2018-04-16 09:33:50,064 - ALPHA_MIND - INFO - 2017-12-07 is finished\n",
"2018-04-16 09:33:50,069 - ALPHA_MIND - INFO - 2017-12-14 full re-balance: 500\n",
"2018-04-16 09:33:50,201 - ALPHA_MIND - INFO - 2017-12-14 is finished\n",
"2018-04-16 09:33:50,205 - ALPHA_MIND - INFO - 2017-12-21 full re-balance: 500\n",
"2018-04-16 09:33:50,340 - ALPHA_MIND - INFO - 2017-12-21 is finished\n",
"2018-04-16 09:33:50,346 - ALPHA_MIND - INFO - 2017-12-28 full re-balance: 500\n",
"2018-04-16 09:33:50,490 - ALPHA_MIND - INFO - 2017-12-28 is finished\n",
"2018-04-16 09:33:50,496 - ALPHA_MIND - INFO - 2018-01-05 full re-balance: 500\n",
"2018-04-16 09:33:50,634 - ALPHA_MIND - INFO - 2018-01-05 is finished\n",
"2018-04-16 09:33:50,639 - ALPHA_MIND - INFO - 2018-01-12 full re-balance: 500\n",
"2018-04-16 09:33:50,855 - ALPHA_MIND - INFO - 2018-01-12 is finished\n",
"2018-04-16 09:33:50,859 - ALPHA_MIND - INFO - 2018-01-19 full re-balance: 500\n",
"2018-04-16 09:33:50,991 - ALPHA_MIND - INFO - 2018-01-19 is finished\n",
"2018-04-16 09:33:50,996 - ALPHA_MIND - INFO - 2018-01-26 full re-balance: 500\n",
"2018-04-16 09:33:51,133 - ALPHA_MIND - INFO - 2018-01-26 is finished\n",
"2018-04-16 09:33:51,138 - ALPHA_MIND - INFO - 2018-02-02 full re-balance: 500\n",
"2018-04-16 09:33:51,274 - ALPHA_MIND - INFO - 2018-02-02 is finished\n",
"2018-04-16 09:33:51,279 - ALPHA_MIND - INFO - 2018-02-09 full re-balance: 500\n",
"2018-04-16 09:33:51,417 - ALPHA_MIND - INFO - 2018-02-09 is finished\n",
"2018-04-16 09:33:51,422 - ALPHA_MIND - INFO - 2018-02-23 full re-balance: 500\n",
"2018-04-16 09:33:51,565 - ALPHA_MIND - INFO - 2018-02-23 is finished\n",
"2018-04-16 09:33:51,570 - ALPHA_MIND - INFO - 2018-03-02 full re-balance: 500\n",
"2018-04-16 09:33:51,707 - ALPHA_MIND - INFO - 2018-03-02 is finished\n",
"2018-04-16 09:33:51,713 - ALPHA_MIND - INFO - 2018-03-09 full re-balance: 500\n",
"2018-04-16 09:33:51,850 - ALPHA_MIND - INFO - 2018-03-09 is finished\n",
"2018-04-16 09:33:51,854 - ALPHA_MIND - INFO - 2018-03-16 full re-balance: 500\n",
"2018-04-16 09:33:51,992 - ALPHA_MIND - INFO - 2018-03-16 is finished\n",
"2018-04-16 09:33:51,997 - ALPHA_MIND - INFO - 2018-03-23 full re-balance: 500\n",
"2018-04-16 09:33:52,133 - ALPHA_MIND - INFO - 2018-03-23 is finished\n",
"2018-04-16 09:33:52,139 - ALPHA_MIND - INFO - 2018-03-30 full re-balance: 500\n",
"2018-04-16 09:33:52,274 - ALPHA_MIND - INFO - 2018-03-30 is finished\n"
]
}
],
"outputs": [],
"source": [
"industry_names = industry_list(industry_name, industry_level)\n",
"industry_total = engine.fetch_industry_matrix_range(universe, dates=ref_dates, category=industry_name, level=industry_level)\n",
......@@ -268,30 +146,9 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x157bcc7a4a8>"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 864x432 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"outputs": [],
"source": [
"ret_df = pd.DataFrame({'returns': rets, 'turn_over': turn_overs, 'leverage': leverags}, index=ref_dates)\n",
"ret_df.loc[advanceDateByCalendar('china.sse', ref_dates[-1], freq)] = 0.\n",
......@@ -328,7 +185,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
"version": "3.6.3"
},
"varInspector": {
"cols": {
......
......@@ -4,7 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"> 本例展示如何在alpha-mind中使用机器学习模型"
"* 本例展示如何在alpha-mind中使用机器学习模型\n",
"\n",
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
......@@ -15,6 +17,7 @@
"source": [
"%matplotlib inline\n",
"\n",
"import os\n",
"import datetime as dt\n",
"import numpy as np\n",
"import pandas as pd\n",
......@@ -44,7 +47,7 @@
"pre_process = [winsorize_normal, standardize]\n",
"post_process = [standardize]\n",
"warm_start = 3\n",
"data_source = None\n",
"data_source = os.environ['DB_URI']\n",
"horizon = map_freq(freq)\n",
"\n",
"engine = SqlEngine(data_source)"
......@@ -120,14 +123,14 @@
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 19:44:49,889 - ALPHA_MIND - INFO - Starting data package fetching ...\n",
"2018-04-16 19:44:50,436 - ALPHA_MIND - INFO - factor data loading finished\n",
"2018-04-16 19:44:51,753 - ALPHA_MIND - INFO - fit target data loading finished\n",
"2018-04-16 19:44:51,921 - ALPHA_MIND - INFO - industry data loading finished\n",
"2018-04-16 19:44:52,029 - ALPHA_MIND - INFO - benchmark data loading finished\n",
"2018-04-16 19:44:53,205 - ALPHA_MIND - INFO - data merging finished\n",
"2018-04-16 19:44:53,403 - ALPHA_MIND - INFO - Loading data is finished\n",
"2018-04-16 19:44:53,523 - ALPHA_MIND - INFO - Data processing is finished\n"
"2018-05-05 14:00:27,215 - ALPHA_MIND - INFO - Starting data package fetching ...\n",
"2018-05-05 14:00:29,623 - ALPHA_MIND - INFO - factor data loading finished\n",
"2018-05-05 14:00:32,271 - ALPHA_MIND - INFO - fit target data loading finished\n",
"2018-05-05 14:00:36,819 - ALPHA_MIND - INFO - industry data loading finished\n",
"2018-05-05 14:00:38,193 - ALPHA_MIND - INFO - benchmark data loading finished\n",
"2018-05-05 14:00:44,173 - ALPHA_MIND - INFO - data merging finished\n",
"2018-05-05 14:00:44,449 - ALPHA_MIND - INFO - Loading data is finished\n",
"2018-05-05 14:00:44,624 - ALPHA_MIND - INFO - Data processing is finished\n"
]
}
],
......@@ -167,14 +170,14 @@
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 19:44:53,628 - ALPHA_MIND - INFO - Starting data package fetching ...\n",
"2018-04-16 19:44:54,124 - ALPHA_MIND - INFO - factor data loading finished\n",
"2018-04-16 19:46:01,930 - ALPHA_MIND - INFO - fit target data loading finished\n",
"2018-04-16 19:46:02,091 - ALPHA_MIND - INFO - industry data loading finished\n",
"2018-04-16 19:46:02,197 - ALPHA_MIND - INFO - benchmark data loading finished\n",
"2018-04-16 19:46:03,408 - ALPHA_MIND - INFO - data merging finished\n",
"2018-04-16 19:46:03,614 - ALPHA_MIND - INFO - Loading data is finished\n",
"2018-04-16 19:46:03,656 - ALPHA_MIND - INFO - Data processing is finished\n"
"2018-05-05 14:00:49,320 - ALPHA_MIND - INFO - Starting data package fetching ...\n",
"2018-05-05 14:00:50,281 - ALPHA_MIND - INFO - factor data loading finished\n",
"2018-05-05 14:02:09,314 - ALPHA_MIND - INFO - fit target data loading finished\n",
"2018-05-05 14:02:09,570 - ALPHA_MIND - INFO - industry data loading finished\n",
"2018-05-05 14:02:09,700 - ALPHA_MIND - INFO - benchmark data loading finished\n",
"2018-05-05 14:02:11,229 - ALPHA_MIND - INFO - data merging finished\n",
"2018-05-05 14:02:11,486 - ALPHA_MIND - INFO - Loading data is finished\n",
"2018-05-05 14:02:11,553 - ALPHA_MIND - INFO - Data processing is finished\n"
]
}
],
......@@ -233,9 +236,9 @@
"data": {
"text/plain": [
"dx 1.000000\n",
"T-M 0.053046\n",
"T 0.124049\n",
"Δ 0.076091\n",
"T-M 0.051985\n",
"T 0.122122\n",
"Δ 0.076031\n",
"dtype: float64"
]
},
......@@ -363,38 +366,32 @@
"output_type": "stream",
"text": [
"\n",
"2010-07-07 Const. Testing IC: 0.5775\n",
"2010-07-07 Regression Testing IC: 0.5750\n",
"\n",
"2010-10-11 Const. Testing IC: 0.6586\n",
"2010-10-11 Regression Testing IC: 0.6907\n",
"\n",
"2011-01-04 Const. Testing IC: 0.5799\n",
"2011-01-04 Regression Testing IC: 0.5460\n",
"2011-01-04 Const. Testing IC: 0.5800\n",
"2011-01-04 Regression Testing IC: 0.5802\n",
"\n",
"2011-04-07 Const. Testing IC: 0.4843\n",
"2011-04-07 Regression Testing IC: 0.6691\n",
"2011-04-07 Const. Testing IC: 0.4844\n",
"2011-04-07 Regression Testing IC: 0.6694\n",
"\n",
"2011-07-04 Const. Testing IC: 0.5862\n",
"2011-07-04 Regression Testing IC: 0.6395\n",
"2011-07-04 Regression Testing IC: 0.6396\n",
"\n",
"2011-09-27 Const. Testing IC: 0.6134\n",
"2011-09-27 Regression Testing IC: 0.6809\n",
"2011-09-27 Const. Testing IC: 0.6321\n",
"2011-09-27 Regression Testing IC: 0.6784\n",
"\n",
"2011-12-27 Const. Testing IC: 0.6155\n",
"2011-12-27 Regression Testing IC: 0.5721\n",
"2011-12-27 Const. Testing IC: 0.6823\n",
"2011-12-27 Regression Testing IC: 0.6613\n",
"\n",
"2012-03-29 Const. Testing IC: 0.3999\n",
"2012-03-29 Regression Testing IC: 0.5205\n",
"2012-03-29 Const. Testing IC: 0.3676\n",
"2012-03-29 Regression Testing IC: 0.4809\n",
"\n",
"2012-06-29 Const. Testing IC: 0.0054\n",
"2012-06-29 Regression Testing IC: 0.0579\n",
"2012-06-29 Const. Testing IC: 0.8619\n",
"2012-06-29 Regression Testing IC: 0.8502\n",
"\n",
"2012-09-21 Const. Testing IC: 0.6827\n",
"2012-09-21 Regression Testing IC: 0.6291\n",
"2012-09-21 Const. Testing IC: 0.6434\n",
"2012-09-21 Regression Testing IC: 0.5957\n",
"\n",
"2012-12-21 Const. Testing IC: 0.7544\n",
"2012-12-21 Regression Testing IC: 0.2699\n",
"2012-12-21 Regression Testing IC: 0.7612\n",
"\n",
"2013-03-27 Const. Testing IC: 0.4713\n",
"2013-03-27 Regression Testing IC: 0.6270\n",
......@@ -405,41 +402,41 @@
"2013-09-25 Const. Testing IC: 0.6586\n",
"2013-09-25 Regression Testing IC: 0.6992\n",
"\n",
"2013-12-25 Const. Testing IC: 0.2487\n",
"2013-12-25 Regression Testing IC: 0.2631\n",
"2013-12-25 Const. Testing IC: 0.2465\n",
"2013-12-25 Regression Testing IC: 0.2582\n",
"\n",
"2014-03-27 Const. Testing IC: 0.3904\n",
"2014-03-27 Regression Testing IC: 0.6418\n",
"2014-03-27 Const. Testing IC: 0.3836\n",
"2014-03-27 Regression Testing IC: 0.6062\n",
"\n",
"2014-06-25 Const. Testing IC: 0.5018\n",
"2014-06-25 Regression Testing IC: 0.6655\n",
"2014-06-25 Regression Testing IC: 0.6660\n",
"\n",
"2014-09-18 Const. Testing IC: 0.6088\n",
"2014-09-18 Regression Testing IC: 0.7215\n",
"2014-09-18 Regression Testing IC: 0.7217\n",
"\n",
"2014-12-18 Const. Testing IC: 0.7788\n",
"2014-12-18 Regression Testing IC: 0.6722\n",
"2014-12-18 Regression Testing IC: 0.6732\n",
"\n",
"2015-03-23 Const. Testing IC: 0.4714\n",
"2015-03-23 Regression Testing IC: 0.7190\n",
"2015-03-23 Regression Testing IC: 0.7187\n",
"\n",
"2015-06-17 Const. Testing IC: 0.6239\n",
"2015-06-17 Regression Testing IC: 0.6565\n",
"2015-06-17 Regression Testing IC: 0.6569\n",
"\n",
"2015-09-14 Const. Testing IC: 0.5984\n",
"2015-09-14 Regression Testing IC: 0.6728\n",
"2015-09-14 Const. Testing IC: 0.5672\n",
"2015-09-14 Regression Testing IC: 0.6353\n",
"\n",
"2015-12-14 Const. Testing IC: 0.9509\n",
"2015-12-14 Regression Testing IC: 0.8566\n",
"2015-12-14 Const. Testing IC: 0.9450\n",
"2015-12-14 Regression Testing IC: 0.8404\n",
"\n",
"2016-03-15 Const. Testing IC: 0.4935\n",
"2016-03-15 Regression Testing IC: 0.6239\n",
"2016-03-15 Regression Testing IC: 0.6238\n",
"\n",
"2016-06-13 Const. Testing IC: 0.5908\n",
"2016-06-13 Regression Testing IC: 0.5992\n",
"\n",
"2016-09-05 Const. Testing IC: 0.6832\n",
"2016-09-05 Regression Testing IC: 0.6782\n",
"2016-09-05 Const. Testing IC: 0.6523\n",
"2016-09-05 Regression Testing IC: 0.6519\n",
"\n",
"2016-12-07 Const. Testing IC: 0.9502\n",
"2016-12-07 Regression Testing IC: 0.9013\n",
......@@ -450,11 +447,11 @@
"2017-06-08 Const. Testing IC: 0.5680\n",
"2017-06-08 Regression Testing IC: 0.5823\n",
"\n",
"2017-08-31 Const. Testing IC: 0.6802\n",
"2017-08-31 Regression Testing IC: 0.6701\n",
"2017-08-31 Const. Testing IC: 0.6659\n",
"2017-08-31 Regression Testing IC: 0.6648\n",
"\n",
"2017-11-30 Const. Testing IC: 0.9940\n",
"2017-11-30 Regression Testing IC: 0.8682\n"
"2017-11-30 Regression Testing IC: 0.8681\n"
]
}
],
......@@ -508,13 +505,13 @@
" <tbody>\n",
" <tr>\n",
" <th>mean</th>\n",
" <td>0.592022</td>\n",
" <td>0.617903</td>\n",
" <td>0.617097</td>\n",
" <td>0.658524</td>\n",
" </tr>\n",
" <tr>\n",
" <th>std</th>\n",
" <td>0.193060</td>\n",
" <td>0.168037</td>\n",
" <td>0.172611</td>\n",
" <td>0.123526</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
......@@ -522,8 +519,8 @@
],
"text/plain": [
" naive ic. regress ic.\n",
"mean 0.592022 0.617903\n",
"std 0.193060 0.168037"
"mean 0.617097 0.658524\n",
"std 0.172611 0.123526"
]
},
"execution_count": 16,
......@@ -572,68 +569,64 @@
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 19:48:10,968 - ALPHA_MIND - INFO - 2010-07-07 full re-balance: 799\n",
"2018-04-16 19:48:12,597 - ALPHA_MIND - INFO - 2010-07-07 is finished\n",
"2018-04-16 19:48:12,606 - ALPHA_MIND - INFO - 2010-10-11 full re-balance: 798\n",
"2018-04-16 19:48:14,369 - ALPHA_MIND - INFO - 2010-10-11 is finished\n",
"2018-04-16 19:48:14,379 - ALPHA_MIND - INFO - 2011-01-04 full re-balance: 799\n",
"2018-04-16 19:48:16,279 - ALPHA_MIND - INFO - 2011-01-04 is finished\n",
"2018-04-16 19:48:16,287 - ALPHA_MIND - INFO - 2011-04-07 full re-balance: 798\n",
"2018-04-16 19:48:18,041 - ALPHA_MIND - INFO - 2011-04-07 is finished\n",
"2018-04-16 19:48:18,050 - ALPHA_MIND - INFO - 2011-07-04 full re-balance: 798\n",
"2018-04-16 19:48:19,781 - ALPHA_MIND - INFO - 2011-07-04 is finished\n",
"2018-04-16 19:48:19,790 - ALPHA_MIND - INFO - 2011-09-27 full re-balance: 797\n",
"2018-04-16 19:48:21,799 - ALPHA_MIND - INFO - 2011-09-27 is finished\n",
"2018-04-16 19:48:21,807 - ALPHA_MIND - INFO - 2011-12-27 full re-balance: 798\n",
"2018-04-16 19:48:23,524 - ALPHA_MIND - INFO - 2011-12-27 is finished\n",
"2018-04-16 19:48:23,532 - ALPHA_MIND - INFO - 2012-03-29 full re-balance: 796\n",
"2018-04-16 19:48:25,323 - ALPHA_MIND - INFO - 2012-03-29 is finished\n",
"2018-04-16 19:48:25,331 - ALPHA_MIND - INFO - 2012-06-29 full re-balance: 798\n",
"2018-04-16 19:48:27,215 - ALPHA_MIND - INFO - 2012-06-29 is finished\n",
"2018-04-16 19:48:27,225 - ALPHA_MIND - INFO - 2012-09-21 full re-balance: 799\n",
"2018-04-16 19:48:28,993 - ALPHA_MIND - INFO - 2012-09-21 is finished\n",
"2018-04-16 19:48:29,001 - ALPHA_MIND - INFO - 2012-12-21 full re-balance: 799\n",
"2018-04-16 19:48:30,722 - ALPHA_MIND - INFO - 2012-12-21 is finished\n",
"2018-04-16 19:48:30,730 - ALPHA_MIND - INFO - 2013-03-27 full re-balance: 800\n",
"2018-04-16 19:48:32,704 - ALPHA_MIND - INFO - 2013-03-27 is finished\n",
"2018-04-16 19:48:32,712 - ALPHA_MIND - INFO - 2013-07-01 full re-balance: 800\n",
"2018-04-16 19:48:34,441 - ALPHA_MIND - INFO - 2013-07-01 is finished\n",
"2018-04-16 19:48:34,450 - ALPHA_MIND - INFO - 2013-09-25 full re-balance: 799\n",
"2018-04-16 19:48:36,183 - ALPHA_MIND - INFO - 2013-09-25 is finished\n",
"2018-04-16 19:48:36,191 - ALPHA_MIND - INFO - 2013-12-25 full re-balance: 800\n",
"2018-04-16 19:48:38,121 - ALPHA_MIND - INFO - 2013-12-25 is finished\n",
"2018-04-16 19:48:38,130 - ALPHA_MIND - INFO - 2014-03-27 full re-balance: 800\n",
"2018-04-16 19:48:39,910 - ALPHA_MIND - INFO - 2014-03-27 is finished\n",
"2018-04-16 19:48:39,920 - ALPHA_MIND - INFO - 2014-06-25 full re-balance: 800\n",
"2018-04-16 19:48:41,848 - ALPHA_MIND - INFO - 2014-06-25 is finished\n",
"2018-04-16 19:48:41,856 - ALPHA_MIND - INFO - 2014-09-18 full re-balance: 800\n",
"2018-04-16 19:48:43,598 - ALPHA_MIND - INFO - 2014-09-18 is finished\n",
"2018-04-16 19:48:43,606 - ALPHA_MIND - INFO - 2014-12-18 full re-balance: 800\n",
"2018-04-16 19:48:45,300 - ALPHA_MIND - INFO - 2014-12-18 is finished\n",
"2018-04-16 19:48:45,309 - ALPHA_MIND - INFO - 2015-03-23 full re-balance: 799\n",
"2018-04-16 19:48:47,258 - ALPHA_MIND - INFO - 2015-03-23 is finished\n",
"2018-04-16 19:48:47,267 - ALPHA_MIND - INFO - 2015-06-17 full re-balance: 800\n",
"2018-04-16 19:48:48,995 - ALPHA_MIND - INFO - 2015-06-17 is finished\n",
"2018-04-16 19:48:49,004 - ALPHA_MIND - INFO - 2015-09-14 full re-balance: 800\n",
"2018-04-16 19:48:50,814 - ALPHA_MIND - INFO - 2015-09-14 is finished\n",
"2018-04-16 19:48:50,822 - ALPHA_MIND - INFO - 2015-12-14 full re-balance: 800\n",
"2018-04-16 19:48:52,784 - ALPHA_MIND - INFO - 2015-12-14 is finished\n",
"2018-04-16 19:48:52,792 - ALPHA_MIND - INFO - 2016-03-15 full re-balance: 799\n",
"2018-04-16 19:48:54,587 - ALPHA_MIND - INFO - 2016-03-15 is finished\n",
"2018-04-16 19:48:54,597 - ALPHA_MIND - INFO - 2016-06-13 full re-balance: 800\n",
"2018-04-16 19:48:56,296 - ALPHA_MIND - INFO - 2016-06-13 is finished\n",
"2018-04-16 19:48:56,307 - ALPHA_MIND - INFO - 2016-09-05 full re-balance: 800\n",
"2018-04-16 19:48:58,297 - ALPHA_MIND - INFO - 2016-09-05 is finished\n",
"2018-04-16 19:48:58,306 - ALPHA_MIND - INFO - 2016-12-07 full re-balance: 800\n",
"2018-04-16 19:49:00,028 - ALPHA_MIND - INFO - 2016-12-07 is finished\n",
"2018-04-16 19:49:00,036 - ALPHA_MIND - INFO - 2017-03-09 full re-balance: 800\n",
"2018-04-16 19:49:01,747 - ALPHA_MIND - INFO - 2017-03-09 is finished\n",
"2018-04-16 19:49:01,754 - ALPHA_MIND - INFO - 2017-06-08 full re-balance: 800\n",
"2018-04-16 19:49:03,679 - ALPHA_MIND - INFO - 2017-06-08 is finished\n",
"2018-04-16 19:49:03,688 - ALPHA_MIND - INFO - 2017-08-31 full re-balance: 800\n",
"2018-04-16 19:49:05,486 - ALPHA_MIND - INFO - 2017-08-31 is finished\n",
"2018-04-16 19:49:05,494 - ALPHA_MIND - INFO - 2017-11-30 full re-balance: 800\n",
"2018-04-16 19:49:07,468 - ALPHA_MIND - INFO - 2017-11-30 is finished\n"
"2018-05-05 14:05:43,938 - ALPHA_MIND - INFO - 2011-01-04 full re-balance: 799\n",
"2018-05-05 14:05:46,929 - ALPHA_MIND - INFO - 2011-01-04 is finished\n",
"2018-05-05 14:05:46,941 - ALPHA_MIND - INFO - 2011-04-07 full re-balance: 798\n",
"2018-05-05 14:05:49,923 - ALPHA_MIND - INFO - 2011-04-07 is finished\n",
"2018-05-05 14:05:49,935 - ALPHA_MIND - INFO - 2011-07-04 full re-balance: 798\n",
"2018-05-05 14:05:53,094 - ALPHA_MIND - INFO - 2011-07-04 is finished\n",
"2018-05-05 14:05:53,106 - ALPHA_MIND - INFO - 2011-09-27 full re-balance: 797\n",
"2018-05-05 14:05:56,334 - ALPHA_MIND - INFO - 2011-09-27 is finished\n",
"2018-05-05 14:05:56,345 - ALPHA_MIND - INFO - 2011-12-27 full re-balance: 798\n",
"2018-05-05 14:05:59,460 - ALPHA_MIND - INFO - 2011-12-27 is finished\n",
"2018-05-05 14:05:59,469 - ALPHA_MIND - INFO - 2012-03-29 full re-balance: 796\n",
"2018-05-05 14:06:02,529 - ALPHA_MIND - INFO - 2012-03-29 is finished\n",
"2018-05-05 14:06:02,538 - ALPHA_MIND - INFO - 2012-06-29 full re-balance: 798\n",
"2018-05-05 14:06:05,431 - ALPHA_MIND - INFO - 2012-06-29 is finished\n",
"2018-05-05 14:06:05,441 - ALPHA_MIND - INFO - 2012-09-21 full re-balance: 799\n",
"2018-05-05 14:06:08,657 - ALPHA_MIND - INFO - 2012-09-21 is finished\n",
"2018-05-05 14:06:08,668 - ALPHA_MIND - INFO - 2012-12-21 full re-balance: 799\n",
"2018-05-05 14:06:11,593 - ALPHA_MIND - INFO - 2012-12-21 is finished\n",
"2018-05-05 14:06:11,602 - ALPHA_MIND - INFO - 2013-03-27 full re-balance: 800\n",
"2018-05-05 14:06:14,838 - ALPHA_MIND - INFO - 2013-03-27 is finished\n",
"2018-05-05 14:06:14,849 - ALPHA_MIND - INFO - 2013-07-01 full re-balance: 800\n",
"2018-05-05 14:06:17,748 - ALPHA_MIND - INFO - 2013-07-01 is finished\n",
"2018-05-05 14:06:17,759 - ALPHA_MIND - INFO - 2013-09-25 full re-balance: 799\n",
"2018-05-05 14:06:20,887 - ALPHA_MIND - INFO - 2013-09-25 is finished\n",
"2018-05-05 14:06:20,897 - ALPHA_MIND - INFO - 2013-12-25 full re-balance: 800\n",
"2018-05-05 14:06:24,106 - ALPHA_MIND - INFO - 2013-12-25 is finished\n",
"2018-05-05 14:06:24,117 - ALPHA_MIND - INFO - 2014-03-27 full re-balance: 800\n",
"2018-05-05 14:06:27,141 - ALPHA_MIND - INFO - 2014-03-27 is finished\n",
"2018-05-05 14:06:27,151 - ALPHA_MIND - INFO - 2014-06-25 full re-balance: 800\n",
"2018-05-05 14:06:30,239 - ALPHA_MIND - INFO - 2014-06-25 is finished\n",
"2018-05-05 14:06:30,249 - ALPHA_MIND - INFO - 2014-09-18 full re-balance: 800\n",
"2018-05-05 14:06:33,049 - ALPHA_MIND - INFO - 2014-09-18 is finished\n",
"2018-05-05 14:06:33,058 - ALPHA_MIND - INFO - 2014-12-18 full re-balance: 800\n",
"2018-05-05 14:06:35,926 - ALPHA_MIND - INFO - 2014-12-18 is finished\n",
"2018-05-05 14:06:35,936 - ALPHA_MIND - INFO - 2015-03-23 full re-balance: 799\n",
"2018-05-05 14:06:39,030 - ALPHA_MIND - INFO - 2015-03-23 is finished\n",
"2018-05-05 14:06:39,040 - ALPHA_MIND - INFO - 2015-06-17 full re-balance: 800\n",
"2018-05-05 14:06:41,833 - ALPHA_MIND - INFO - 2015-06-17 is finished\n",
"2018-05-05 14:06:41,844 - ALPHA_MIND - INFO - 2015-09-14 full re-balance: 800\n",
"2018-05-05 14:06:44,789 - ALPHA_MIND - INFO - 2015-09-14 is finished\n",
"2018-05-05 14:06:44,800 - ALPHA_MIND - INFO - 2015-12-14 full re-balance: 800\n",
"2018-05-05 14:06:48,028 - ALPHA_MIND - INFO - 2015-12-14 is finished\n",
"2018-05-05 14:06:48,039 - ALPHA_MIND - INFO - 2016-03-15 full re-balance: 799\n",
"2018-05-05 14:06:51,033 - ALPHA_MIND - INFO - 2016-03-15 is finished\n",
"2018-05-05 14:06:51,044 - ALPHA_MIND - INFO - 2016-06-13 full re-balance: 800\n",
"2018-05-05 14:06:54,095 - ALPHA_MIND - INFO - 2016-06-13 is finished\n",
"2018-05-05 14:06:54,107 - ALPHA_MIND - INFO - 2016-09-05 full re-balance: 800\n",
"2018-05-05 14:06:57,177 - ALPHA_MIND - INFO - 2016-09-05 is finished\n",
"2018-05-05 14:06:57,188 - ALPHA_MIND - INFO - 2016-12-07 full re-balance: 800\n",
"2018-05-05 14:07:00,012 - ALPHA_MIND - INFO - 2016-12-07 is finished\n",
"2018-05-05 14:07:00,023 - ALPHA_MIND - INFO - 2017-03-09 full re-balance: 800\n",
"2018-05-05 14:07:03,315 - ALPHA_MIND - INFO - 2017-03-09 is finished\n",
"2018-05-05 14:07:03,328 - ALPHA_MIND - INFO - 2017-06-08 full re-balance: 800\n",
"2018-05-05 14:07:06,336 - ALPHA_MIND - INFO - 2017-06-08 is finished\n",
"2018-05-05 14:07:06,348 - ALPHA_MIND - INFO - 2017-08-31 full re-balance: 800\n",
"2018-05-05 14:07:09,893 - ALPHA_MIND - INFO - 2017-08-31 is finished\n",
"2018-05-05 14:07:09,904 - ALPHA_MIND - INFO - 2017-11-30 full re-balance: 800\n",
"2018-05-05 14:07:13,134 - ALPHA_MIND - INFO - 2017-11-30 is finished\n"
]
}
],
......@@ -700,7 +693,7 @@
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x1b6adc713c8>"
"<matplotlib.axes._subplots.AxesSubplot at 0x1fbc693ee48>"
]
},
"execution_count": 19,
......@@ -709,9 +702,9 @@
},
{
"data": {
"image/png": "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\n",
"image/png": "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\nREREJOD9P7YiQOMpZl+hAAAAAElFTkSuQmCC\n",
"text/plain": [
"<Figure size 864x432 with 1 Axes>"
"<matplotlib.figure.Figure at 0x1fbc693e438>"
]
},
"metadata": {},
......@@ -752,7 +745,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
"version": "3.6.3"
},
"varInspector": {
"cols": {
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 使用行业内的排序,进行因子测试;与回归版本,以及原始因子值版本进行比较。本部分参考自《QEPM》 p.p 117\n",
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 参数设定\n",
"--------------"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import os\n",
"import pandas as pd\n",
"import numpy as np\n",
"from PyFin.api import *\n",
"from alphamind.api import *\n",
"\n",
"factor = 'CFO2EV'\n",
"universe = Universe('custom', ['zz800'])\n",
"start_date = '2010-01-01'\n",
"end_date = '2018-04-26'\n",
"freq = '20b'\n",
"category = 'sw_adj'\n",
"level = 1\n",
"horizon = map_freq(freq)\n",
"\n",
"engine = SqlEngine(os.environ['DB_URI'])\n",
"\n",
"ref_dates = makeSchedule(start_date, end_date, freq, 'china.sse')\n",
"sample_date = '2018-01-04'\n",
"sample_codes = engine.fetch_codes(sample_date, universe)\n",
"\n",
"sample_industry = engine.fetch_industry(sample_date, sample_codes, category=category, level=level)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_industry.head()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 样例因子\n",
"--------------------\n",
"\n",
"我们下面分三种方法,分别考查这几种方法在避免行业集中上面的效果:\n",
"\n",
"* 使用原始因子的排序;\n",
"* 使用原始因子在行业内的排序;\n",
"* 使用原始因子在行业哑变量上回归后得到的残差排序。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. 原始因子排序\n",
"--------------------"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"factor1 = {'f1': CSQuantiles(factor)}\n",
"sample_factor1 = engine.fetch_factor(sample_date, factor1, sample_codes)\n",
"sample_factor1 = pd.merge(sample_factor1, sample_industry[['code', 'industry']], on='code')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_factor1.sort_values('f1', ascending=False).head(15)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"对于原始因子,如果我们不做任何行业上面的处理,发现我们选定的alpha因子`CFO2EV`较大的股票集中于银行和大金融板块。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. 行业内排序因子\n",
"----------------------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"这里我们使用调整后的申万行业分类作为行业标签:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"factor2 = {'f2': CSQuantiles(factor, groups='sw1_adj')}\n",
"sample_factor2 = engine.fetch_factor(sample_date, factor2, sample_codes)\n",
"sample_factor2 = pd.merge(sample_factor2, sample_industry[['code', 'industry']], on='code')\n",
"sample_factor2.sort_values('f2', ascending=False).head(15)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"使用行业内的排序,则行业分布会比较平均。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. 使用回归将因子行业中性\n",
"--------------------------------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"还有一种思路,使用线性回归,以行业为哑变量,使用回归后的残差作为因子的替代值,做到行业中性:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"factor3 = {'f3': factor}\n",
"sample_factor3 = engine.fetch_factor(sample_date, factor3, sample_codes)\n",
"risk_cov, risk_exp = engine.fetch_risk_model(sample_date, sample_codes)\n",
"sample_factor3 = pd.merge(sample_factor3, sample_industry[['code', 'industry']], on='code')\n",
"sample_factor3 = pd.merge(sample_factor3, risk_exp, on='code')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"raw_factors = sample_factor3['f3'].values\n",
"industry_exp = sample_factor3[industry_styles + ['COUNTRY']].values.astype(float)\n",
"processed_values = factor_processing(raw_factors, pre_process=[], risk_factors=industry_exp, post_process=[percentile])\n",
"sample_factor3['f3'] = processed_values"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"sample_factor3 = sample_factor3[['code', 'isOpen', 'f3', 'industry']]\n",
"sample_factor3.sort_values('f3', ascending=False).head(15)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们发现这种方法的效果并不是很好。调整的幅度并不是很大,同时仍然存在着集中于大金融板块的问题。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# 回测结果\n",
"------------------"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"我们使用简单等权重做多前20%支股票,做空后20%的方法,考察三种方法的效果:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"factors = {\n",
" 'raw': CSQuantiles(factor),\n",
" 'peer quantile': CSQuantiles(factor, groups='sw1'),\n",
" 'risk neutral': LAST(factor)\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_ret = pd.DataFrame(columns=['raw', 'peer quantile', 'risk neutral'])\n",
"df_ic = pd.DataFrame(columns=['raw', 'peer quantile', 'risk neutral'])\n",
"\n",
"for date in ref_dates:\n",
" ref_date = date.strftime('%Y-%m-%d')\n",
" codes = engine.fetch_codes(ref_date, universe)\n",
"\n",
" total_factor = engine.fetch_factor(ref_date, factors, codes)\n",
" risk_cov, risk_exp = engine.fetch_risk_model(ref_date, codes)\n",
" industry = engine.fetch_industry(ref_date, codes, category=category, level=level)\n",
" rets = engine.fetch_dx_return(ref_date, codes, horizon=horizon, offset=1)\n",
" total_factor = pd.merge(total_factor, industry[['code', 'industry']], on='code')\n",
" total_factor = pd.merge(total_factor, risk_exp, on='code')\n",
" total_factor = pd.merge(total_factor, rets, on='code').dropna()\n",
"\n",
" raw_factors = total_factor['risk neutral'].values\n",
" industry_exp = total_factor[industry_styles + ['COUNTRY']].values.astype(float)\n",
" processed_values = factor_processing(raw_factors, pre_process=[], risk_factors=industry_exp, post_process=[percentile])\n",
" total_factor['risk neutral'] = processed_values\n",
"\n",
" total_factor[['f1_d', 'f2_d', 'f3_d']] = (total_factor[['raw', 'peer quantile', 'risk neutral']] >= 0.8) * 1.\n",
" total_factor.loc[total_factor['raw'] <= 0.2, 'f1_d'] = -1.\n",
" total_factor.loc[total_factor['peer quantile'] <= 0.2, 'f2_d'] = -1.\n",
" total_factor.loc[total_factor['risk neutral'] <= 0.2, 'f3_d'] = -1.\n",
" total_factor[['f1_d', 'f2_d', 'f3_d']] /= np.abs(total_factor[['f1_d', 'f2_d', 'f3_d']]).sum(axis=0)\n",
"\n",
" ret_values = total_factor.dx.values @ total_factor[['f1_d', 'f2_d', 'f3_d']].values\n",
" df_ret.loc[date] = ret_values\n",
" \n",
" ic_values = total_factor[['dx', 'raw', 'peer quantile', 'risk neutral']].corr().values[0, 1:]\n",
" df_ic.loc[date] = ic_values\n",
" print(f\"{date} is finished\")"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_ret.cumsum().plot(figsize=(14, 7))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df_ic.cumsum().plot(figsize=(14, 7))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.3"
},
"varInspector": {
"cols": {
"lenName": 16,
"lenType": 16,
"lenVar": 40
},
"kernels_config": {
"python": {
"delete_cmd_postfix": "",
"delete_cmd_prefix": "del ",
"library": "var_list.py",
"varRefreshCmd": "print(var_dic_list())"
},
"r": {
"delete_cmd_postfix": ") ",
"delete_cmd_prefix": "rm(",
"library": "var_list.r",
"varRefreshCmd": "cat(var_dic_list()) "
}
},
"types_to_exclude": [
"module",
"function",
"builtin_function_or_method",
"instance",
"_Feature"
],
"window_display": false
}
},
"nbformat": 4,
"nbformat_minor": 2
}
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"> 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"from matplotlib import pyplot as plt\n",
"from alphamind.api import *\n",
"from alphamind.strategy.strategy import RunningSetting\n",
"from alphamind.strategy.strategy import Strategy\n",
"from PyFin.api import *\n",
"from PyFin.Math.Accumulators.StatefulAccumulators import MovingAverage\n",
"from PyFin.Math.Accumulators.StatefulAccumulators import MovingSharp\n",
......@@ -21,7 +31,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -29,368 +39,79 @@
"Back test parameter settings\n",
"\"\"\"\n",
"\n",
"start_date = '2016-01-01'\n",
"end_date = '2018-02-24'\n",
"\n",
"freq = '10b'\n",
"industry_lower = 1.0\n",
"industry_upper = 1.0\n",
"neutralized_risk = industry_styles\n",
"industry_name = 'sw'\n",
"industry_level = 1\n",
"turn_over_target_base = 0.4\n",
"benchmark_total_lower = 0.8\n",
"benchmark_total_upper = 1.0\n",
"batch = 1\n",
"horizon = map_freq(freq)\n",
"universe = Universe(\"custom\", ['zz800'])\n",
"data_source = 'postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha'\n",
"benchmark_code = 905\n",
"method = 'tv'\n",
"target_vol = 0.05\n",
"risk_model = 'short'\n",
"universe = Universe('zz800') + Universe('cyb')\n",
"\n",
"executor = NaiveExecutor()\n",
"ref_dates = makeSchedule(start_date, end_date, freq, 'china.sse')\n",
"engine = SqlEngine(data_source)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"\n",
"Factor Model\n",
"\"\"\"\n",
"def create_scenario(weights_bandwidth=0.02, target_vol=0.01, method='risk_neutral'):\n",
" start_date = '2017-01-01'\n",
" end_date = '2018-04-27'\n",
" freq = '10b'\n",
" neutralized_risk = None\n",
"\n",
"alpha_factors = {\n",
" 'f01': LAST('CFO2EV'),\n",
" 'f02': LAST('EPS'),\n",
" alpha_factors = {\n",
" 'f01': CSQuantiles(LAST('EPS'), groups='sw1'),\n",
" 'f02': CSQuantiles(LAST('ROE'), groups='sw1'),\n",
" }\n",
"\n",
" weights = dict(\n",
" f01=1.,\n",
" f02=1.\n",
" )\n",
"\n",
"weights = dict(f01=1.,\n",
" f02=1.)\n",
"\n",
"alpha_model = ConstLinearModel(features=alpha_factors, weights=weights)\n",
" alpha_model = ConstLinearModel(features=alpha_factors, weights=weights)\n",
"\n",
"def predict_worker(params):\n",
" data_meta = DataMeta(freq=freq,\n",
" universe=universe,\n",
" batch=batch,\n",
" neutralized_risk=neutralized_risk,\n",
" risk_model='short',\n",
" pre_process=[winsorize_normal, standardize],\n",
" post_process=[winsorize_normal, standardize],\n",
" warm_start=0,\n",
" data_source=data_source)\n",
" ref_date, model = params\n",
" er = predict_by_model(ref_date, model, data_meta)\n",
" return er"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 18:39:18,131 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:18,148 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:18,476 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:18,479 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:18,817 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:18,819 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:19,214 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:19,216 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:19,537 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:19,539 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:19,885 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:19,887 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:20,229 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:20,232 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:20,571 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:20,573 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:20,912 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:20,915 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:21,260 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:21,263 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:21,605 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:21,607 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:21,957 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:21,959 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:22,293 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:22,297 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:22,646 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:22,648 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:22,986 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:22,987 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:23,326 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:23,328 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:23,661 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:23,664 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:23,985 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:23,988 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:24,328 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:24,330 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:24,661 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:24,663 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:24,984 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:24,986 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:25,309 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:25,311 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:25,649 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:25,651 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:25,993 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:25,995 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:26,337 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:26,339 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:26,663 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:26,665 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:26,999 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:27,001 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:27,364 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:27,366 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:27,709 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:27,712 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:28,061 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:28,062 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:28,484 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:28,486 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:28,808 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:28,810 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:29,137 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:29,140 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:29,496 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:29,498 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:29,843 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:29,845 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:30,183 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:30,185 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:30,524 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:30,527 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 18:39:30,889 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:30,891 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:31,272 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:31,275 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:31,621 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:31,623 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:31,982 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:31,984 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:32,342 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:32,344 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:32,685 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:32,687 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:33,021 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:33,024 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:33,364 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:33,366 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:33,696 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:33,697 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:34,038 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:34,041 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:34,406 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:34,408 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:34,749 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:34,751 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:35,091 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:35,093 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:35,446 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:35,448 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:35,790 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:35,792 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:36,166 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:39:36,168 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Wall time: 18.5 s\n"
]
}
],
"source": [
"%%time\n",
"\n",
"\"\"\"\n",
"Predicting Phase\n",
"\"\"\"\n",
"\n",
"predicts = [predict_worker((d.strftime('%Y-%m-%d'), alpha_model)) for d in ref_dates]"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [],
"source": [
"\"\"\"\n",
"Shared Data\n",
"\"\"\"\n",
"\n",
"industry_names = industry_list(industry_name, industry_level)\n",
"constraint_risk = ['SIZE', 'SIZENL', 'BETA'] + industry_names\n",
"total_risk_names = constraint_risk + ['benchmark', 'total']\n",
"all_styles = risk_styles + industry_styles + macro_styles\n",
"\n",
"b_type = []\n",
"l_val = []\n",
"u_val = []\n",
" batch=1,\n",
" neutralized_risk=None,\n",
" pre_process=None,\n",
" post_process=None,\n",
" data_source=os.environ['DB_URI'])\n",
"\n",
"for name in total_risk_names:\n",
" if name == 'benchmark':\n",
" b_type.append(BoundaryType.RELATIVE)\n",
" l_val.append(benchmark_total_lower)\n",
" u_val.append(benchmark_total_upper)\n",
" elif name in {'SIZE', 'SIZENL', 'BETA', 'total'}:\n",
" b_type.append(BoundaryType.ABSOLUTE)\n",
" l_val.append(0.0)\n",
" u_val.append(0.0)\n",
" else:\n",
" b_type.append(BoundaryType.RELATIVE)\n",
" l_val.append(industry_lower)\n",
" u_val.append(industry_upper)\n",
" \n",
"bounds = create_box_bounds(total_risk_names, b_type, l_val, u_val)\n",
"industry_total = engine.fetch_industry_matrix_range(universe, dates=ref_dates, category=industry_name, level=industry_level)\n",
"benchmark_total = engine.fetch_benchmark_range(dates=ref_dates, benchmark=benchmark_code)\n",
"risk_cov_total, risk_exposure_total = engine.fetch_risk_model_range(universe, dates=ref_dates, risk_model=risk_model)\n",
"index_return = engine.fetch_dx_return_index_range(benchmark_code, start_date, end_date, horizon=horizon, offset=1).set_index('trade_date')"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# rebalance\n",
" industry_names = industry_list('sw', 1)\n",
" constraint_risk = ['SIZE', 'SIZENL', 'BETA']\n",
" total_risk_names = constraint_risk + ['benchmark', 'total']\n",
" all_styles = risk_styles + industry_styles + macro_styles\n",
"\n",
"def create_scenario(weight_gap, target_vol, method):\n",
" b_type = []\n",
" l_val = []\n",
" u_val = []\n",
"\n",
" previous_pos = pd.DataFrame()\n",
" rets = []\n",
" turn_overs = []\n",
" leverags = []\n",
"\n",
" for i, ref_date in enumerate(ref_dates):\n",
" ref_date = ref_date.strftime('%Y-%m-%d')\n",
" industry_matrix = industry_total[industry_total.trade_date == ref_date]\n",
" benchmark_w = benchmark_total[benchmark_total.trade_date == ref_date]\n",
" risk_exposure = risk_exposure_total[risk_exposure_total.trade_date == ref_date]\n",
" risk_cov = risk_cov_total[risk_cov_total.trade_date == ref_date]\n",
"\n",
" res = pd.merge(industry_matrix, benchmark_w, on=['code'], how='left').fillna(0.)\n",
" res = pd.merge(res, risk_exposure, on=['code'])\n",
" res = res.dropna()\n",
" codes = res.code.values.tolist()\n",
" \n",
" risk_exposure = res[all_styles].values\n",
" risk_cov = risk_cov[all_styles].values\n",
" special_risk = res.srisk.values\n",
" sec_cov = risk_exposure @ risk_cov @ risk_exposure.T / 10000 + np.diag(special_risk ** 2) / 10000\n",
"\n",
" benchmark_w = res.weight.values\n",
" is_in_benchmark = (benchmark_w > 0.).astype(float).reshape((-1, 1))\n",
"\n",
" total_risk_exp = np.concatenate([res[constraint_risk].values.astype(float),\n",
" is_in_benchmark,\n",
" np.ones_like(is_in_benchmark)],\n",
" axis=1)\n",
" total_risk_exp = pd.DataFrame(total_risk_exp, columns=total_risk_names)\n",
" constraints = LinearConstraints(bounds, total_risk_exp, benchmark_w)\n",
"\n",
" lbound = np.maximum(0., benchmark_w - weight_gap) # np.zeros(len(total_data))\n",
" ubound = weight_gap + benchmark_w\n",
"\n",
" if previous_pos.empty:\n",
" current_position = None\n",
" turn_over_target = None\n",
" for name in total_risk_names:\n",
" if name == 'benchmark':\n",
" b_type.append(BoundaryType.RELATIVE)\n",
" l_val.append(0.8)\n",
" u_val.append(1.0)\n",
" else:\n",
" previous_pos.set_index('code', inplace=True)\n",
" remained_pos = previous_pos.loc[codes]\n",
"\n",
" remained_pos.fillna(0., inplace=True)\n",
" turn_over_target = turn_over_target_base\n",
" current_position = remained_pos.weight.values\n",
"\n",
" er = predicts[i].loc[codes].values\n",
"\n",
" try:\n",
" target_pos, _ = er_portfolio_analysis(er,\n",
" industry_matrix.industry_name.values,\n",
" None,\n",
" constraints,\n",
" False,\n",
" benchmark_w,\n",
" method=method,\n",
" turn_over_target=turn_over_target,\n",
" current_position=current_position,\n",
" lbound=lbound,\n",
" ubound=ubound,\n",
" target_vol=target_vol,\n",
" cov=sec_cov)\n",
" except ValueError:\n",
" target_pos, _ = er_portfolio_analysis(er,\n",
" industry_matrix.industry_name.values,\n",
" None,\n",
" constraints,\n",
" False,\n",
" benchmark_w,\n",
" method=method,\n",
" lbound=lbound,\n",
" ubound=ubound,\n",
" target_vol=target_vol,\n",
" cov=sec_cov)\n",
"\n",
" target_pos['code'] = codes\n",
" turn_over, executed_pos = executor.execute(target_pos=target_pos)\n",
"\n",
" executed_codes = executed_pos.code.tolist()\n",
" dx_returns = engine.fetch_dx_return(ref_date, executed_codes, horizon=horizon, offset=1)\n",
" result = pd.merge(executed_pos, dx_returns, on=['code'])\n",
"\n",
" leverage = result.weight.abs().sum()\n",
"\n",
" ret = result.weight.values @ (np.exp(result.dx.values) - 1.)\n",
" rets.append(np.log(1. + ret))\n",
" executor.set_current(executed_pos)\n",
" turn_overs.append(turn_over)\n",
" leverags.append(leverage)\n",
"\n",
" previous_pos = executed_pos\n",
" \n",
" ret_df = pd.DataFrame({'returns': rets, 'turn_over': turn_overs, 'leverage': leverags}, index=ref_dates)\n",
"\n",
" # index return\n",
" \n",
" ret_df['index'] = index_return['dx']\n",
"\n",
" ret_df.loc[advanceDateByCalendar('china.sse', ref_dates[-1], freq)] = 0.\n",
" ret_df = ret_df.shift(1)\n",
" ret_df.iloc[0] = 0.\n",
" ret_df['tc_cost'] = ret_df.turn_over * 0.002\n",
" ret_df['ret_after_tc'] = ret_df['returns'] - ret_df['tc_cost'] - ret_df['index'] * ret_df['leverage']\n",
" b_type.append(BoundaryType.ABSOLUTE)\n",
" l_val.append(0.0)\n",
" u_val.append(0.0)\n",
"\n",
" bounds = create_box_bounds(total_risk_names, b_type, l_val, u_val)\n",
" running_setting = RunningSetting(universe,\n",
" start_date,\n",
" end_date,\n",
" freq,\n",
" benchmark=benchmark_code,\n",
" weights_bandwidth=weights_bandwidth,\n",
" rebalance_method=method,\n",
" bounds=bounds,\n",
" target_vol=target_vol,\n",
" turn_over_target=0.4)\n",
"\n",
" strategy = Strategy(alpha_model, data_meta, running_setting)\n",
" ret_df, positions = strategy.run()\n",
" return ret_df"
]
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -398,7 +119,8 @@
" sharp_calc = MovingSharp(windows, x='ret', y='riskFree')\n",
" drawdown_calc = MovingMaxDrawdown(windows, x='ret')\n",
" max_drawdown_calc = MovingMaxDrawdown(len(ret_df), x='ret')\n",
"\n",
" \n",
" ret_df['ret_after_tc'] = ret_df.excess_return - 0.002 * ret_df.turn_over\n",
" res_df = pd.DataFrame(columns=['daily_return', 'cum_ret', 'sharp', 'drawdown', 'max_drawn', 'leverage'])\n",
" total_returns = 0.\n",
"\n",
......@@ -424,36 +146,13 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"d:\\ProgramData\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:45: FutureWarning: \n",
"Passing list-likes to .loc or [] with any missing label will raise\n",
"KeyError in the future, you can use .reindex() as an alternative.\n",
"\n",
"See the documentation here:\n",
"http://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike\n",
"d:\\ProgramData\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:45: FutureWarning: \n",
"Passing list-likes to .loc or [] with any missing label will raise\n",
"KeyError in the future, you can use .reindex() as an alternative.\n",
"\n",
"See the documentation here:\n",
"http://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike\n",
"2018-04-16 18:40:03,214 - ALPHA_MIND - INFO - weight_gap: 0.005 finished\n",
"2018-04-16 18:40:26,049 - ALPHA_MIND - INFO - weight_gap: 0.01 finished\n",
"2018-04-16 18:40:48,115 - ALPHA_MIND - INFO - weight_gap: 0.015 finished\n",
"2018-04-16 18:41:10,290 - ALPHA_MIND - INFO - weight_gap: 0.02 finished\n"
]
}
],
"outputs": [],
"source": [
"weight_gaps = [0.005, 0.010, 0.015, 0.020]\n",
"\n",
"with pd.ExcelWriter(f'{universe.base_universe[0]}_{benchmark_code}_gap.xlsx', engine='xlsxwriter') as writer:\n",
"with pd.ExcelWriter(f'zz800_cyb_{benchmark_code}_gap.xlsx', engine='xlsxwriter') as writer:\n",
" for i, weight_gap in enumerate(weight_gaps):\n",
" ret_df = create_scenario(weight_gap, target_vol=0.01, method='risk_neutral')\n",
" res_df = create_report(ret_df, 25)\n",
......@@ -463,32 +162,15 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"d:\\ProgramData\\Anaconda3\\lib\\site-packages\\ipykernel_launcher.py:45: FutureWarning: \n",
"Passing list-likes to .loc or [] with any missing label will raise\n",
"KeyError in the future, you can use .reindex() as an alternative.\n",
"\n",
"See the documentation here:\n",
"http://pandas.pydata.org/pandas-docs/stable/indexing.html#deprecate-loc-reindex-listlike\n",
"2018-04-16 18:41:44,346 - ALPHA_MIND - INFO - target_vol: 0.0150 finished\n",
"2018-04-16 18:42:12,024 - ALPHA_MIND - INFO - target_vol: 0.0300 finished\n",
"2018-04-16 18:42:34,614 - ALPHA_MIND - INFO - target_vol: 0.0450 finished\n",
"2018-04-16 18:42:57,030 - ALPHA_MIND - INFO - target_vol: 0.0600 finished\n"
]
}
],
"outputs": [],
"source": [
"target_vols = [0.015, 0.030, 0.045, 0.060]\n",
"\n",
"with pd.ExcelWriter(f'{universe.base_universe[0]}_{benchmark_code}_tv.xlsx', engine='xlsxwriter') as writer:\n",
"with pd.ExcelWriter(f'zz800_cyb_{benchmark_code}_tv.xlsx', engine='xlsxwriter') as writer:\n",
" for i, target_vol in enumerate(target_vols):\n",
" ret_df = create_scenario(weight_gap=0.02, target_vol=target_vol, method='tv')\n",
" ret_df = create_scenario(0.01, target_vol=target_vol, method='tv')\n",
" res_df = create_report(ret_df, 25)\n",
" res_df.to_excel(writer, sheet_name=f'{i}')\n",
" alpha_logger.info(f\"target_vol: {target_vol:.4f} finished\")"
......@@ -508,18 +190,6 @@
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.5"
},
"varInspector": {
"cols": {
"lenName": 16,
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"from matplotlib import pyplot as plt\n",
......@@ -18,7 +26,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -40,8 +48,8 @@
"benchmark_total_upper = 1.0\n",
"batch = 1\n",
"horizon = map_freq(freq)\n",
"universe = Universe(\"custom\", ['zz800'])\n",
"data_source = 'postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha'\n",
"universe = Universe('zz800')\n",
"data_source = os.environ['DB_URI']\n",
"benchmark_code = 905\n",
"weight_gap = 0.01\n",
"\n",
......@@ -52,7 +60,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -73,7 +81,7 @@
" neutralized_risk=neutralized_risk,\n",
" risk_model='short',\n",
" pre_process=[winsorize_normal, standardize],\n",
" post_process=[winsorize_normal, standardize],\n",
" post_process=[standardize],\n",
" warm_start=0,\n",
" data_source=data_source)\n",
" ref_date, model = params\n",
......@@ -83,133 +91,9 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 18:41:19,906 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:19,908 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:20,265 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:20,291 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:20,608 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:20,610 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:20,956 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:20,958 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:21,320 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:21,323 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:21,667 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:21,669 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:21,997 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:21,999 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:22,395 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:22,397 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:22,744 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:22,747 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:23,072 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:23,080 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:23,444 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:23,446 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:23,768 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:23,770 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:24,126 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:24,127 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:24,460 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:24,463 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:24,840 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:24,842 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:25,193 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:25,195 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:25,557 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:25,559 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:25,885 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:25,887 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:26,194 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:26,195 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:26,527 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:26,529 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:26,847 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:26,849 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:27,185 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:27,187 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:27,554 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:27,557 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:27,907 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:27,910 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:28,249 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:28,263 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:28,619 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:28,622 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:28,982 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:28,984 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:29,346 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:29,348 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:29,753 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:29,755 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:30,126 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:30,128 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:30,476 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:30,479 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:30,816 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:30,818 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:31,164 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:31,167 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:31,502 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:31,504 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:31,842 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:31,859 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:32,196 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:32,198 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:32,639 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:32,642 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n"
]
},
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-04-16 18:41:32,990 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:32,992 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:33,343 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:33,345 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:33,678 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:33,680 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:34,037 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:34,054 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:34,403 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:34,405 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:34,787 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:34,789 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:35,137 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:35,140 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:35,482 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:35,484 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:35,842 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:35,844 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:36,181 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:36,183 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:36,552 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:36,554 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:36,906 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:36,909 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:37,289 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:37,291 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:37,654 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:37,656 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:38,016 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n",
"2018-04-16 18:41:38,018 - ALPHA_MIND - WARNING - winsorize_normal normally should not be done after neutralize\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"Wall time: 18.6 s\n"
]
}
],
"outputs": [],
"source": [
"%%time\n",
"\n",
......@@ -221,7 +105,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -229,8 +113,7 @@
"Shared Data\n",
"\"\"\"\n",
"\n",
"industry_names = industry_list(industry_name, industry_level)\n",
"constraint_risk = ['SIZE', 'SIZENL', 'BETA'] + industry_names\n",
"constraint_risk = ['SIZE', 'SIZENL', 'BETA']\n",
"total_risk_names = constraint_risk + ['benchmark', 'total']\n",
"\n",
"b_type = []\n",
......@@ -260,7 +143,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -344,30 +227,9 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x240ed0d7f60>"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 864x432 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"outputs": [],
"source": [
"ret_df = create_scenario(weight_gap)\n",
"ret_df[['returns', 'tc_cost']].cumsum().plot(figsize=(12, 6),\n",
......@@ -375,13 +237,6 @@
" secondary_y='tc_cost')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
......
......@@ -4,7 +4,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"> The methodolegy is similar to The Barra China Equity Model (CNE5)'s documentation"
"* 方法参考自 The Barra China Equity Model (CNE5)'s 文档\n",
"\n",
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
......@@ -15,6 +17,7 @@
"source": [
"%matplotlib inline\n",
"\n",
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"from matplotlib import pyplot as plt\n",
......@@ -41,7 +44,7 @@
"category = 'sw'\n",
"level = 1\n",
"freq = '20b'\n",
"universe = Universe('custom', ['ashare_ex'])\n",
"universe = Universe('ashare_ex')\n",
"\n",
"horizon = map_freq(freq)\n",
"ref_dates = makeSchedule(start_date, end_date, freq, 'china.sse')"
......@@ -54,7 +57,7 @@
"outputs": [],
"source": [
"def risk_factor_analysis(factor_name):\n",
" data_source = 'postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha'\n",
" data_source = os.environ['DB_URI']\n",
" engine = SqlEngine(data_source)\n",
" risk_names = list(set(risk_styles).difference({factor_name}))\n",
" industry_names = list(set(industry_styles).difference({factor_name}))\n",
......@@ -146,7 +149,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": null,
......@@ -7,11 +14,13 @@
"outputs": [],
"source": [
"%matplotlib inline\n",
"import os\n",
"import numpy as np\n",
"import pandas as pd\n",
"from matplotlib import pyplot as plt\n",
"from PyFin.api import *\n",
"from alphamind.api import *\n",
"from alphamind.strategy.strategy import Strategy, RunningSetting\n",
"from alphamind.portfolio.meanvariancebuilder import target_vol_builder\n",
"\n",
"plt.style.use('ggplot')"
......@@ -32,8 +41,8 @@
"outputs": [],
"source": [
"ref_date = '2018-01-08'\n",
"engine = SqlEngine('postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha')\n",
"universe = Universe('custom', ['zz800'])"
"engine = SqlEngine(os.environ['DB_URI'])\n",
"universe = Universe('zz800')"
]
},
{
......@@ -108,7 +117,7 @@
"# check the result\n",
"print(f\"total weight is {p_weight.sum(): .4f}\")\n",
"print(f\"portfolio activate weight forecasting vol is {np.sqrt((p_weight - bm) @ sec_cov @ (p_weight - bm)):.4f}\")\n",
"print(f\"portfolio expected return is {p_weight @ er:.4f} comparing with benchmark er {bm @ er:.4f}\")"
"print(f\"portfolio er: {p_weight @ er:.4f} comparing with benchmark er: {bm @ er:.4f}\")"
]
},
{
......@@ -139,14 +148,11 @@
"risk_model = 'short'\n",
"batch = 0\n",
"horizon = map_freq(freq)\n",
"universe = Universe(\"custom\", ['zz800'])\n",
"data_source = 'postgres+psycopg2://postgres:A12345678!@10.63.6.220/alpha'\n",
"universe = Universe('zz800')\n",
"data_source = os.environ['DB_URI']\n",
"benchmark_code = 906\n",
"target_vol = 0.05\n",
"\n",
"executor = NaiveExecutor()\n",
"ref_dates = makeSchedule(start_date, end_date, freq, 'china.sse')\n",
"engine = SqlEngine(data_source)"
"weights_bandwidth = 0.02"
]
},
{
......@@ -160,38 +166,18 @@
"\"\"\"\n",
"\n",
"alpha_factors = {'f01': CSRank(LAST('EPS'))}\n",
"\n",
"weights = dict(f01=1.)\n",
"\n",
"alpha_model = ConstLinearModel(features=alpha_factors, weights=weights)\n",
"\n",
"def predict_worker(params):\n",
" data_meta = DataMeta(freq=freq,\n",
"data_meta = DataMeta(freq=freq,\n",
" universe=universe,\n",
" batch=batch,\n",
" neutralized_risk=neutralized_risk,\n",
" risk_model='short',\n",
" pre_process=[winsorize_normal, standardize],\n",
" post_process=[winsorize_normal, standardize],\n",
" post_process=[standardize],\n",
" warm_start=0,\n",
" data_source=data_source)\n",
" ref_date, model = params\n",
" er = predict_by_model(ref_date, model, data_meta)\n",
" return er"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%time\n",
"\n",
"\"\"\"\n",
"Predicting Phase\n",
"\"\"\"\n",
"predicts = [predict_worker((d.strftime('%Y-%m-%d'), alpha_model)) for d in ref_dates]"
" data_source=data_source)"
]
},
{
......@@ -201,26 +187,32 @@
"outputs": [],
"source": [
"\"\"\"\n",
"Shared Data\n",
"Constraintes settings\n",
"\"\"\"\n",
"\n",
"constraint_risk = ['SIZE', 'SIZENL', 'BETA']\n",
"total_risk_names = constraint_risk + ['total']\n",
"total_risk_names = constraint_risk + ['benchmark', 'total']\n",
"\n",
"b_type = []\n",
"l_val = []\n",
"u_val = []\n",
"\n",
"previous_pos = pd.DataFrame()\n",
"rets = []\n",
"turn_overs = []\n",
"leverags = []\n",
"\n",
"for name in total_risk_names:\n",
" b_type.append(BoundaryType.ABSOLUTE)\n",
" l_val.append(0.0)\n",
" u_val.append(0.0)\n",
" \n",
"bounds = create_box_bounds(total_risk_names, b_type, l_val, u_val)\n",
"industry_total = engine.fetch_industry_matrix_range(universe, dates=ref_dates, category=industry_name, level=industry_level)\n",
"benchmark_total = engine.fetch_benchmark_range(dates=ref_dates, benchmark=benchmark_code)\n",
"risk_cov_total, risk_exposure_total = engine.fetch_risk_model_range(universe, dates=ref_dates, risk_model=risk_model)\n",
"index_return = engine.fetch_dx_return_index_range(benchmark_code, start_date, end_date, horizon=horizon, offset=1).set_index('trade_date')"
" if name == 'benchmark':\n",
" b_type.append(BoundaryType.RELATIVE)\n",
" l_val.append(0.8)\n",
" u_val.append(1.0)\n",
" else:\n",
" b_type.append(BoundaryType.ABSOLUTE)\n",
" l_val.append(0.0)\n",
" u_val.append(0.0)\n",
"\n",
"bounds = create_box_bounds(total_risk_names, b_type, l_val, u_val)"
]
},
{
......@@ -229,94 +221,18 @@
"metadata": {},
"outputs": [],
"source": [
"# rebalance\n",
"\n",
"def create_scenario(target_vol):\n",
" \n",
" all_styles = risk_styles + industry_styles + macro_styles\n",
" previous_pos = pd.DataFrame()\n",
" rets = []\n",
" turn_overs = []\n",
" leverags = []\n",
" ics = []\n",
"\n",
" for i, ref_date in enumerate(ref_dates):\n",
" ref_date = ref_date.strftime('%Y-%m-%d')\n",
" industry_matrix = industry_total[industry_total.trade_date == ref_date]\n",
" benchmark_w = benchmark_total[benchmark_total.trade_date == ref_date]\n",
" risk_exposure = risk_exposure_total[risk_exposure_total.trade_date == ref_date]\n",
" risk_cov = risk_cov_total[risk_cov_total.trade_date == ref_date]\n",
" \n",
" total_data = pd.merge(industry_matrix, benchmark_w, on=['code'], how='left').fillna(0.)\n",
" total_data = pd.merge(total_data, risk_exposure, on=['code'])\n",
" total_data = total_data.dropna()\n",
" codes = total_data.code.values.tolist()\n",
" \n",
" risk_exposure = total_data[all_styles].values\n",
" risk_cov = risk_cov[all_styles].values\n",
" special_risk = total_data.srisk.values\n",
" sec_cov = risk_exposure @ risk_cov @ risk_exposure.T / 10000 + np.diag(special_risk ** 2) / 10000\n",
"\n",
" benchmark_w = total_data.weight.values\n",
" \n",
" total_risk_exp = np.concatenate([total_data[constraint_risk].values.astype(float),\n",
" np.ones((len(benchmark_w),1))],\n",
" axis=1)\n",
" total_risk_exp = pd.DataFrame(total_risk_exp, columns=total_risk_names)\n",
" constraints = LinearConstraints(bounds, total_risk_exp, benchmark_w)\n",
" \n",
" lbound = np.zeros(len(total_data))\n",
" ubound = np.ones(len(total_data)) * 0.1\n",
"\n",
" er = predicts[i].loc[codes].values.flatten()\n",
" cons_mat = np.ones((len(er), 1))\n",
" risk_target = (benchmark_w.sum(), benchmark_w.sum())\n",
" \n",
" try:\n",
" target_pos, _ = er_portfolio_analysis(er,\n",
" total_data.industry_name.values,\n",
" None,\n",
" constraints,\n",
" False,\n",
" benchmark_w,\n",
" method='tv',\n",
" lbound=lbound,\n",
" ubound=ubound,\n",
" cov=sec_cov,\n",
" target_vol=target_vol)\n",
" except:\n",
" import pdb\n",
" pdb.set_trace()\n",
"\n",
" target_pos['code'] = codes\n",
" turn_over, executed_pos = executor.execute(target_pos=target_pos)\n",
"\n",
" executed_codes = executed_pos.code.tolist()\n",
" dx_returns = engine.fetch_dx_return(ref_date, executed_codes, horizon=horizon, offset=1)\n",
" result = pd.merge(executed_pos, total_data[['code', 'weight']], on=['code'], how='inner')\n",
" result = pd.merge(result, dx_returns, on=['code'])\n",
" \n",
" excess_return = np.exp(result.dx.values) - 1. - index_return.loc[ref_date, 'dx']\n",
" raw_weight = result.weight_x.values\n",
" activate_weight = raw_weight - result.weight_y.values\n",
" ret = raw_weight @ excess_return\n",
" risk_adjusted_ic = np.corrcoef(excess_return, activate_weight)[0, 1]\n",
" rets.append(np.log(1. + ret))\n",
" ics.append(risk_adjusted_ic)\n",
" executor.set_current(executed_pos)\n",
" turn_overs.append(turn_over)\n",
" \n",
" leverage = raw_weight.sum()\n",
" leverags.append(leverage)\n",
" alpha_logger.info(f\"{ref_date} is finished with expected vol {np.sqrt((target_pos.weight.values - benchmark_w) @ sec_cov @ (target_pos.weight.values - benchmark_w)):.2f}\")\n",
"\n",
" ret_df = pd.DataFrame({'returns': rets, 'turn_over': turn_overs, 'IC': ics, 'leverage': leverags}, index=ref_dates)\n",
"\n",
" ret_df.loc[advanceDateByCalendar('china.sse', ref_dates[-1], freq)] = 0.\n",
" ret_df = ret_df.shift(1)\n",
" ret_df.iloc[0] = 0.\n",
" ret_df['tc_cost'] = ret_df.turn_over * 0.002\n",
" return ret_df"
"\"\"\"\n",
"Running Settings\n",
"\"\"\"\n",
"running_setting = RunningSetting(universe,\n",
" start_date,\n",
" end_date,\n",
" freq,\n",
" benchmark=benchmark_code,\n",
" weights_bandwidth=weights_bandwidth,\n",
" rebalance_method='tv',\n",
" bounds=bounds,\n",
" target_vol=target_vol)"
]
},
{
......@@ -325,7 +241,11 @@
"metadata": {},
"outputs": [],
"source": [
"ret_df = create_scenario(target_vol)"
"\"\"\"\n",
"Strategy run\n",
"\"\"\"\n",
"strategy = Strategy(alpha_model, data_meta, running_setting)\n",
"ret_df, positions = strategy.run()"
]
},
{
......@@ -334,9 +254,10 @@
"metadata": {},
"outputs": [],
"source": [
"ret_df[['returns', 'tc_cost']].cumsum().plot(figsize=(12, 6),\n",
" title='Fixed freq rebalanced: {0} with benchmark {1}'.format(freq, 905),\n",
" secondary_y='tc_cost')"
"ret_df[['excess_return', 'turn_over']].cumsum().plot(figsize=(14, 7),\n",
" title='Fixed freq rebalanced with target vol \\\n",
" at {2}: {0} with benchmark {1}'.format(freq, benchmark_code, target_vol),\n",
" secondary_y='turn_over')"
]
},
{
......@@ -363,7 +284,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
......@@ -12,15 +12,18 @@
"\n",
"* 由于``scipy``在``ashare_ex``上面性能太差,所以一般忽略``scipy``在这个股票池上的表现;\n",
"\n",
"* 时间单位都是毫秒。"
"* 时间单位都是毫秒。\n",
"\n",
"* 请在环境变量中设置`DB_URI`指向数据库"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import timeit\n",
"import numpy as np\n",
"import pandas as pd\n",
......@@ -46,7 +49,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -57,7 +60,7 @@
"factor = 'EPS'\n",
"lb = 0.0\n",
"ub = 0.1\n",
"data_source = 'postgres+psycopg2://postgres:we083826@localhost/alpha'\n",
"data_source = os.environ['DB_URI']\n",
"engine = SqlEngine(data_source)\n",
"\n",
"universes = [Universe('custom', [u_name]) for u_name in u_names]\n",
......@@ -75,22 +78,9 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-03-30 16:52:12,784 - ALPHA_MIND - INFO - sh50 is finished\n",
"2018-03-30 16:52:12,796 - ALPHA_MIND - INFO - hs300 is finished\n",
"2018-03-30 16:52:12,813 - ALPHA_MIND - INFO - zz500 is finished\n",
"2018-03-30 16:52:12,842 - ALPHA_MIND - INFO - zz800 is finished\n",
"2018-03-30 16:52:12,879 - ALPHA_MIND - INFO - zz1000 is finished\n",
"2018-03-30 16:52:13,184 - ALPHA_MIND - INFO - ashare_ex is finished\n"
]
}
],
"outputs": [],
"source": [
"df = pd.DataFrame(columns=u_names, index=['cvxpy', 'alphamind'])\n",
"\n",
......@@ -123,8 +113,8 @@
" objective = cvxpy.Minimize(-w.T * er)\n",
" prob = cvxpy.Problem(objective, constraints)\n",
" \n",
" prob.solve(solver='GLPK')\n",
" elasped_time2 = timeit.timeit(\"prob.solve(solver='GLPK')\",\n",
" prob.solve()\n",
" elasped_time2 = timeit.timeit(\"prob.solve()\",\n",
" number=number, globals=globals()) / number * 1000\n",
"\n",
" np.testing.assert_almost_equal(x1 @ er, np.array(w.value).flatten() @ er, 4)\n",
......@@ -136,72 +126,9 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>sh50</th>\n",
" <th>hs300</th>\n",
" <th>zz500</th>\n",
" <th>zz800</th>\n",
" <th>zz1000</th>\n",
" <th>ashare_ex</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>cvxpy</th>\n",
" <td>1.68</td>\n",
" <td>3.13</td>\n",
" <td>5.02</td>\n",
" <td>10.15</td>\n",
" <td>13.55</td>\n",
" <td>145.17</td>\n",
" </tr>\n",
" <tr>\n",
" <th>alphamind</th>\n",
" <td>0.20</td>\n",
" <td>0.33</td>\n",
" <td>0.44</td>\n",
" <td>0.58</td>\n",
" <td>0.72</td>\n",
" <td>2.94</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" sh50 hs300 zz500 zz800 zz1000 ashare_ex\n",
"cvxpy 1.68 3.13 5.02 10.15 13.55 145.17\n",
"alphamind 0.20 0.33 0.44 0.58 0.72 2.94"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"df"
]
......@@ -216,22 +143,9 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-03-30 16:52:13,374 - ALPHA_MIND - INFO - sh50 is finished\n",
"2018-03-30 16:52:13,509 - ALPHA_MIND - INFO - hs300 is finished\n",
"2018-03-30 16:52:13,732 - ALPHA_MIND - INFO - zz500 is finished\n",
"2018-03-30 16:52:14,220 - ALPHA_MIND - INFO - zz800 is finished\n",
"2018-03-30 16:52:14,956 - ALPHA_MIND - INFO - zz1000 is finished\n",
"2018-03-30 16:52:21,246 - ALPHA_MIND - INFO - ashare_ex is finished\n"
]
}
],
"outputs": [],
"source": [
"from cvxpy import pnorm\n",
"\n",
......@@ -263,13 +177,13 @@
" current_position=current_position,\n",
" method='interior')\n",
" elasped_time1 = timeit.timeit(\"\"\"linear_builder(er,\n",
" lbound,\n",
" ubound,\n",
" risk_constraints,\n",
" risk_target,\n",
" turn_over_target=turn_over_target,\n",
" current_position=current_position,\n",
" method='interior')\"\"\", number=number, globals=globals()) / number * 1000\n",
" lbound,\n",
" ubound,\n",
" risk_constraints,\n",
" risk_target,\n",
" turn_over_target=turn_over_target,\n",
" current_position=current_position,\n",
" method='interior')\"\"\", number=number, globals=globals()) / number * 1000\n",
" \n",
" w = cvxpy.Variable(n)\n",
" curr_risk_exposure = risk_constraints.T @ w\n",
......@@ -282,18 +196,18 @@
" objective = cvxpy.Minimize(-w.T * er)\n",
" prob = cvxpy.Problem(objective, constraints)\n",
" \n",
" prob.solve(solver='GLPK')\n",
" elasped_time2 = timeit.timeit(\"prob.solve(solver='GLPK')\",\n",
" prob.solve()\n",
" elasped_time2 = timeit.timeit(\"prob.solve()\",\n",
" number=number, globals=globals()) / number * 1000\n",
" \n",
" elasped_time3 = timeit.timeit(\"\"\"linear_builder(er,\n",
" lbound,\n",
" ubound,\n",
" risk_constraints,\n",
" risk_target,\n",
" turn_over_target=turn_over_target,\n",
" current_position=current_position,\n",
" method='simplex')\"\"\", number=number, globals=globals()) / number * 1000\n",
" lbound,\n",
" ubound,\n",
" risk_constraints,\n",
" risk_target,\n",
" turn_over_target=turn_over_target,\n",
" current_position=current_position,\n",
" method='simplex')\"\"\", number=number, globals=globals()) / number * 1000\n",
" \n",
" \n",
" np.testing.assert_almost_equal(x1 @ er, np.array(w.value).flatten() @ er, 4)\n",
......@@ -306,72 +220,9 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>sh50</th>\n",
" <th>hs300</th>\n",
" <th>zz500</th>\n",
" <th>zz800</th>\n",
" <th>zz1000</th>\n",
" <th>ashare_ex</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>cvxpy</th>\n",
" <td>2.49</td>\n",
" <td>22.19</td>\n",
" <td>51.62</td>\n",
" <td>123.42</td>\n",
" <td>190.62</td>\n",
" <td>1,946.96</td>\n",
" </tr>\n",
" <tr>\n",
" <th>alphamind</th>\n",
" <td>3.77</td>\n",
" <td>33.59</td>\n",
" <td>55.49</td>\n",
" <td>116.31</td>\n",
" <td>165.62</td>\n",
" <td>1,239.47</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" sh50 hs300 zz500 zz800 zz1000 ashare_ex\n",
"cvxpy 2.49 22.19 51.62 123.42 190.62 1,946.96\n",
"alphamind 3.77 33.59 55.49 116.31 165.62 1,239.47"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"df"
]
......@@ -388,22 +239,9 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-03-30 16:52:21,418 - ALPHA_MIND - INFO - sh50 is finished\n",
"2018-03-30 16:52:21,888 - ALPHA_MIND - INFO - hs300 is finished\n",
"2018-03-30 16:52:23,360 - ALPHA_MIND - INFO - zz500 is finished\n",
"2018-03-30 16:52:29,066 - ALPHA_MIND - INFO - zz800 is finished\n",
"2018-03-30 16:52:37,334 - ALPHA_MIND - INFO - zz1000 is finished\n",
"2018-03-30 16:56:15,174 - ALPHA_MIND - INFO - ashare_ex is finished\n"
]
}
],
"outputs": [],
"source": [
"from cvxpy import quad_form\n",
"\n",
......@@ -449,72 +287,9 @@
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>sh50</th>\n",
" <th>hs300</th>\n",
" <th>zz500</th>\n",
" <th>zz800</th>\n",
" <th>zz1000</th>\n",
" <th>ashare_ex</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>cvxpy</th>\n",
" <td>9.81</td>\n",
" <td>185.93</td>\n",
" <td>638.17</td>\n",
" <td>2,501.41</td>\n",
" <td>3,665.02</td>\n",
" <td>94,592.70</td>\n",
" </tr>\n",
" <tr>\n",
" <th>alphamind</th>\n",
" <td>0.33</td>\n",
" <td>5.66</td>\n",
" <td>14.24</td>\n",
" <td>47.29</td>\n",
" <td>109.08</td>\n",
" <td>2,515.06</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" sh50 hs300 zz500 zz800 zz1000 ashare_ex\n",
"cvxpy 9.81 185.93 638.17 2,501.41 3,665.02 94,592.70\n",
"alphamind 0.33 5.66 14.24 47.29 109.08 2,515.06"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"df"
]
......@@ -529,22 +304,9 @@
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-03-30 16:56:16,044 - ALPHA_MIND - INFO - sh50 is finished\n",
"2018-03-30 16:56:16,705 - ALPHA_MIND - INFO - hs300 is finished\n",
"2018-03-30 16:56:19,651 - ALPHA_MIND - INFO - zz500 is finished\n",
"2018-03-30 16:56:30,227 - ALPHA_MIND - INFO - zz800 is finished\n",
"2018-03-30 16:56:44,840 - ALPHA_MIND - INFO - zz1000 is finished\n",
"2018-03-30 17:08:25,343 - ALPHA_MIND - INFO - ashare_ex is finished\n"
]
}
],
"outputs": [],
"source": [
"df = pd.DataFrame(columns=u_names, index=['cvxpy', 'alphamind'])\n",
"number = 1\n",
......@@ -590,72 +352,9 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>sh50</th>\n",
" <th>hs300</th>\n",
" <th>zz500</th>\n",
" <th>zz800</th>\n",
" <th>zz1000</th>\n",
" <th>ashare_ex</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>cvxpy</th>\n",
" <td>10.78</td>\n",
" <td>300.36</td>\n",
" <td>1,377.87</td>\n",
" <td>5,244.64</td>\n",
" <td>6,758.95</td>\n",
" <td>334,990.46</td>\n",
" </tr>\n",
" <tr>\n",
" <th>alphamind</th>\n",
" <td>0.32</td>\n",
" <td>4.78</td>\n",
" <td>16.02</td>\n",
" <td>41.63</td>\n",
" <td>64.54</td>\n",
" <td>1,134.95</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" sh50 hs300 zz500 zz800 zz1000 ashare_ex\n",
"cvxpy 10.78 300.36 1,377.87 5,244.64 6,758.95 334,990.46\n",
"alphamind 0.32 4.78 16.02 41.63 64.54 1,134.95"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"df"
]
......@@ -672,19 +371,7 @@
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"2018-03-30 17:08:25,991 - ALPHA_MIND - INFO - sh50 is finished\n",
"2018-03-30 17:08:26,763 - ALPHA_MIND - INFO - hs300 is finished\n",
"2018-03-30 17:08:30,276 - ALPHA_MIND - INFO - zz500 is finished\n",
"2018-03-30 17:08:41,580 - ALPHA_MIND - INFO - zz800 is finished\n",
"2018-03-30 17:09:18,856 - ALPHA_MIND - INFO - zz1000 is finished\n"
]
}
],
"outputs": [],
"source": [
"df = pd.DataFrame(columns=u_names, index=['cvxpy', 'alphamind'])\n",
"number = 1\n",
......@@ -842,7 +529,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"* 请在环境变量中设置`DB_URI`指向数据库\n",
"* 请在环境变量中设置`DATAYES_TOKEN`作为通联数据登陆凭证"
]
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%matplotlib inline\n",
"import os\n",
"from matplotlib import pyplot as plt\n",
"import uqer\n",
"import numpy as np\n",
......@@ -20,31 +29,23 @@
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"16937@wmcloud.com 账号登录成功\n"
]
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"_ = uqer.Client(token='')"
"_ = uqer.Client(token=os.environ['DATAYES_TOKEN'])"
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"ref_date = '2017-06-23'\n",
"factor = 'EPS'\n",
"\n",
"engine = SqlEngine()\n",
"engine = SqlEngine(os.environ['DB_URI'])\n",
"universe = Universe('custom', ['zz800'])"
]
},
......@@ -96,7 +97,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -108,7 +109,7 @@
},
{
"cell_type": "code",
"execution_count": 8,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -118,20 +119,9 @@
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"800"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(total_data)"
]
......@@ -146,17 +136,9 @@
},
{
"cell_type": "code",
"execution_count": 32,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"128 ms ± 4.27 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)\n"
]
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%timeit\n",
"neutralized_factor_uqer = uqer.neutralize(total_data[factor],\n",
......@@ -166,97 +148,9 @@
},
{
"cell_type": "code",
"execution_count": 33,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>uqer</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>000001</th>\n",
" <td>-0.076975</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000002</th>\n",
" <td>-0.288382</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000006</th>\n",
" <td>-0.054668</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000008</th>\n",
" <td>-0.034123</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000009</th>\n",
" <td>0.029815</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000012</th>\n",
" <td>0.089305</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000021</th>\n",
" <td>0.034998</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000025</th>\n",
" <td>-0.048613</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000027</th>\n",
" <td>-0.003274</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000028</th>\n",
" <td>0.347669</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" uqer\n",
"000001 -0.076975\n",
"000002 -0.288382\n",
"000006 -0.054668\n",
"000008 -0.034123\n",
"000009 0.029815\n",
"000012 0.089305\n",
"000021 0.034998\n",
"000025 -0.048613\n",
"000027 -0.003274\n",
"000028 0.347669"
]
},
"execution_count": 33,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"neutralized_factor_uqer = uqer.neutralize(total_data[factor],\n",
" target_date=ref_date.replace('-', ''),\n",
......@@ -267,78 +161,18 @@
},
{
"cell_type": "code",
"execution_count": 34,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"800"
]
},
"execution_count": 34,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(neutralized_factor_uqer)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"BETA 6.017409e-14\n",
"MOMENTUM 7.993606e-15\n",
"SIZE -6.938894e-14\n",
"EARNYILD -1.829648e-13\n",
"RESVOL 2.109424e-13\n",
"GROWTH -1.021405e-13\n",
"BTOP -1.425526e-13\n",
"LEVERAGE -1.541545e-13\n",
"LIQUIDTY 4.973799e-14\n",
"SIZENL 5.417888e-14\n",
"Bank -8.770762e-15\n",
"RealEstate -1.076916e-14\n",
"Health -6.661338e-16\n",
"Transportation 3.774758e-15\n",
"Mining -4.551914e-15\n",
"NonFerMetal 9.020562e-15\n",
"HouseApp -4.218847e-15\n",
"LeiService -4.163336e-16\n",
"MachiEquip 2.395306e-14\n",
"BuildDeco 9.547918e-15\n",
"CommeTrade 5.884182e-15\n",
"CONMAT 7.188694e-15\n",
"Auto 6.883383e-15\n",
"Textile 4.996004e-15\n",
"FoodBever -6.217249e-15\n",
"Electronics 1.132427e-14\n",
"Computer 8.604228e-15\n",
"LightIndus 1.346145e-15\n",
"Utilities -6.258882e-15\n",
"Telecom 3.608225e-16\n",
"AgriForest 8.160139e-15\n",
"CHEM -6.994405e-15\n",
"Media 1.088019e-14\n",
"IronSteel -1.915135e-15\n",
"NonBankFinan -9.880985e-15\n",
"ELECEQP -5.773160e-15\n",
"AERODEF 4.031497e-15\n",
"Conglomerates -5.023759e-15\n",
"dtype: float64"
]
},
"execution_count": 35,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"risk_exposure_uqer = uqer.DataAPI.RMExposureDayGet(tradeDate=ref_date.replace('-', '')).set_index('ticker')\n",
"targeted_secs = risk_exposure_uqer.loc[neutralized_factor_uqer.index]\n",
......@@ -360,7 +194,7 @@
},
{
"cell_type": "code",
"execution_count": 36,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -370,17 +204,9 @@
},
{
"cell_type": "code",
"execution_count": 37,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"139 µs ± 8 µs per loop (mean ± std. dev. of 7 runs, 10000 loops each)\n"
]
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"%%timeit\n",
"neutralized_factor_alphamind = neutralize(x, y, weights=np.ones(len(y)))"
......@@ -388,78 +214,9 @@
},
{
"cell_type": "code",
"execution_count": 38,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>uqer</th>\n",
" <th>alpha-mind</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>000001</th>\n",
" <td>-0.076975</td>\n",
" <td>-0.076975</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000002</th>\n",
" <td>-0.288382</td>\n",
" <td>-0.288382</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000006</th>\n",
" <td>-0.054668</td>\n",
" <td>-0.054668</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000008</th>\n",
" <td>-0.034123</td>\n",
" <td>-0.034123</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000009</th>\n",
" <td>0.029815</td>\n",
" <td>0.029815</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" uqer alpha-mind\n",
"000001 -0.076975 -0.076975\n",
"000002 -0.288382 -0.288382\n",
"000006 -0.054668 -0.054668\n",
"000008 -0.034123 -0.034123\n",
"000009 0.029815 0.029815"
]
},
"execution_count": 38,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"neutralized_factor_alphamind = neutralize(x, y, weights=np.ones(len(y)))\n",
"alphamind_series = pd.Series(neutralized_factor_alphamind.flatten(), index=total_data.index)\n",
......@@ -469,20 +226,9 @@
},
{
"cell_type": "code",
"execution_count": 39,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"800"
]
},
"execution_count": 39,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"len(alphamind_series)"
]
......@@ -497,7 +243,7 @@
},
{
"cell_type": "code",
"execution_count": 40,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -506,96 +252,9 @@
},
{
"cell_type": "code",
"execution_count": 41,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>code</th>\n",
" <th>isOpen</th>\n",
" <th>EPS</th>\n",
" <th>srisk</th>\n",
" <th>BETA</th>\n",
" <th>MOMENTUM</th>\n",
" <th>SIZE</th>\n",
" <th>EARNYILD</th>\n",
" <th>RESVOL</th>\n",
" <th>GROWTH</th>\n",
" <th>...</th>\n",
" <th>Telecom</th>\n",
" <th>AgriForest</th>\n",
" <th>CHEM</th>\n",
" <th>Media</th>\n",
" <th>IronSteel</th>\n",
" <th>NonBankFinan</th>\n",
" <th>ELECEQP</th>\n",
" <th>AERODEF</th>\n",
" <th>Conglomerates</th>\n",
" <th>COUNTRY</th>\n",
" </tr>\n",
" <tr>\n",
" <th>ticker</th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" <th></th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" </tbody>\n",
"</table>\n",
"<p>0 rows × 43 columns</p>\n",
"</div>"
],
"text/plain": [
"Empty DataFrame\n",
"Columns: [code, isOpen, EPS, srisk, BETA, MOMENTUM, SIZE, EARNYILD, RESVOL, GROWTH, BTOP, LEVERAGE, LIQUIDTY, SIZENL, Bank, RealEstate, Health, Transportation, Mining, NonFerMetal, HouseApp, LeiService, MachiEquip, BuildDeco, CommeTrade, CONMAT, Auto, Textile, FoodBever, Electronics, Computer, LightIndus, Utilities, Telecom, AgriForest, CHEM, Media, IronSteel, NonBankFinan, ELECEQP, AERODEF, Conglomerates, COUNTRY]\n",
"Index: []\n",
"\n",
"[0 rows x 43 columns]"
]
},
"execution_count": 41,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"total_data.loc[missed_codes]"
]
......@@ -610,7 +269,7 @@
},
{
"cell_type": "code",
"execution_count": 42,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -619,7 +278,7 @@
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -629,7 +288,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -646,7 +305,7 @@
},
{
"cell_type": "code",
"execution_count": 45,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
......@@ -656,126 +315,18 @@
},
{
"cell_type": "code",
"execution_count": 46,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<matplotlib.axes._subplots.AxesSubplot at 0x1fcc2856978>"
]
},
"execution_count": 46,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA10AAAGUCAYAAADOA0gKAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4yLCBodHRwOi8vbWF0cGxvdGxpYi5vcmcvNQv5yAAAIABJREFUeJzt3XuQXtV55/tvowYhlw1IarBpIdvioDqDXCcWAYN8qYEREkg4IJkojyHEFokS2Q4uElTjMg6uUQWRCYTbwTnYjrgEQXkMjwkOSgWsCITt1MHcgk08MsVEXJyWm7GnkcBUYbWs5p0/eknTat6+SG8vqSW+n6ouv3vttZ+990P+yK/W3lttjUYDSZIkSVIdh+zvC5AkSZKkg5mhS5IkSZIqMnRJkiRJUkWGLkmSJEmqyNAlSZIkSRUZuiRJkiSpovaxKBIRC4CbgAnArZl59aD9E4E7gZOBV4BPZuZLZd+XgGVAH3BpZq4brmZEfB74U+D/Ao7OzJ4y3lbmnwO8AVycmU+XfUuBL5fLuSoz14zFfUuSJEnSSFpe6YqICcDNwEJgFnBhRMwaNG0ZsDUzTwBuBK4px84CLgA+ACwAvhoRE0ao+f8D84CfDjrHQmBm+VsOfK2cYwqwEjgNOBVYGRGTW71vSZIkSRqNsXi88FRgU2a+kJnbgbuBRYPmLAJ2ri7dC5xZVqYWAXdnZm9mvghsKvWGrJmZP9y5StbkHHdmZiMzHwOOiohjgbOB9Zm5JTO3AuvpD3iSJEmSVN1YPF44DegasL2Z/lWlpnMyc0dEvAZMLeOPDTp2Wvk9Us3RXMe0YcbfIiKW079KRmaePML5JEmSJKltpAljEbqanaQxyjlDjTdbgRtcc7TXMZrrAyAzVwOrd87p7u4e4ZT7RkdHBz09Pfv7Mg5q9rg+e1yfPa7PHtdnj+uzx/XZ47rGU387OztHNW8sHi/cDEwfsH0cMDit7JoTEe3AkcCWYY4dTc3RXsfe1JIkSZKkMTEWK11PAjMjYgbwM/o/jPG7g+asBZYCPwCWABsysxERa4H/FhE3AJ30fwTjCfpXp0aqOdha4PMRcTf9jyK+lpkvR8Q64L8O+HjGWcCXWrpjSZIkSRqllle6MnMH8HlgHfBs/1BujIgrI+K8Mu02YGpEbAJWAJeXYzcCCfwE+A5wSWb2DVUTICIujYjN9K9Y/WtE3FrO8QDwAv0f47gF+ONyji3AKvrD4ZPAlWVMkiRJkqprazRGelXqbct3ut5G7HF99rg+e1yfPa7PHtdnj+tr1uNGo8G2bdt48803aWsb8bsLGsbEiRPp7e3dZ+drNBoccsghHH744W/5b1fe6donH9KQJEmSNIxt27Zx6KGH0t7u//vdqvb2diZMmLBPz7ljxw62bdvGpEmT9ur4sfiQhiRJkqRhvPnmmwauA1h7eztvvvnmXh9v6JIkSZIq85HCA18r/w0NXZIkSZJUkaFLkiRJ0n7T1dXF3Llz9/dlVGXokiRJkrRX+vr69vclHBAMXZIkSdJBbvBq0te//nWuv/56AP71X/+VefPmce6557Jq1apd8/r6+li1ahXnnHMO8+bN46677gLg0UcfZcmSJVxyySWceeaZo76GRqOxq/6ZZ57J/fff/5Y5zz33HB//+MeZP38+8+bN44UXXmjltscNP6EiSZIk7UNv3n0Lja4Xx7Rm2/QZHHLBH+3VsStWrGDVqlV8+MMfZtWqVbvGv/nNb/Kud72LBx54gN7eXhYvXszpp58OwI9+9CM2bNjAe9/73lGf54EHHmDjxo2sX7+eLVu2cM455zBnzpzd5tx1110sW7aM888/n+3btx80K2mGLkmSJOlt6pe//CWvvfYaH/7whwH47d/+bR555BEAvve97/Hss8/yj//4jwC8/vrrvPjiixx66KHMnj17jwIXwBNPPMHixYuZMGECRx99NHPmzOGZZ57hxBNP3DXn5JNP5itf+Qovv/wyCxcu5Pjjjx+jO92/DF2SJEnSPrS3K1KtmDBhwm7/ztS2bduA/kf+hvsU+lVXXcUZZ5yx29ijjz7KO97xjqbzH3zwQW644QYArrvuOj74wQ/u2tdoNEa8zk984hOcdNJJPPzww1x00UVce+21fOxjHxvxuPHOd7okSZKkg9zRRx9NT08PW7Zsobe3l4ceegiAI488kiOOOIInnngCgG9/+9u7jjn99NO58847+fWvfw3A888/zxtvvDHseRYuXMj69etZv379boELYM6cOaxdu5a+vj5eeeUVHn/8cWbPnr3bnJ/+9Ke8733vY9myZcyfP59nn3225XsfD1zpkiRJkg5yhx56KJdddhnnnnsu06dP54QTTti174YbbmDFihVMmjRpt1Wt3/3d36Wrq4sFCxbQaDSYMmUKt99++15fw8KFC/mXf/kX5s+fT1tbG1dccQXHHHMMXV1du+asXbuW++67j/b2do455hguu+yyvT7feNI2mmW+t6lGd3f3/r4GADo6Oujp6dnfl3FQs8f12eP67HF99rg+e1yfPa6vWY/feOONIR/JG0+6urpYunQpGzZs2N+XMqT29nZ27Nixz8/b7L9hZ2cnwNDPZxY+XihJkiRJFRm6JEmSJAEwffr0cb3KdaAydEmSJElSRYYuSZIkSarI0CVJkiRJFRm6JEmSJKkiQ5ckSZL0NnbaaaexZcuWlufsjWuvvZbvf//7e3TMKaecMibX0tXVxdy5c1uuMxr+48iSJEmS9osvfOEL+/sS9glDlyRJkvQ28Ad/8Ad0d3fT29vLsmXL+L3f+73d9nd1dXHRRRdx0kknsXHjRmbMmMFXvvIVJk2aBMDtt9/O+vXr2bFjB3/zN3/DCSecwA9/+ENWrlzJtm3bOPzww7nhhhs44YQTuOeee1i3bh19fX0899xzfOYzn2H79u383d/9HYcddhh33XUXkydP5k//9E+ZN28ev/Vbv8Vpp53G7/zO77zlHFu2bOGSSy7hlVdeYfbs2TQajT2670ajwVVXXcUjjzxCW1sbl156KYsWLdptznPPPceKFSvYvn07jUaD1atXc/zxx7fW8AEMXZIkSdI+dOtTP+fFrdvGtOaMyYfzh6e8e9g5119/PZMnT+ZXv/oVH//4xznnnHOYMmXKbnOef/55rr/+ej70oQ+xYsUK1qxZw2c/+1kApkyZwrp167jjjjv4+te/znXXXccJJ5zAfffdR3t7O9///ve55ppruOWWW4D+ILNu3Tp6e3v56Ec/yp/92Z/xT//0T6xcuZJ7772XP/qjP3rLNTY7x4033sipp57KZZddxkMPPcQ3vvGNPerNAw88wMaNG1m/fj1btmzhnHPOYc6cObvNueuuu1i2bBnnn38+27dvp6+vb4/OMRLf6ZIkSZLeBm6//XbmzZvHueeeS3d3Ny+++OJb5nR2dvKhD30IgPPPP58nnnhi176FCxcC8Bu/8Rt0dXUB8Mtf/pLPfOYzzJ07lz//8z/nueee2zX/Ix/5CO985zuZOnUq73rXu5g/fz4AJ5544q7jB2t2jscee4zzzz8fgHnz5nHUUUft0X0/8cQTLF68mAkTJnD00UczZ84cnnnmmd3mnHzyyfz1X/81N998M5s3b961ujdWXOmSJEmS9qGRVqRqePTRR/nnf/5n/uEf/oFJkyaxZMkSent73zKvra1tyO2JEycCMGHChF0rQddeey0f+chHuO222+jq6mLJkiW75h922GG7fh9yyCG7jm9raxtyJanZOZpd12APPvggN9xwAwDXXXcdH/zgB3ftG83jiJ/4xCc46aSTePjhh7nooou49tpr+djHPjbicaPlSpckSZJ0kHv99dc58sgjmTRpEps2beLpp59uOu9nP/sZTz31FAD333//rlWv4eq+5z3vASAzx/aiizlz5nDfffcBsGHDBl599dW3zFm4cCHr169n/fr1uwWuncevXbuWvr4+XnnlFR5//HFmz56925yf/vSnvO9972PZsmXMnz+fZ599dkzvwdAlSZIkHeTOOOMM+vr6mDdvHn/1V3/Fb/7mbzadN3PmTL71rW8xb948Xn31VZYuXTps3c997nP85V/+JYsWLRrz96B2uuyyy3j88cc5++yz+d73vsdxxx23R8cvXLiQE088kfnz5xMRXHHFFRxzzDG7zVm7di1z585l/vz5PP/887ut2I2Ftj39+sfbSKO7u3t/XwMAHR0d9PT07O/LOKjZ4/rscX32uD57XJ89rs8e19esx2+88QbveMc79tMVjU5XVxdLly5lw4YN+/tShtXe3s6OHTv2+Xmb/Tfs7OwEGP7ZR1zpkiRJkqSqDF2SJEmSmD59+rhf5TpQGbokSZKkynyl58DXyn9DQ5ckSZJU2SGHHLJf3kPS2NixYweHHLL30cl/p0uSJEmq7PDDD2fbtm309vaO+G9OaXgTJ05s+m+M1dJoNDjkkEM4/PDD97qGoUuSJEmqrK2tjUmTJu3vyzgoHIhf4PTxQkmSJEmqyNAlSZIkSRUZuiRJkiSpIkOXJEmSJFVk6JIkSZKkisbk64URsQC4CZgA3JqZVw/aPxG4EzgZeAX4ZGa+VPZ9CVgG9AGXZua64WpGxAzgbmAK8DTwqczcHhE3Av+pnPIdwDGZeVQ5pg/4cdn375l53ljctyRJkiSNpOWVroiYANwMLARmARdGxKxB05YBWzPzBOBG4Jpy7CzgAuADwALgqxExYYSa1wA3ZuZMYGupTWZelpmzM3M28NfAfQPO/6ud+wxckiRJkvalsXi88FRgU2a+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\n",
"text/plain": [
"<matplotlib.figure.Figure at 0x1fcc28465c0>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df[['uqer - ols', 'alphamind - ols']].plot(figsize=(14, 7), ylim=(-1e-4, 1e-4))"
]
},
{
"cell_type": "code",
"execution_count": 47,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div>\n",
"<style scoped>\n",
" .dataframe tbody tr th:only-of-type {\n",
" vertical-align: middle;\n",
" }\n",
"\n",
" .dataframe tbody tr th {\n",
" vertical-align: top;\n",
" }\n",
"\n",
" .dataframe thead th {\n",
" text-align: right;\n",
" }\n",
"</style>\n",
"<table border=\"1\" class=\"dataframe\">\n",
" <thead>\n",
" <tr style=\"text-align: right;\">\n",
" <th></th>\n",
" <th>uqer</th>\n",
" <th>alpha-mind</th>\n",
" <th>ols</th>\n",
" <th>uqer - ols</th>\n",
" <th>alphamind - ols</th>\n",
" </tr>\n",
" </thead>\n",
" <tbody>\n",
" <tr>\n",
" <th>000001</th>\n",
" <td>-0.076975</td>\n",
" <td>-0.076975</td>\n",
" <td>-0.076975</td>\n",
" <td>-1.609823e-15</td>\n",
" <td>-1.165734e-15</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000002</th>\n",
" <td>-0.288382</td>\n",
" <td>-0.288382</td>\n",
" <td>-0.288382</td>\n",
" <td>3.885781e-16</td>\n",
" <td>-2.220446e-16</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000006</th>\n",
" <td>-0.054668</td>\n",
" <td>-0.054668</td>\n",
" <td>-0.054668</td>\n",
" <td>8.881784e-16</td>\n",
" <td>6.106227e-16</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000008</th>\n",
" <td>-0.034123</td>\n",
" <td>-0.034123</td>\n",
" <td>-0.034123</td>\n",
" <td>-5.204170e-16</td>\n",
" <td>-7.910339e-16</td>\n",
" </tr>\n",
" <tr>\n",
" <th>000009</th>\n",
" <td>0.029815</td>\n",
" <td>0.029815</td>\n",
" <td>0.029815</td>\n",
" <td>-2.567391e-16</td>\n",
" <td>2.775558e-16</td>\n",
" </tr>\n",
" </tbody>\n",
"</table>\n",
"</div>"
],
"text/plain": [
" uqer alpha-mind ols uqer - ols alphamind - ols\n",
"000001 -0.076975 -0.076975 -0.076975 -1.609823e-15 -1.165734e-15\n",
"000002 -0.288382 -0.288382 -0.288382 3.885781e-16 -2.220446e-16\n",
"000006 -0.054668 -0.054668 -0.054668 8.881784e-16 6.106227e-16\n",
"000008 -0.034123 -0.034123 -0.034123 -5.204170e-16 -7.910339e-16\n",
"000009 0.029815 0.029815 0.029815 -2.567391e-16 2.775558e-16"
]
},
"execution_count": 47,
"metadata": {},
"output_type": "execute_result"
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"df.head()"
]
......@@ -804,7 +355,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
......@@ -58,7 +58,7 @@
"\n",
" objective = cvxpy.Minimize(-w.T * er)\n",
" prob = cvxpy.Problem(objective, constraints)\n",
" prob.solve(solver='GLPK')\n",
" prob.solve()\n",
" return w, prob"
]
},
......@@ -113,6 +113,13 @@
" print(\"{0:<8}{1:>12.2f}{2:>12.2f}{3:>12f}{4:>12f}{5:>12f}{6:>15}\".format(n, elapsed*1000, result[1], s.min(), s.max(), s.sum(), s[0] + s[1]))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
......@@ -137,7 +144,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.4"
"version": "3.6.5"
},
"varInspector": {
"cols": {
......
# 中证500增强新策略设置建议
## 当前情况
* 市场风格中,`EARNYILD`以及`SIZE`进入2月份以来发生了反转;
* 当前中证500策略回撤到达1.6%左右,打破之前的最大回撤记录;
* 当前策略比较显著的风格暴露:`EARNYILD`以及`BETA`的正向暴露;
* 存在风格暴露不太确定,没有上限的问题。
## 方案
### 措施
* 控制`EARNYILD`暴露在0.25至0.5之间;
* 控制`LIQUIDTY`暴露在-0.25至-0.5之间;
* 控制`GROWTH`暴露在0.2到0.4之间;
* 控制`SIZE`暴露至-0.2到0.0之间;
* 控制`BETA`暴露至0.0;
* 使用新的行业分类:sw_adj;
* 其他限制保持与当前一致。
### 特点
* 仍然会坚持当前以估值为核心,重视历史业绩表现的风格;
* 增加对风格暴露的控制,加入主观的风格判定;
* 主动增加对成长性的风格暴露;
* 对于某些行业采取主动暴露;
* 给予风格暴露上限,防止风格过于明显。
### 负面影响:
* 会提高换手率;
* 需要关注行业基本面的变化;
* 需要更多的关注风格的切换,难度较大。
### 注意事项
* 需要定期review当前风格;
* 对于`LIQUIDTY`的因子暴露,需要收集实际成交的信息。了解当前的交易成本假设是否能够覆盖低流动率下的真实交易成本。
## 因子
### 因子内容
新的因子组合,包括的内容:
* 估值类:`ep_q`;
* 质量类:`roe_q`;
* 成长类:`SGRO`, `GREV`;
* 技术类:`IVR`, `ILLIQUIDITY`;
* 预期信号:`con_target_price`, `con_pe_rolling_order`;
```python
alpha_factors = {
'f01': LAST('ep_q'),
'f02': LAST('roe_q'),
'f03': LAST('SGRO'),
'f04': LAST('GREV'),
'f05': LAST('con_target_price'),
'f06': LAST('con_pe_rolling_order'),
'f07': LAST('IVR'),
'f08': LAST('ILLIQUIDITY'),
}
```
### 因子权重
基本原则:
* `ep_q`作为核心因子,但是最近回撤较大,取1的权重;
* 基本面因子,赋予1的权重;
* 预期类因子,赋予1的权重;
* 技术指标类因子,最近表现较好,权重至0.5的权重。
```python
weights = dict(f01=1.,
f02=1.,
f03=1.,
f04=1.,
f05=1.,
f06=-1.,
f07=0.5,
f08=0.5,
)
```
# 沪深300增强新策略设置建议
## 当前情况
* 市场风格中,`EARNYILD`以及`SIZE`进入2月份以来发生了反转;
* 当前沪深300策略属于回测期;
* 当前策略比较显著的风格暴露:`EARNYILD`
* 当前策略的跟踪误差相对于中证500策略偏低;
* 存在风格暴露不太确定,没有上限的问题。
## 方案
### 措施
* 控制`EARNYILD`暴露在0.25至0.5之间;
* 控制`LIQUIDTY`暴露在-0.25至-0.5之间;
* 控制`SIZE`暴露至-0.2到0.0之间;
* 控制`BETA``SIZE``GROWTH`暴露至0.0;
* 使用新的行业分类:sw_adj;
* 其他限制保持与当前一致。
### 特点
* 仍然会坚持当前以估值为核心,重视历史业绩表现的风格;
* 增加对风格暴露的控制,加入主观的风格判定;
* 主动增加对成长性的风格暴露;
* 给予风格暴露上限,防止风格过于明显。
### 负面影响:
* 会提高换手率;
* 需要关注行业基本面的变化;
* 需要更多的关注风格的切换,难度较大。
### 注意事项
* 需要定期review当前风格;
* 对于`LIQUIDTY`的因子暴露,需要收集实际成交的信息。了解当前的交易成本假设是否能够覆盖低流动率下的真实交易成本。
## 因子
### 因子内容
新的因子组合,包括的内容:
* 估值类:`ep_q`;
* 质量类:`roe_q`;
* 成长类:`SGRO`, `GREV`;
* 技术类:`IVR`, `ILLIQUIDITY`;
* 预期信号:`con_target_price`, `con_pe_rolling_order`;
* 个股指标:`DividendPaidRatio`;
```python
alpha_factors = {
'f01': LAST('ep_q'),
'f02': LAST('roe_q'),
'f03': LAST('GREV'),
'f04': LAST('SGRO'),
'f05': LAST('con_target_price'),
'f06': LAST('con_pe_rolling_order'),
'f07': LAST('IVR'),
'f08': LAST('ILLIQUIDITY'),
'f09': LAST('DividendPaidRatio')
}
```
### 因子权重
基本原则:
* `ep_q`作为核心因子,但是最近回撤较大,取1的权重;
* 基本面因子,赋予1的权重;
* 预期类因子,赋予1的权重;
* 技术指标类因子,最近表现较好,权重至0.5的权重。
```python
weights = dict(f01=1.,
f02=1.,
f03=1.,
f04=1.,
f05=1.,
f06=-1.,
f07=0.5,
f08=0.5,
f09=0.5
)
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment