Commit 355af6c4 authored by wenwen.tang's avatar wenwen.tang 😕

init

parents
Pipeline #757 failed with stages
# FT Quant Rebalance API #
Version 1.3
## Quick Start for Developing
### Developing
1. 為你的再平衡模型取一個簡稱(vender),Ex: `sample` ( mpt, tv 已使用)
2. 複製 vender/sample_quant_api.py 到 vender/`sample`_quant_api.py
3. 打開 vender/`sample`_quant_api.py 實作 _rebalance(), 與 _dividend() (如果有需要)
_rebalance(), 與 _dividend() 開發請參考 vender/sample_quant_api.py
測試方式分述如下:
#### _Rebalance() 測試說明
1. Testing:
```
python reb_test.py vender mode portfiolio_file [recommand_file [ class_file [record_recomm_file [YYYYMMDD]]]]
ex:
python reb_test.py sample test test_data\reb.json test_data\portfolio.json test_data\class.json test_data\record_portfolio.json 20180401
```
2. 參數格式說明
1. vender = 再平衡模型取一個簡稱 (sample 會執行 sample code )
2. mode = test or pord. **`計量團隊`請固定用test**, prod 會取實際資料,且不需後續參數
3. portfiolio_file = 客戶持股比例檔(請參考quant_api.pdf: Q00003), Prod mode: 資料由中台系統傳入
4. recommand_file = 從推薦系統取得資料之格式,Prod mode: 資料由本RecommSystem提供
5. class_file = 資產分類檔。 **需由`計量團隊`提供**
6. record_recomm_file = 歷史推薦檔 ( for testing: default: "")
7. YYYYMMDD 模擬測試日期(default: Today)
##### About Test Case
為了測試各種情況,請參考 src\test_case_gen\Sample 下建立相關用測試用例所需 json檔
```
cd src\test_case_gen\sample
python gentestcase.py testcase_example.xlsx
```
步驟:
1. 建立目錄 src/test_case_gen/`vendor`
2. copy gentestcase.py, testcase_example.xlsx 到 src/test_case_gen/`vendor`
3. 編輯 test_case.xlsx (與相關人員確認test case正確性)
4. 執行 gentestcase.py (可自行調整程式,確保正確 parse test_case.xlsx)
系統UAT時,會根據測試用例,做產出所有測試結果,提供`計量團隊`與相關專案人員審視,是否與白皮書相符。
通過所有測試,方可上線
##### Troubleshooting
1. 為了測試Prod mode(RecommSystem 與 prod_api_agent.py )
```
SET PYTHONPATH=.
python lib\prod_api_agent.py
```
#### _Dividend() 測試說明
1. Testing:
```
python div_test.py vender portfiolio_file [YYYYMMDD]
ex:
python div_test.py sample test_data\test_case_01.json
or
python div_test.py sample test_data_div\test_case_01.json 20230301
```
2. 參數格式說明
1. vender = 再平衡模型取一個簡稱 (sample 會執行 sample code )
2. portfiolio_file = 客戶持股詳細資料 (請參考quant_api.pdf: Q00003)
3. YYYYMMDD 模擬測試日期(default: Today)
##### About Test Case
TBD
\ No newline at end of file
#from quant_api import Quant_api
import sys
import json
import importlib as im
def is_request_validated(data, ptype):
para = { "dividend" : ["vendor_id", "pts"]}
for parameter in para[ptype]:
if not parameter in data.keys():
return False
return True
def dividend(data):
default_return_msg = {}
if not is_request_validated(data, "dividend"):
default_return_msg.update({"errorCode":"2002", "errorMsg":"json format is incorrect."})
return json.dumps(default_return_msg)
result = quant_api.dividend(data["pts"])
result.update(default_return_msg)
#quant_api.show_debug_info()
return json.dumps(result,indent=2, sort_keys=True, ensure_ascii=False)
def usage():
print( '\n\tUsage: %s vender portfiolio_file [YYYYMMDD]' % sys.argv[0] )
print(
'''
vender = sample (for testing)
portfiolio_file = 客戶持股詳情檔
YYYYMMDD = 假設今日日期 Default: Today
''' )
sys.exit()
_NOW__ = ''
if __name__ == '__main__':
if len(sys.argv) >= 3 and len(sys.argv) <= 4:
vender = sys.argv[1]
data = json.load(open(sys.argv[2], encoding='utf-8'))
if len(sys.argv) == 4:
_NOW__=sys.argv[3]
else:
usage()
mod = im.import_module('vender.'+ vender + "_quant_api")
Quant_api = getattr(mod, "Quant_api")
quant_api = Quant_api("test", "", "", now=_NOW__)
print(dividend(data))
import urllib3
import json
from datetime import datetime
try:
from __main__ import _NOW__
except:
_NOW__ = ''
class Api_agent():
def __init__(self, portfolioDataPath="", assetClassPath=''):
if portfolioDataPath == "":
portfolioDataPath = 'test_data/portfolio.json'
if assetClassPath == "":
assetClassPath = 'test_data/class.json'
self.__DEBUG_INFO = ""
self.__classdata = json.load(open(assetClassPath, encoding='utf8'))
if type(portfolioDataPath) == str:
self.__portfolioData = json.load(open(portfolioDataPath, encoding='utf8'))
else:
self.__portfolioData = json.load(open(portfolioDataPath[0], encoding='utf8'))
if portfolioDataPath[1] != "":
self.__portfolioData_record = json.load(open(portfolioDataPath[1], encoding='utf8'))
self.__portfolios = {}
self.__rp_idPortfolios = {}
self.today = datetime.now().strftime("%Y%m%d")
if _NOW__ != '':
self.today = _NOW__
print(f"日期非採用系統時間 ,採用傳入時間{self.today}")
'''
def __get_request(self, query_string):
try:
rel = self.__TV_API.request("GET", query_string, headers = self.__HEADER_VALUES)
if rel == None:
pe
return None
data = ""
data = json.loads(rel.data.decode('utf-8'))
if len(data['data']) == 0:
return None
return data
except Exception as e:
self.__DEBUG_INFO = str(e)
return None
'''
def get_debug_info(self):
return self.__DEBUG_INFO
def get_latest_portfolio(self, portfolio_id, query_date):
query_date = query_date.replace('-','')
if query_date != '' and query_date != self.today:
self.__DEBUG_INFO = ('目前不提供指定日期取得推薦功能,除非是本日(%s),實際輸入日期為(%s)' %(self.today,query_date ))
#print("測試時忽略推薦日期檢查")
print("*** 測試時間不同,可能是 {vender}_quant_api.py 未加入 replace datetime.now()的程式碼")
return None
if portfolio_id in self.__portfolios.keys():
return self.__portfolios[portfolio_id]
try:
data = self.__portfolioData
if len(data['rps']) == 0:
return None
for d in data['rps'] :
if d['recomm_guid'] == portfolio_id:
if not 'data_Date' in d:
print(" Warning: `data_Date` is None, 但是不影響測試,只影響生產環境")
elif d['data_Date'] == self.today :
pass
else:
print(" Warning: `data_Date` is invalidated, 但是不影響測試,只影響生產環境")
self.__portfolios[portfolio_id] = d
return self.__portfolios[portfolio_id]
return None
except Exception as e:
self.__DEBUG_INFO = str(e)
return None
pass
api_query = "%s/franklin/prediction_data/%s/%s" % (self.__TV_API_URL, portfolio_id, query_date)
return self.__get_request(api_query)
def get_portfolio_by_rpid(self, rp_id):
rp_id = str(rp_id)
if rp_id in self.__rp_idPortfolios.keys():
return self.__rp_idPortfolios[rp_id]
try:
data = self.__portfolioData_record
if len(data['rps']) == 0:
return None
for d in data['rps'] :
d['rp_id'] = str(d['rp_id'])
if d['rp_id'] == rp_id:
self.__rp_idPortfolios[rp_id] = d
return self.__rp_idPortfolios[rp_id]
return None
except Exception as e:
self.__DEBUG_INFO = str(e)
return None
pass
api_query = "%s/franklin/prediction_data/%s/%s" % (self.__TV_API_URL, portfolio_id, query_date)
return self.__get_request(api_query)
def get_fund_metadata(self, fund_id):
return self.__classdata[fund_id]
pass
api_query = "%s/franklin/fundpool/%s" % (self.__TV_API_URL, fund_code)
data = self.__get_request(api_query)
if not data == None:
return data['data'][0]
return None
#from quant_api import Quant_api
import sys
import json
import importlib as im
def is_request_validated(data, ptype):
para = { "rebalance" : ["vendor_id", "pts"]}
for parameter in para[ptype]:
if not parameter in data.keys():
return False
return True
def rebalance(data):
default_return_msg = {}
if not is_request_validated(data, "rebalance"):
default_return_msg.update({"errorCode":"2002", "errorMsg":"json format is incorrect."})
return json.dumps(default_return_msg)
result = quant_api.rebalance(data["pts"])
result.update(default_return_msg)
#quant_api.show_debug_info()
return json.dumps(result,indent=2, sort_keys=True, ensure_ascii=False)
def usage():
print( '\n\tUsage: %s vender mode portfiolio_file [recommand_file [ class_file [recordportfolio [now]] ]]' % sys.argv[0] )
print(
'''
vender = sample (for testing)
mode = test / pord
portfiolio_file = 客戶持股比例檔
recommandile = 推薦檔 ( for testing: default: test_data/portfolio.json)
class_file = 大類資產檔 ( for testing: default: test_data/class.json)
recordportfolio = 歷史推薦檔 ( for testing: default: "")
now = 測試用系統時間 (預設為當日,format: yyyymmdd))
''' )
sys.exit()
_NOW__ = ''
if __name__ == '__main__':
portfolioDataPath = ""
assetClassPath = ""
recordportfolio_DataPath = ""
now = ""
if len(sys.argv) >= 4:
vender = sys.argv[1]
mode = sys.argv[2]
if mode == 'test' :
data = json.load(open(sys.argv[3], encoding='utf-8'))
if len(sys.argv) == 5 :
portfolioDataPath = sys.argv[4]
if len(sys.argv) == 6 :
portfolioDataPath = sys.argv[4]
assetClassPath = sys.argv[5]
if len(sys.argv) == 7:
portfolioDataPath = sys.argv[4]
assetClassPath = sys.argv[5]
recordportfolio_DataPath = sys.argv[6]
if len(sys.argv) == 8:
portfolioDataPath = sys.argv[4]
assetClassPath = sys.argv[5]
recordportfolio_DataPath = '' if sys.argv[6] == 'None' else sys.argv[6]
_NOW__=sys.argv[7]
elif mode == 'prod':
data = json.load(open(sys.argv[3], encoding='utf-8'))
else:
usage()
else:
usage()
'''
else:
vender = sys.argv[1]
mode = 'release'
data = json.load(open(sys.argv[2]))
'''
mod = im.import_module('vender.'+ vender + "_quant_api")
Quant_api = getattr(mod, "Quant_api")
if assetClassPath == "":
def print2(*args):
#print(*args)
return
from flask import Flask, current_app
app = Flask(__name__)
app.logger.error = print2
app.logger.warning = print2
app.logger.info = print2
app.logger.debug = print2
with app.app_context():
quant_api = Quant_api(mode, portfolioDataPath)
print(rebalance(data))
else:
#if recordportfolio_DataPath == "":
# quant_api = Quant_api(mode, portfolioDataPath, assetClassPath)
#else:
quant_api = Quant_api(mode, [portfolioDataPath, recordportfolio_DataPath], assetClassPath, now=_NOW__)
#quant_api = Quant_api(mode, [portfolioDataPath, recordportfolio_DataPath], assetClassPath)
print(rebalance(data))
click==6.7
DateTime==4.3
Flask==1.0.2
Flask-Cors==3.0.6
itsdangerous==0.24
Jinja2==2.10
joblib==0.14.0
MarkupSafe
numpy==1.17.2
pandas==0.25.1
python-dateutil==2.8.0
pytz==2018.7
scikit-learn==0.21.3
scipy==1.3.1
six==1.11.0
urllib3==1.23
Werkzeug==0.14.1
wfastcgi==3.0.0
xlrd==1.2.0
zope.interface==4.6.0
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import json
import sys
import pandas as pd
def main(xls_path):
xls = pd.ExcelFile(xls_path)
if len(xls.sheet_names) <2:
return
for sheet_name in xls.sheet_names[1:]:
df = xls.parse(sheet_name)
tc = sheet_name.replace(' ', '_')
portfolio = {}
portfolio["vendor_id"] = "test"
portfolio["sync"] = 1
obj = {}
portfolio["pts"]= [obj]
obj['recomm_id'] = str(df.loc[0][2])
obj['rp_id'] = df.loc[1][2]
obj['pt_id'] = df.loc[2][2]
obj['last_tx_date'] = df.loc[3][2]
obj['last_recomm_date'] = df.loc[4][2]
obj['create_date'] = df.loc[5][2]
if str(df.loc[6][2]) != 'nan': obj['last_check_date'] = df.loc[6][2]
if str(df.loc[7][2]) != 'nan': obj['last_reb_date'] = df.loc[7][2]
obj['note'] = str(df.loc[8][2])
fas = []
obj['fas'] = fas
#for i in range(10, len(df.loc[1:])):
for i in range(10, 20):
try:
#print(str(df.loc[i][2]))
if str(df.loc[i][2]) == 'nan':
print('len(portfolios) = ' + str(i))
break
fas.append( {
'fund_id': str(df.loc[i][2]),
'weight': str(df.loc[i][3])
})
except:
print('len(portfolios) = ' + str(i))
break
#print(json.dumps(portfolio))
outf = tc+'_portfolio.json'
print ( "create file: " + outf)
with open(outf, 'w') as outfile:
json.dump(portfolio, outfile)
recomm = {}
rp = {}
recomm['rps'] = [rp]
rp['recomm_guid'] = str(df.loc[0][9])
rp['rp_id'] = df.loc[1][9]
rp['note'] = str(df.loc[8][2])
fws = []
rp['fws'] = fws
#for i in range(10, len(df.loc[1:])):
for i in range(10, 20):
try:
#print(df.loc[i][8])
if str(df.loc[i][8]) == 'nan':
print('len(recomm) = ' + str(i))
break
fws.append( {
'fund_id': str(df.loc[i][8]),
'weight': str(df.loc[i][9])
})
except:
print('len(recomm) = ' + str(i))
break
outf = tc+'_recomm.json'
print ( "create file: " + outf)
with open(outf, 'w') as outfile:
json.dump(recomm, outfile)
def usage():
print("USAGE:\n\t", sys.argv[0], " XLS_PATH" )
if __name__ == "__main__":
if len(sys.argv) == 2 :
xls_path = sys.argv[1]
main(xls_path)
else:
usage()
{
"0352": "Equity"
, "0358": "Equity"
, "0361": "Equity"
, "0375": "Equity"
, "0419": "Equity"
, "0433": "Equity"
, "0533": "Equity"
, "0575": "Equity"
, "0587": "Equity"
, "0592": "Equity"
, "0776": "Equity"
, "0777": "Equity"
, "0778": "Equity"
, "0779": "Equity"
, "0781": "Equity"
, "0782": "Equity"
, "0785": "Equity"
, "0786": "Equity"
, "0790": "Equity"
, "0794": "Equity"
, "0796": "Equity"
, "0797": "Equity"
, "0799": "Equity"
, "0805": "Equity"
, "0822": "Equity"
, "0824": "Equity"
, "0828": "Equity"
, "0830": "Equity"
, "0836": "Equity"
, "1000": "Equity"
, "1009": "Equity"
, "101": "Equity"
, "1019": "Equity"
, "102": "Equity"
, "103": "Equity"
, "104": "Equity"
, "106": "Equity"
, "107": "Equity"
, "108": "Equity"
, "150": "Equity"
, "158": "Equity"
, "0152": "Equity"
, "0384": "Bond"
, "0426": "Bond"
, "0549": "Bond"
, "0651": "Bond"
, "0750": "Bond"
, "0751": "Bond"
, "1105": "Bond"
, "1174": "Bond"
, "1715": "Bond"
, "132": "AntiInflation"
, "0788": "Cash"
, "1641": "Cash"
}
{
"errorMSG": true,
"rps": [
{
"recomm_guid": "12345678-1234-ABCD-ABCD-123456789012",
"rp_id": 888,
"fws": [
{
"fund_id": "0575",
"weight": "35"
},
{
"fund_id": "0352",
"weight": "6"
},
{
"fund_id": "0384",
"weight": "19"
},
{
"fund_id": "1105",
"weight": "5"
},
{
"fund_id": "103",
"weight": "35"
}
]
,"note" : "{\"last_reg_reb\":\"20190801\",\"last_irreg_reb\":\"20190825\",\"risk_control\": [[0, 1, 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],[0, 1, 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]]}"
},
{
"recomm_guid": "87654321-4321-DCBA-DCBA-210987654321",
"rp_id": "999",
"fws": [
{
"fund_id": "0575",
"weight": "35"
},
{
"fund_id": "0352",
"weight": "6"
},
{
"fund_id": "0384",
"weight": "19"
},
{
"fund_id": "1105",
"weight": "5"
},
{
"fund_id": "103",
"weight": "35"
}
]
,"note" : "{\"last_reg_reb\":\"20190801\",\"last_irreg_reb\":\"20190825\",\"risk_control\": [[0, 1, 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],[0, 1, 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]]}"
},
{
"recomm_guid": "87654321-4321-DCBA-DCBA-210987654321",
"rp_id": "310",
"fws": [
{
"fund_id": "0575",
"weight": "30"
},
{
"fund_id": "0352",
"weight": "11"
},
{
"fund_id": "0384",
"weight": "19"
},
{
"fund_id": "1105",
"weight": "5"
},
{
"fund_id": "103",
"weight": "35"
}
]
,"note" : "{\"last_reg_reb\":\"20190801\",\"last_irreg_reb\":\"20190825\",\"risk_control\": [[0, 1, 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19],[0, 1, 2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19]]}"
}
]
}
{
"pts": [{
"create_date": "2018-02-20",
"fas": [{
"fund_id": "0352",
"weight": "45"
}, {
"fund_id": "0788",
"weight": "0"
}, {
"fund_id": "0384",
"weight": "6"
}, {
"fund_id": "1105",
"weight": "5"
}, {
"fund_id": "1715",
"weight": "9"
}
],
"last_recomm_date": "2018-03-20",
"last_tx_date": "2018-03-27",
"last_check_date": "2018-03-27",
"pt_id": "0",
"recomm_id": "87654321-4321-DCBA-DCBA-210987654321",
"rp_id": 310
}, {
"create_date": "2018-02-20",
"fas": [{
"fund_id": "0352",
"weight": "45"
}, {
"fund_id": "0788",
"weight": "0"
}, {
"fund_id": "0384",
"weight": "6"
}, {
"fund_id": "1105",
"weight": "5"
}, {
"fund_id": "1715",
"weight": "9"
}
],
"last_recomm_date": "2018-03-20",
"last_tx_date": "2018-03-27",
"last_check_date": "2018-03-27",
"pt_id": "1",
"recomm_id": "87654321-4321-DCBA-DCBA-210987654321",
"rp_id": 310
}, {
"create_date": "2018-02-20",
"fas": [{
"fund_id": "0352",
"weight": "5"
}, {
"fund_id": "0788",
"weight": "35"
}, {
"fund_id": "0384",
"weight": "16"
}, {
"fund_id": "1105",
"weight": "25"
}, {
"fund_id": "1715",
"weight": "19"
}
],
"last_recomm_date": "2018-03-20",
"last_tx_date": "2018-03-27",
"last_check_date": "2018-03-27",
"pt_id": "9999999",
"recomm_id": "87654321-4321-DCBA-DCBA-210987654321",
"rp_id": 310
}
],
"vendor_id": "TV",
"sync": 1
}
{
"errorMSG": true,
"rps": [
{
"recomm_guid": "D02C7E12-0A50-46BA-840F-F7C47BD8FCD5",
"rp_id": "310",
"fws": [
{
"fund_id": "0779",
"weight": "30"
},
{
"fund_id": "1049",
"weight": "15"
},
{
"fund_id": "0810",
"weight": "20"
},
{
"fund_id": "1435",
"weight": "5"
},
{
"fund_id": "1174",
"weight": "30"
}
],
"note": "{'last_reg_reb': '20190501', 'last_irreg_reb': '20190420', 'risk_control': [{'2019-06-27': {('成熟亚洲股票', 'ACDPAC Index'): 7.1, ('大宗商品', 'BCOM Index'): 3.9, ('全球股票', 'BWORLD Index'): 5.5, ('中国债券', 'CSIH1001 Index'): 3.6, ('中国利率债', 'CSIH1006 Index'): 4.1, ('中国信用债', 'CSIH1073 Index'): 3.7, ('新兴市场债券', 'EMUSTRUU Index'): 6.6, ('成熟亚洲债券', 'H05411US Index'): 4.0, ('北美投资级及以上债券', 'I17660US Index'): 7.9, ('大中华债券', 'I29380US Index'): 4.7, ('新兴亚洲债券', 'LAPCTRUU Index'): 5.3, ('全球债券', 'LEGATRUU Index'): 4.3, ('北美高收益债券', 'LF98TRUU Index'): 4.0, ('货币市场', 'LIBMB01M Index'): 2.8, ('欧洲高收益债券', 'LP01TREU Index'): 5.1, ('新兴拉美股票', 'MSEUEGFL Index'): 5.3, ('欧非中东股票', 'MXEE Index'): 4.8, ('新兴市场股票', 'MXEF Index'): 4.3, ('欧洲股票', 'MXEU Index'): 3.7, ('大中华股票', 'MXGD Index'): 5.2, ('新兴亚洲股票', 'MXMS Index'): 4.1, ('房地产', 'MXWO0RE Index'): 5.3, ('欧洲投资级及以上债券', 'QW1A Index'): 4.0, ('A股小盘', 'SH000905 Index'): 4.2, ('A股被动型', 'SHSZ300 Index'): 3.1, ('能源', 'SPGSCITR Index'): 4.0, ('北美股票', 'SPX Index'): 4.9, ('A股大盘', 'SSE50 Index'): 4.6, ('全球另类策略', 'USGG3YR Index'): 5.6, ('黄金', 'XAU Curncy'): 4.4}}, {'2019-06-26': {('成熟亚洲股票', 'ACDPAC Index'): 7.2, ('大宗商品', 'BCOM Index'): 3.9, ('全球股票', 'BWORLD Index'): 5.5, ('中国债券', 'CSIH1001 Index'): 3.3, ('中国利率债', 'CSIH1006 Index'): 4.2, ('中国信用债', 'CSIH1073 Index'): 3.5, ('新兴市场债券', 'EMUSTRUU Index'): 6.6, ('成熟亚洲债券', 'H05411US Index'): 3.6, ('北美投资级及以上债券', 'I17660US Index'): 7.9, ('大中华债券', 'I29380US Index'): 5.0, ('新兴亚洲债券', 'LAPCTRUU Index'): 5.3, ('全球债券', 'LEGATRUU Index'): 4.3, ('北美高收益债券', 'LF98TRUU Index'): 3.7, ('货币市场', 'LIBMB01M Index'): 2.8, ('欧洲高收益债券', 'LP01TREU Index'): 5.3, ('新兴拉美股票', 'MSEUEGFL Index'): 5.4, ('欧非中东股票', 'MXEE Index'): 4.7, ('新兴市场股票', 'MXEF Index'): 4.4, ('欧洲股票', 'MXEU Index'): 3.7, ('大中华股票', 'MXGD Index'): 5.3, ('新兴亚洲股票', 'MXMS Index'): 4.2, ('房地产', 'MXWO0RE Index'): 5.1, ('欧洲投资级及以上债券', 'QW1A Index'): 4.0, ('A股小盘', 'SH000905 Index'): 4.2, ('A股被动型', 'SHSZ300 Index'): 3.0, ('能源', 'SPGSCITR Index'): 4.3, ('北美股票', 'SPX Index'): 4.9, ('A股大盘', 'SSE50 Index'): 4.6, ('全球另类策略', 'USGG3YR Index'): 5.5, ('黄金', 'XAU Curncy'): 4.3}}]}"
}
]
}
\ No newline at end of file
{ "vendor_id": "Dividen_TEST",
"pts": [{
"pt_id": 123,
"last_pd_date": "2023-07-15",
"last_check_date": "2023-07-15",
"create_date": "2017-11-23",
"total_in": 30000.0,
"div_rate": 0.12,
"div_month": [3, 6, 9, 12],
"asset": [{
"fund_id": "1234",
"nav": 1.11,
"rate": 30.11,
"rr": 4,
"share": "1.111"
}, {
"fund_id": "5678",
"nav": 2.22,
"rate": 30.22,
"rr": 4,
"share": "2.222"
}
]
}, {
"pt_id": 1,
"create_date": "2017-11-23",
"total_in": 30000.0,
"asset": [{
"fund_id": "4321",
"nav": 3.33,
"rate": 30.33,
"rr": 4,
"share": "3.333"
}, {
"fund_id": "8765",
"nav": 4.44,
"rate": 30.44,
"rr": 4,
"share": "11.444"
}
]
}
]
}
import hashlib
import json
import importlib as im
from datetime import datetime, timedelta
class base_Quant_api():
def __init__(self, type, portfolioDataPath="", assetClassPath="", now=""):
mod = im.import_module('lib.' + type + "_api_agent")
api_agent = getattr(mod, "Api_agent")
self.__default_msg = {'errorCode': '0', 'errorMsg': ''}
self.__pt_keys = ["pt_id", "recomm_id", "rp_id", "last_tx_date", "last_recomm_date", "create_date", "fas", "last_check_date"]
self.__fas_keys = ["fund_id", "weight"]
self.__api = api_agent(portfolioDataPath, assetClassPath)
self.__portfolios = {}
self.__fund_metadata = {}
self.__debug_info = {}
self.nowstr = now
def now(self):
if self.nowstr == "":
return datetime.now()
else:
return datetime(int(self.nowstr[:4]), int(self.nowstr[4:6]), int(self.nowstr[6:8]))
def __debug(self, msg):
self.__debug_info[datetime.now()] = msg
return True
def __gen_rcv_no(self, recomm_id, numbers):
return "%s-%s-%s" % (datetime.now().strftime("%Y%m%d%H%M%S"), recomm_id, numbers)
def __verify_pt_data(self, data):
para = ["fas", "last_recomm_date", "last_tx_date", "pt_id", "recomm_id", "rp_id"]
for parameter in para:
if not parameter in data.keys():
return False
if len(data["fas"]) == 0:
return False
return True
def __verify_pt_data_dividend(self, data):
para = ["pt_id", "total_in", "asset"]
for parameter in para:
if not parameter in data.keys():
return False
if len(data["asset"]) == 0:
return False
return True
def get_latest_portfolio(self, recomm_id, query_date = ''):
#self.__debug("Getting portfolio data: %s" % query_key)
portfolio_data = self.__api.get_latest_portfolio(recomm_id, query_date)
#self.__debug("%s: %s" % (portfolio_id, json.dumps(portfolio_data)))
if portfolio_data != None:
return portfolio_data
return None
def get_portfolio_by_rpid(self, rp_id):
#self.__debug("Getting portfolio data: %s" % query_key)
portfolio_data = self.__api.get_portfolio_by_rpid(rp_id)
#self.__debug("%s: %s" % (portfolio_id, json.dumps(portfolio_data)))
if portfolio_data != None:
return portfolio_data
return None
def get_fund_asset_class(self, fund_id):
self.__debug("Getting fund asset class: %s" % fund_id)
data = self.__api.get_fund_metadata(fund_id)
if data != None:
return data
return None
def _rebalance(self, pt):
pass
def _dividend(self, pt):
pass
def show_debug_info(self):
if not bool(self.__debug_info):
return True
for exec_time in sorted(self.__debug_info):
print("[%s] %s" % (exec_time.strftime("%Y-%m-%d %H:%M:%S.%f"), self.__debug_info[exec_time]))
self.__debug_info = {}
return True
def rebalance(self, pts):
self.__default_msg = {'errorCode': '0', 'errorMsg': 'Success', 'rebs':[]}
pt_len = len(pts)
if pt_len == 0:
return self.__default_msg
self.__default_msg['rcv_no'] = self.__gen_rcv_no(pts[0]["recomm_id"], pt_len) # it need to be confirmed.
for pt in pts:
if not self.__verify_pt_data(pt):
self.__debug("Failed to verify pt data")
self.__default_msg['errorCode'] ="1001"
self.__default_msg['errorMsg'] = "Failed to verify pts data."
self.__default_msg['rebs']= []
break
else :
#self._rebalance(pt)
res = self._rebalance(pt)
#(rp_id, reason) = self._rebalance(pt)
if res == 0:
rp_id = '0'
reason = '符合所有條件,無需REB'
else:
(rp_id, reason) = res
self.__default_msg['rebs'].append({"pt_id":pt["pt_id"], "rp_id":rp_id, "reason":reason })
#self.__default_msg['rebs'].append({"pt_id":pt["pt_id"], "rp_id": self._rebalance(pt)})
return self.__default_msg
def dividend(self, pts):
self.__default_msg = {'errorCode': '0', 'errorMsg': 'Success', 'dividends':[]}
pt_len = len(pts)
if pt_len == 0:
return self.__default_msg
for pt in pts:
if not self.__verify_pt_data_dividend(pt):
self.__debug("Failed to verify pt data")
self.__default_msg['errorCode'] ="1001"
self.__default_msg['errorMsg'] = "Failed to verify pts data."
self.__default_msg['rebs']= []
break
else :
res = self._dividend(pt)
self.__default_msg['dividends'].append({"pt_id":pt["pt_id"], "redeem":res })
#self.__default_msg['rebs'].append({"pt_id":pt["pt_id"], "rp_id": self._rebalance(pt)})
return self.__default_msg
from vender.quant_api_base import base_Quant_api
import json
class Quant_api(base_Quant_api):
def _rebalance(self, pt):
# 此為範例
# print out pt data
print("------------ 此為 客戶portfolio ---------")
print(json.dumps(pt, indent=2))
print("------------ 此為 最新的投組推薦 ---------")
model_portfolio = self.get_latest_portfolio(pt['recomm_id'])
print(json.dumps(model_portfolio, indent=2))
print("------------ 利用 rp_id 舊的投組推薦 ---------")
print("rp_id = %s" % pt['rp_id'])
model_portfolio2 = self.get_portfolio_by_rpid(pt['rp_id'])
print(json.dumps(model_portfolio2, indent=2))
if model_portfolio2['note'] != "":
try:
print("note JSON Object:")
print(json.dumps(json.loads(model_portfolio2['note']), indent=2))
except:
None
print("------------ 此為基金的分類(非必要)-------")
for fund in model_portfolio['fws']:
fund_id = fund['fund_id']
fundclass = self.get_fund_asset_class(fund_id)
print(fund_id + ": " ,fundclass)
print("------------ 基金的分類(end)-------")
## ------- 返回結果 -------
# 返回 model_portfolio 的 rp_id 表示需要 rebalance
# 若是不需要 rebalance 返回 0
return (model_portfolio['rp_id'], "判斷的理由")
def _dividend(self, pt):
# 此為範例
# print out pt data
print("**** dividend Called **** ")
print("--- 此為 客戶portfolio (input data)---")
print(json.dumps(pt, indent=2))
'''
Your core here...
注意事項:
因為排程是每日執行,若是要每月只執行一次,請以last_check_date判斷。
為方便測試,若需取今日日期,請使用 warp function self.now()
'''
print (f"--- 假設今日日期 : {self.now()} " )
## ------- 返回結果 For example-------
'''
返回一個 list
每個 element 只少包含 fund_id, share,
允許包含有其他自訂欄位
'''
print("--- 此為 回傳結果 ---")
asset = pt['asset']
return (
{"fund_id" : f"{asset[0]['fund_id']}", "share" : f"{asset[0]['share']}" },
{"fund_id" : f"{asset[1]['fund_id']}", "share" : f"{asset[1]['share']}" },
)
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