diff --git a/config.yml b/config.yml index 1b20c3cb1edcc78b121d6b0427a64bd6473cff51..a490ea58a4f097306db4066d8ef416a6d2e54f48 100644 --- a/config.yml +++ b/config.yml @@ -45,6 +45,11 @@ logger: root: level: INFO handlers: [console] +datas: + navs: + exrate: + - from: EUR + ticker: EURUSD BGN Curncy fund_pool: fund_optimize: sortino_weight: diff --git a/datas/datum/api.py b/datas/datum/api.py index 609969164731ff8ea938eb0609321120cd4a20d1..9c5ff4b0aee7187da91582dab3e9e9b688e0379c 100644 --- a/datas/datum/api.py +++ b/datas/datum/api.py @@ -2,9 +2,13 @@ import datas.datum.robo_base_datum as _rbd from datas.datum.enums import DatumType -def get_fund_datums(crncy=None, risk=None): +def get_fund_datums(crncy=None, risk=None, fund_ids=None): return _rbd.get_base_datums(type=DatumType.FUND, crncy=crncy, risk=risk) +def get_without_crncy(crncy): + pass + + if __name__ == '__main__': print(get_fund_datums(risk=1)) diff --git a/datas/mysql.sql b/datas/mysql.sql index 7eccdcf560a14108a1c16e091cc32296b95f9e7f..88b3e543be589162534de1886c9b54effe2e284f 100644 --- a/datas/mysql.sql +++ b/datas/mysql.sql @@ -38,3 +38,33 @@ CREATE TABLE IF NOT EXISTS robo_fund_navs ) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT '基金数æ®è¡¨'; + +CREATE TABLE IF NOT EXISTS `robo_exrate` +( + `re_id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `re_ticker` VARCHAR(255) NOT NULL COMMENT '汇率ticker', + `re_date` DATETIME NOT NULL COMMENT '日期', + `re_close` DOUBLE NOT NULL COMMENT '收盘价', + `re_create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `re_update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`re_id`), + UNIQUE (`re_ticker`, `re_date`), + INDEX (`re_date`) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT '基金数æ®è¡¨'; + + +CREATE TABLE IF NOT EXISTS robo_optimize_pool +( + rop_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + rop_date DATETIME NOT NULL COMMENT 'æ•°æ®æ—¥æœŸ', + rop_fund_ids JSON DEFAULT NULL COMMENT '基金ID', + rop_create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + rop_update_time DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (rop_id), + INDEX (rop_date) +) ENGINE = InnoDB + AUTO_INCREMENT = 0 + DEFAULT CHARSET = utf8mb4 COMMENT 'ä¼˜é€‰åŸºé‡‘æ± '; + diff --git a/datas/navs/__init__.py b/datas/navs/__init__.py index e4429d059eca1a2203970bb3a20d690242ba62fa..68f23671ff65b789e90707cdafbd9f21fcdfc8e9 100644 --- a/datas/navs/__init__.py +++ b/datas/navs/__init__.py @@ -1,3 +1,5 @@ -from .robo_fund_navs import * +from .api import ( + get_navs +) -del robo_fund_navs +del api diff --git a/datas/navs/api.py b/datas/navs/api.py new file mode 100644 index 0000000000000000000000000000000000000000..921910e543849542e13e916e615be114486486e7 --- /dev/null +++ b/datas/navs/api.py @@ -0,0 +1,32 @@ +import pandas as _pd +from datas.navs import robo_exrate as _re, robo_fund_navs as _navs +from datas import datum as _datum, DatumType +from utils import config, to_bool +from datetime import timedelta + +navs_config = config['datas']['navs'] if 'datas' in config and 'navs' in config['datas'] else {} + + +def get_navs(fund_id=None, min_date=None, max_date=None): + navs = _navs.get_navs(fund_id=fund_id, min_date=min_date, max_date=max_date) + if 'exrate' in navs_config: + navs = _pd.DataFrame(navs) + navs = navs.pivot_table(index='nav_date', columns='fund_id', values='nav_cal') + for exrate_config in navs_config['exrate']: + exrate = _pd.DataFrame(_re.get_exrates(ticker=exrate_config['ticker'], min_date=navs.index.min(), max_date=navs.index.max())) + exrate = exrate[['date', 'close']] + exrate.set_index('date', inplace=True) + for fund in _datum.get_fund_datums(crncy=exrate_config['from']): + navs[fund['id']] = round(navs[fund['id']] * exrate['close'], 4) + navs = navs.reset_index().melt(id_vars='nav_date', value_name='nav_cal') + navs.dropna(inplace=True) + navs = navs[['fund_id', 'nav_date', 'nav_cal']] + navs.sort_values('fund_id', inplace=True) + navs = navs.to_dict('records') + return navs + + +if __name__ == '__main__': + from utils import parse_date + + print(get_navs(min_date=parse_date('2022-11-01'))) diff --git a/datas/navs/robo_exrate.py b/datas/navs/robo_exrate.py new file mode 100644 index 0000000000000000000000000000000000000000..3d21a5970f4daa5288a13bf3a3242325e28e036b --- /dev/null +++ b/datas/navs/robo_exrate.py @@ -0,0 +1,26 @@ +from utils import read, where, format_date + + +@read +def _select(*args, **kwargs): + return f'''select re_id as id, re_ticker as ticker, re_date as date, re_close as close from robo_exrate {where(*args, **kwargs)}''' + + +def get_exrates(ticker=None, min_date=None, max_date=None): + sqls = [] + if min_date: + sqls.append(f"re_date >= '{format_date(min_date)}'") + if max_date: + sqls.append(f"re_date <= '{format_date(max_date)}'") + return _select(*sqls, re_ticker=ticker) + + +@read(one=True) +def get_exrate(ticker, date): + return f'''select re_id as id, re_ticker as ticker, re_date as date, re_close as close from robo_exrate {where(re_ticker=ticker, re_date=date)}''' + + +if __name__ == '__main__': + from utils import parse_date + + print(get_exrate(date=parse_date('2022-11-01'), ticker='EURUSD BGN Curncy')) diff --git a/fund_pool/fund_optimize.py b/fund_pool/fund_optimize.py index 9bf4025f2c26aac76ef5ca4daa1aa356fd579367..72828aae479482e0645fc439bcefdde17408cb8a 100644 --- a/fund_pool/fund_optimize.py +++ b/fund_pool/fund_optimize.py @@ -4,11 +4,14 @@ from datas import navs from dateutil.relativedelta import relativedelta from empyrical import sortino_ratio -optimize_config = config['fund_pool']['fund_optimize'] -sortino_config = [{**x, 'name': [f"sortino_{y[1]}_{y[0]}" for y in x.items() if y[0] != 'weight'][0]} for x in optimize_config['sortino_weight']] +optimize_config = config['fund_pool']['fund_optimize'] if 'fund_pool' in config and 'fund_optimize' in config['fund_pool'] else {} +sortino_config = [{**x, 'name': [f"sortino_{y[1]}_{y[0]}" for y in x.items() if y[0] != 'weight'][0]} for x in optimize_config['sortino_weight']] \ + if 'sortino_weight' in optimize_config else [] def find_optimize(fund_ids, day): + if not sortino_config: + raise NameError(f"find optimize, but not found sortino config.") start = filter_weekend(sorted([day - relativedelta(days=1, **dict_remove(x, ('weight', 'name'))) for x in sortino_config])[0]) fund_navs = pd.DataFrame(navs.get_navs(fund_id=tuple(fund_ids), min_date=start, max_date=day)) fund_navs.sort_values('nav_date', inplace=True) @@ -17,31 +20,21 @@ def find_optimize(fund_ids, day): day_income = round(fund_navs.pct_change(), 4) sortino = pd.DataFrame() for item in sortino_config: - delta_kwargs = {**item} + delta_kwargs = item.copy() del delta_kwargs['weight'], delta_kwargs['name'] ratio = dict(sortino_ratio(day_income.truncate(before=(day - relativedelta(**delta_kwargs))))) sortino = pd.concat([sortino, pd.DataFrame([ratio], index=[item['name']])]) sortino = sortino.T - sortino['score'] = sortino.apply(lambda r:sum([x['weight'] * r[x['name']] for x in sortino_config]), axis=1) + sortino['score'] = sortino.apply(lambda r: sum([x['weight'] * r[x['name']] for x in sortino_config]), axis=1) sortino.sort_values('score', ascending=False, inplace=True) return day_income.columns[sortino.index[0]] -def get_base_fund_pool(): +def get_optimize_fund_pool(day): pass if __name__ == '__main__': - from datas import datum from datetime import date - funds = datum.get_fund_datums() - funds = pd.DataFrame(funds) - for (category, assetType), fund_group in funds.groupby(by=['category', 'assetType']): - if len(fund_group) > 1: - fund_ids = tuple(fund_group['id']) - fund_id = find_optimize(fund_ids, date.today()) - print(fund_ids, "->", fund_id) - else: - fund_ids = tuple(fund_group['id']) - print(fund_ids, "->", fund_ids[0]) + get_optimize_fund_pool(date.today()) diff --git a/utils/base.py b/utils/base.py index 8e5af9736a511c5054b7a6b1c4fdd0dbaf08c094..8a8b9abacd7867743c1380a3776a9157f3f0f534 100644 --- a/utils/base.py +++ b/utils/base.py @@ -1,6 +1,13 @@ import os +from functools import reduce -__all__ = ['get_project_path', 'deep_dict_update', 'dict_remove'] +__all__ = [ + 'get_project_path', + 'deep_dict_update', + 'dict_remove', + 'equals_ignore_case', + 'to_bool', +] def get_project_path(): @@ -36,3 +43,20 @@ def dict_remove(d: dict, k) -> dict: for key in tuple(k): del result[key] return result + + +def equals_ignore_case(a, b) -> bool: + return str(a).upper() == str(b).upper() if a and b else False + + +_TRUE_STR = ['true', 't', 'yes', 'y', 'on'] +_FALSE_STR = ['false', 'f', 'no', 'n', 'off'] + + +def to_bool(v) -> bool: + if isinstance(v, str): + if reduce(lambda a, b: a or b, [equals_ignore_case(v, x) for x in _TRUE_STR]): + return True + if reduce(lambda a, b: a or b, [equals_ignore_case(v, x) for x in _FALSE_STR]): + return False + return bool(v)