def portfolio_composition(): ''' Returns a list of aggregations (e.g. geography, sector) and their portfolio value per member currently in dollar value terms. ''' if request.method == 'POST': req = request.get_json(silent=True) portfolio = investmentportfolio.Get_Portfolio_Holdings( req['portfolio'], False)['holdings'] # client portfolio portfolio = [item['holdings'] for item in portfolio ] #since we loaded the data in chunks originally portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' aggregations = req["aggregations"] # aggregations to compute NAV = sum( float(item['quantity']) * float(item['PRICE']) for item in portfolio) universe = get_expanded_universe(portfolio) exposures = {"NAV": NAV} for a in aggregations: values = {} #get unique entries for the given aggregation (keep an eye out for python2 --> 3 quirks) unique_a = {item[a]: item[a] for item in universe}.values() for u in unique_a: values[u] = sum( [item['portfolio_value'] for item in universe if item[a] == u]) exposures[a] = values return Response(json.dumps(exposures), mimetype='application/json')
def parse_universe(): ''' Extracts the various types of constraints from the instrument universe portfolio holdings data. ''' constraints = { 'hard_constraints': [], 'esg_constraints': [], 'allocation_constraints': [] } #Get instrument universe - look at first holding tags (since they're all the same) iu = investmentportfolio.Get_Portfolio_Holdings('instrument_universe')['holdings'][0]['holdings']['holdings'] #iterate through headers to figure out what types of constraints exist within the data. for key,value in iu[0].items(): try: c_type,c_desc = key.split('_') #hard constraints start with 'has_' if c_type == 'has': constraints['hard_constraints'].append({'type':key,'description':'Any company with significant business operations that deal with ' + str(c_desc) + '.'}) #esg constraints start with 'esg_' if c_type == 'esg': constraints['esg_constraints'].append({'type':key,'description':'ESG ranking for a given company\'s ' + str(c_desc) + ' score.'}) except: #allocation constraints have no '_'. Here we need to serve up all possible values from the data to populate the UI. if key not in ['instrumentId','CUSIP','asset','Price']: enumeration = list(set([row[key] for row in iu])) constraints['allocation_constraints'].append({'values':enumeration,'type':key,'description':'The ' + str(key) + ' of the security.'}) return constraints
def search(portfolio,security): ''' Returns details around the true presence of a given security [by ticker for now] in a portfolio. ''' if request.method == 'POST': req = request.get_json(silent=True) portfolio = req["portfolio"] security = req["security"] # security to check portfolio = investmentportfolio.Get_Portfolio_Holdings(portfolio,False)['holdings'] # client portfolio portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' NAV = sum(float(item['quantity'])*float(item['PRICE']) for item in portfolio) universe = get_expanded_universe(portfolio) exposures = {"NAV":NAV} #get unique entries for the given aggregation (keep an eye out for python3 quirks) securities = [item for item in universe if item['TICKER']==security] #get esg data from the first instance (since they theoretically should all be the same for the same security) esg_data = {} price = sum(float(item['PRICE']) for item in securities) for key,value in securities[0].items(): if 'esg_' in key: esg_data[key] = value exposures = { "NAV":NAV, "security":security, "price": price, "direct":sum([item['portfolio_value'] for item in securities if item['user_portfolio'] == True]), "indirect":sum([item['portfolio_value'] for item in securities if item['user_portfolio'] == False]), "esg":esg_data } return Response(json.dumps(exposures), mimetype='application/json')
def get_expanded_universe(portfolio): #portfolio object as input universe = portfolio for p in portfolio: p.update({'portfolio_value':float(p['quantity'])*float(p['PRICE']),'user_portfolio':True}) look_throughs = [item["TICKER"] for item in portfolio if item["HAS_LOOKTHROUGH"] == 'TRUE'] for l in look_throughs: #Get fund's NAV from user portfolio (that's where the data lives) and our exposure to that fund (in $) fund_NAV = [float(item['FUND_NAV']) for item in portfolio if item['TICKER'] == l][0] exposure_to_fund = [float(item['quantity']) for item in portfolio if item['TICKER'] == l][0] #Get fund's individual holdings fund = investmentportfolio.Get_Portfolio_Holdings(l,False)['holdings'] fund = [item['holdings'] for item in fund] #since we loaded the data in chunks originally fund = [item for sublist in fund for item in sublist] #flatten the list #calculate effective dollar exposure to each fund based on market value in parent portfolio for f in fund: #errors in csv file formats can cause issues here try: f.update({'portfolio_value':((float(f['quantity']) *float(f['PRICE']))/ fund_NAV) * exposure_to_fund,'user_portfolio':False}) except: print('look at ' + str(f['name'])) universe += fund return universe
def compute_unit_tests(): ''' Calculates analytics for a portfolio. Breaks into 500 instrument chunks to comply with container constraints. ''' if request.method == 'POST': portfolios = [] data = json.loads(request.data) portfolios.append(data["portfolio"]) #Stopwatch start_time = datetime.datetime.now() if data: analytics = data["analytics"] else: analytics = ['THEO/Price','THEO/Value'] results = [] for p in portfolios: portfolio_start = datetime.datetime.now() holdings = investmentportfolio.Get_Portfolio_Holdings(p,False)['holdings'] #Since the payload is too large, odds are there are 500-instrument chunks added to the portfolio. for ph in range(0,len(holdings)): instruments = [row['instrumentId'] for row in holdings[ph]['holdings']] print("Processing " + str(p) + " portfolio segment #"+str(ph) +".") #send 500 IDs at a time to Instrument Analytics Service: #for i in instruments... for i in range(0,len(instruments),500): ids = instruments[i:i+500] ia = instrumentanalytics.Compute_InstrumentAnalytics(ids,analytics) #for j in results... if 'error' not in ia: for j in ia: r = { "portfolio":p, "id":j['instrument']} for a in analytics: r[a] = j['values'][0][a] r["date"] = j['values'][0]['date'] h = [row for row in holdings[0]['holdings'] if j['instrument']==row['instrumentId']][0] for key,value in h.items(): if key not in ['instrumentId']: r[key] = value results.append(r) #Debug if i+500<len(instruments): l = i+500 else: l = len(instruments) print("Processed securities " + str(i) + " through " + str(l) + ". Time elapsed on this portfolio: " + str(datetime.datetime.now() - portfolio_start)) print("Unit testing completed. Total time elapsed: " + str(datetime.datetime.now() - start_time)) return Response(json.dumps(results), mimetype='application/json')
def get_universe(portfolio): #portfolio object as input universe = portfolio look_throughs = [item["TICKER"] for item in portfolio if item["HAS_LOOKTHROUGH"] == 'TRUE'] for l in look_throughs: #Get fund's individual holdings fund = investmentportfolio.Get_Portfolio_Holdings(l,False)['holdings'] fund = [item['holdings'] for item in fund] #since we loaded the data in chunks originally fund = [item for sublist in fund for item in sublist] #flatten the list universe += [item for item in fund if item['TICKER'] != ''] return universe
def get_unit_test_delete(): ''' Deletes all portfolios and respective holdings that are of type 'unit test' ''' portfolios = investmentportfolio.Get_Portfolios_by_Selector('type','unit test portfolio')['portfolios'] print(portfolios) for p in portfolios: holdings = investmentportfolio.Get_Portfolio_Holdings(p['name'],False) # delete all holdings for h in holdings['holdings']: timestamp = h['timestamp'] rev = h['_rev'] investmentportfolio.Delete_Portfolio_Holdings(p['name'],timestamp,rev) investmentportfolio.Delete_Portfolio(p['name'],p['timestamp'],p['_rev']) return "Portfolios deleted successfully."
def compute_unit_tests(): ''' Analyzes a video for mentions (image or audio) of companies held in your portfolio. ''' if request.method == 'POST': portfolios = [] data = json.loads(request.data) portfolios.append(data["portfolio"]) #Stopwatch start_time = datetime.datetime.now() tickers = [] for p in portfolios: portfolio_start = datetime.datetime.now() holdings = investmentportfolio.Get_Portfolio_Holdings( p, False)['holdings'] #Since the payload is too large, odds are there are 500-instrument chunks added to the portfolio. for ph in range(0, len(holdings)): #names = [row['instrumentId'] for row in holdings[ph]['holdings']] print(ph) if len(holdings[ph]['holdings']) > 0: if 'Ticker' in holdings[ph]['holdings'][0]: tickers = [ row['Ticker'] for row in holdings[ph]['holdings'] ] print(tickers) print("Total time elapsed: " + str(datetime.datetime.now() - start_time)) #initiate processor command = "python processor/main.py" metadataFile = "clips/clip.metadata" if os.system(command) == 0: print('yo yo') with open(metadataFile, 'rb') as fp: metadata = pickle.load(fp) return json.dumps({ 'success': 'true', 'metadata': metadata }), 200, { 'ContentType': 'application/json' } else: print("ERROR in processor") return json.dumps({'success': 'fail fail ash'}), 500, { 'ContentType': 'application/json' }
def search_universe(portfolio): ''' Returns the total list of securities touched by an investment portfolio (e.g. including look-throughs). ''' if request.method == 'POST': req = request.get_json(silent=True) portfolio = req['portfolio'] portfolio = investmentportfolio.Get_Portfolio_Holdings(portfolio,False)['holdings'] # client portfolio portfolio = [item['holdings'] for item in portfolio] #since we loaded the data in chunks originally portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' universe = get_universe(portfolio) universe = [item['name'] + ' (' + item['TICKER'] + ')' for item in universe] return Response(json.dumps(universe), mimetype='application/json')
def reset_app(): ''' Deletes all portfolios and respective holdings that are of type 'user_portfolio', 'benchmark', and 'root' (instrument universe) ''' portfolios = investmentportfolio.Get_Portfolios_by_Selector('type','user_portfolio')['portfolios'] portfolios += investmentportfolio.Get_Portfolios_by_Selector('type','benchmark')['portfolios'] portfolios += investmentportfolio.Get_Portfolios_by_Selector('type','root')['portfolios'] for p in portfolios: holdings = investmentportfolio.Get_Portfolio_Holdings(p['name'],False) # delete all holdings for h in holdings['holdings']: timestamp = h['timestamp'] rev = h['_rev'] investmentportfolio.Delete_Portfolio_Holdings(p['name'],timestamp,rev) investmentportfolio.Delete_Portfolio(p['name'],p['timestamp'],p['_rev']) return "Portfolios deleted successfully."
def portfolio_analyze(portfolio): ''' Returns data compatible with the Portfolio.Analyze() v1.0 front-end GUI ''' if request.method == 'POST': req = request.get_json(silent=True) portfolio = req['portfolio'] portfolio_name = portfolio #persist name portfolio = investmentportfolio.Get_Portfolio_Holdings( portfolio, False)['holdings'] # client portfolio portfolio = [item['holdings'] for item in portfolio ] #since we loaded the data in chunks originally portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' aggregations = [ "geography", "Asset Class", "sector", "has_Tobacco", "has_Alcohol", "has_Gambling", "has_Military", "has_Fossil Fuels", "esg_Controversy", "esg_Environmental", "esg_Governance", "esg_Social", "esg_Sustainability" ] NAV = sum( float(item['quantity']) * float(item['PRICE']) for item in portfolio) response = { "NAV": NAV, 'sin': {}, 'esg': { portfolio_name: {} }, 'search': [], # search universe 'portfolio': [{ 'name': item['name'], 'value ($USD)': (float(item['quantity']) * float(item['PRICE'])), 'Portfolio Contribution (%)': ((float(item['quantity']) * float(item['PRICE'])) / NAV) * 100, 'Industry Sector': item['sector'], 'Asset Class': item['Asset Class'], 'Geography': item['geography'] } for item in portfolio], 'composition': {} } universe = get_expanded_universe(portfolio, NAV) response['search'] = list( set([item['name'] + ' (' + item['TICKER'] + ')' for item in universe])) #hard-coded benchmarks for now, as it's possible a user would want to make benchmark choices static... #benchmarks = ['IVV','HYG','LQD'] benchmarks = ['IVV'] for b in benchmarks: response['esg'][b] = {} #Calculate data for response for a in aggregations: #sin stocks - just need true if 'has_' in a: #we omit the parent funds in the portfolio (has_lookthrough=true) to avoid double counting the exposure response['sin'][a] = sum([ item['portfolio_value'] for item in universe if item['HAS_LOOKTHROUGH'] == 'FALSE' if item[a] == 'TRUE' ]) #esg elif 'esg_' in a: #compute average ESG for the portfolio (and benchmarks!) response['esg'][portfolio_name][a] = sum([ (item['portfolio_value'] / NAV) * float(item[a]) for item in universe if item['HAS_LOOKTHROUGH'] == 'FALSE' ]) #regular aggregations else: values = {} #get unique entries for the given aggregation (keep an eye out for python3 quirks) unique_a = {item[a]: item[a] for item in universe}.values() for u in unique_a: values[u] = sum([ item['portfolio_value'] for item in universe if item['HAS_LOOKTHROUGH'] == 'FALSE' if item[a] == u ]) response['composition'][a] = values #get ESG data for benchmarks for b in benchmarks: portfolio = investmentportfolio.Get_Portfolio_Holdings( b, False)['holdings'] portfolio = [item['holdings'] for item in portfolio ] #since we loaded the data in chunks originally portfolio = [item for sublist in portfolio for item in sublist] #flatten the list' b_NAV = sum( float(item['quantity']) * float(item['PRICE']) for item in portfolio) b_universe = get_expanded_universe(portfolio, b_NAV) for a in aggregations: if 'esg_' in a: #compute average ESG for the portfolio (and benchmarks!) response['esg'][b][a] = sum([ (item['portfolio_value'] / b_NAV) * float(item[a]) for item in b_universe if item['HAS_LOOKTHROUGH'] == 'FALSE' ]) #create world investment json for the D3 element create_world_json(response['composition']["geography"]) return Response(json.dumps(response), mimetype='application/json')
def api_analyze(): """ Processes the user inputs to generate the scenario csv and run simulated instrument analytics on each holding in the portfolio """ output = {} #retrieve the json from the ajax call json_file = '' if request.method == 'POST': json_file = request.json print("post request") #if json_file successfully posted.. if json_file != '': # check all required arguments are present: if not all(arg in json_file for arg in ["portfolio", "riskfactor", "shockmag"]): print("Missing arguments in post request") return json.dumps({ "status": "Error", "messages": "Missing arguments" }), 422 portfolio = json_file["portfolio"] riskfactor = json_file["riskfactor"] shockmag = json_file["shockmag"] print("retreived data: " + str(portfolio) + " | " + str(riskfactor) + " | " + str(shockmag)) #run Predictive Market Scenario service PMS_status = predictivemarketscenario.Generate_Scenario( riskfactor, shockmag) #if error in the call, return with error if PMS_status != 200: print( "Unable to create csv from Predictive Market Scenario service") return json.dumps({ 'error': str(PMS_status) + " Unable to create csv from Predictive Market Scenario service" }) print("CREATED CSV") #get holdings data holdings_data = investmentportfolio.Get_Portfolio_Holdings(portfolio) #go through each holding in the portfolio asset_output = [] for holding in holdings_data["holdings"][-1]["holdings"]: #call the simulatedinstrumentanalytics module data = simulatedinstrumentanalytics.Compute_Simulated_Analytics( instrument_id=holding["instrumentId"]) #if returned as json would mean error, assign N/A, else assing the values from the list of objects if (type(data) is dict): value1 = "N/A" value2 = "N/A" else: value1 = data[0]["values"][0]["THEO/Price"] value2 = data[1]["values"][0]["THEO/Price"] #create obj with the output values asset = holding["asset"] instrumentId = holding["instrumentId"] quantity = holding["quantity"] companyName = holding["companyName"] obj = { 'Asset': asset, 'InstrumentId': instrumentId, 'Quantity': quantity, 'CompanyName': companyName, 'BaseVal': value1, 'NewVal': value2 } #print (obj) asset_output.append(obj) #get the market_conditions as list if os.path.exists("output_PMS.csv"): market_conditions = get_market_conditions("output_PMS.csv") #create the output json output = { "holdingsInfo": asset_output, "marketConditions": market_conditions } return (json.dumps(output))
def compute_unit_tests(): global bloomUsProcess, bloomGlobProcess, skyProcess, cnbcAfricaProcess, bloomUsNews, bloomGlobNews, skyNews, cnbcAfricaNews ''' Calculates analytics for a portfolio. Breaks into 500 instrument chunks to comply with container constraints. ''' print('Ash - Inside computing') if (bloomUsProcess != None): bloomUsProcess.terminate() bloomUsProcess = None if (bloomGlobProcess != None): bloomGlobProcess.terminate() bloomGlobProcess = None if (skyProcess != None): skyProcess.terminate() skyProcess = None if (cnbcAfricaProcess != None): cnbcAfricaProcess.terminate() cnbcAfricaProcess = None portfolios = [] feeds = None if request.method == 'POST': data = json.loads(request.data) portfolios.append(data["portfolio"]) feeds = data["feeds"] print(portfolios) print(feeds) else: return "API not available through GET" #clean for root, dirs, files in os.walk('clips/'): for filename in files: if filename == 'empty.txt': continue os.remove('clips/' + filename) if len(feeds) == 0: return json.dumps({ 'success': 'false', 'metadata': 'No feeds selected' }), 400, { 'ContentType': 'application/json' } #Stopwatch start_time = datetime.datetime.now() tickers = [] for p in portfolios: portfolio_start = datetime.datetime.now() holdings = investmentportfolio.Get_Portfolio_Holdings( p, False)['holdings'] #Since the payload is too large, odds are there are 500-instrument chunks added to the portfolio. for ph in range(0, len(holdings)): #names = [row['instrumentId'] for row in holdings[ph]['holdings']] print(ph) if len(holdings[ph]['holdings']) > 0: if 'Ticker' in holdings[ph]['holdings'][0]: tickers = [ row['Ticker'] for row in holdings[ph]['holdings'] ] print(tickers) print("Total time elapsed: " + str(datetime.datetime.now() - start_time)) #initiate processor metadataFile = "clips/clip.metadata" if bloomUsNews in feeds: print("Bloomberg US feed...") bloomUsProcess = subprocess.Popen( ['python', 'processor/main.py', bloomUsNews], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) t = threading.Thread(target=output_reader_bloom_us, args=(bloomUsProcess, )) t.start() if bloomGlobNews in feeds: print("Bloomberg Global feed...") bloomGlobProcess = subprocess.Popen([ 'python', 'processor/main.py', 'https://www.youtube.com/watch?v=Ga3maNZ0x0w' ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) t = threading.Thread(target=output_reader_bloom_glob, args=(bloomGlobProcess, )) t.start() if skyNews in feeds: print("Sky News feed...") skyProcess = subprocess.Popen(['python', 'processor/main.py', skyNews], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) t = threading.Thread(target=output_reader_sky, args=(skyProcess, )) t.start() if cnbcAfricaNews in feeds: print("CNBC Africa feed...") cnbcAfricaProcess = subprocess.Popen( ['python', 'processor/main.py', cnbcAfricaNews], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) t = threading.Thread(target=output_reader_cnbc_africa, args=(cnbcAfricaProcess, )) t.start() return json.dumps({ 'success': 'true', 'metadata': 'Processor started on feeds' + str(feeds) }), 200, { 'ContentType': 'application/json' }
def optimize(): ''' Runs an optimization calculation from a series of inputs 1) Grabs instrument universe, user portfolio and benchmark portfolio 2) Builds input portfolios in correct format 3) Iterates through constraints and builds 'subportfolios' - groupings required to leverage constraints 4) Runs the optimization and returns the set of optimal trades. ''' optimization = { 'portfolios': [], 'objectives': [], 'constraints': [] } #retrieve the json from the ajax call req = '' if request.method == 'POST': req = json.loads(request.data) else: #for debug, a sample request req = { 'user_portfolio': { 'Type':'existing', 'Name':'my_portfolio' }, 'benchmark':'Aggressive', 'hard_constraints':['has_tobacco','has_military'], 'esg_constraints':[ {'type':'esg_sustainability','value':'Average'}, {'type':'esg_environmental','value':'High'} ], 'allocation_constraints':[ {'type':'asset-class','value':'Equity','allocation':.5,'inequality':'equal'}, {'type':'asset-class','value':'Corporate Bonds','allocation':.3,'inequality':'less than or equal'}, {'type':'asset-class','value':'Government Bonds','allocation':.2,'inequality':'equal'} ], 'result_requirements':[ {'type':'AllowShortSales','value':False}, {'type':'MaximumInvestmentWeight','value':.2}, #note the decimal! It's a percentage {'type':'CashInfusion','value':50000} ] } #FETCH PORTFOLIOS==================================================================================================== #Grab user portfolio if req['user_portfolio'] is not None: try: user_portfolio = investmentportfolio.Get_Portfolio_Holdings(req['user_portfolio']['Name'])['holdings'][0]['holdings']['holdings'] except: user_portfolio = { 'name': 'user_portfolio', 'type':'user_portfolio', 'holdings':[] } else: user_portfolio = { 'name': 'user_portfolio', 'type':'user_portfolio', 'holdings':[] } #Grab instrument universe holdings (for instrument universe definition and to grab all the meta-data) iu = investmentportfolio.Get_Portfolio_Holdings('instrument_universe')['holdings'][0]['holdings']['holdings'] #Grab benchmark benchmark = investmentportfolio.Get_Portfolio_Holdings(req['benchmark'])['holdings'][0]['holdings']['holdings'] #CONSTRUCT INSTRUMETN UNIVERSE, BENCHMARK and OBJECTIVE=================================================================== #need to add position units with anything you currently hold tradeable_universe = { 'name': 'Universe', 'type':'root', 'holdings':[] } for asset in iu: if req['user_portfolio']['Type'] != 'new': holding = [h['quantity'] for h in user_portfolio if h['instrumentId'] == asset['instrumentId']] if holding != []: tradeable_universe['holdings'].append({'asset':asset['instrumentId'],'quantity':holding[0]}) else: tradeable_universe['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) else: tradeable_universe['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) optimization['portfolios'].append(tradeable_universe) #Construct benchmark portfolios - assumes benchmarks have defined quantities. benchmark_portfolio = { 'name': req['benchmark'], 'type':'benchmark', 'holdings':[] } for b in benchmark: benchmark_portfolio['holdings'].append({'asset':b['instrumentId'],'quantity':b['quantity']}) optimization['portfolios'].append(benchmark_portfolio) #Objective function optimization['objectives'] = [{ 'sense': 'minimize', 'measure': 'variance', 'attribute': 'return', 'portfolio': 'Universe', 'TargetPortfolio': req['benchmark'], 'timestep': 30, 'description': 'minimize tracking error squared (variance of the difference between Universe portfolio and Benchmark returns) at time 30 days' }] #HARD CONSTRAINTS==================================================================================================== #Add sub-portfolio (how the optimization algorithm knows which asset has which property) for hc in req['hard_constraints']: #initialize the subportfolio if hc != None: subportfolio = { 'ParentPortfolio':'Universe', 'name':hc, 'type':'subportfolio', 'holdings':[] } #filter the instrument universe on things that have this property. This is easy as it's true/false. for asset in iu: if asset[hc] == 'TRUE': #Figure out if the user currently holds any of the asset as the quantity needs to be adjusted if req['user_portfolio']['Type'] != 'new': q = [row['quantity'] for row in user_portfolio if row['instrumentId']==asset['instrumentId']] if q!=[]: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':q[0]}) else: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) else: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) optimization['portfolios'].append(subportfolio) #Add constraint to list optimization['constraints'].append({ 'attribute':'weight', 'portfolio':hc, 'InPortfolio':'Universe', 'relation':'equal', 'constant':0.0, 'description':'Excluding all securities which have the property ' + hc + '.' }) #ESG CONSTRAINTS==================================================================================================== #Add sub-portfolio (how the optimization algorithm knows which asset has which property) #Until we have a live data stream, we're taking a shortcut and making a single grouping and setting it to x% for esgc in req['esg_constraints']: #initialize the subportfolio subportfolio_name = esgc['value'] + '-' + esgc['type'] subportfolio = { 'ParentPortfolio':'Universe', 'name':subportfolio_name, 'type':'subportfolio', 'holdings':[] } #filter the instrument universe on things that have this property. This is easy as it's true/false. for asset in iu: if asset[esgc['type']] == esgc['value']: if req['user_portfolio']['Type'] != 'new': #Figure out if the user currently holds any of the asset as the quantity needs to be adjusted q = [row['quantity'] for row in user_portfolio if row['instrumentId']==asset['instrumentId']] if q!=[]: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':q[0]}) else: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) else: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) optimization['portfolios'].append(subportfolio) #Add constraint to list optimization['constraints'].append({ 'attribute':'weight', 'portfolio':subportfolio_name, 'InPortfolio':'Universe', 'relation':'greater-or-equal', 'constant':0.5, 'description':'Setting the portfolio to have an average ' + esgc['type'] + ' score of ' + esgc['value']+ '.' }) #ALLOCATION CONSTRAINTS================================================================================================ #Add sub-portfolio (how the optimization algorithm knows which asset has which property) for ac in req['allocation_constraints']: #initialize the subportfolio subportfolio_name = ac['type'] + '-' + ac['value'] subportfolio = { 'ParentPortfolio':'Universe', 'name':subportfolio_name, 'type':'subportfolio', 'holdings':[] } #filter the instrument universe on things that have this property. This is easy as it's true/false. for asset in iu: if asset[ac['type']] == ac['value']: #Figure out if the user currently holds any of the asset as the quantity needs to be adjusted if req['user_portfolio']['Type'] != 'new': q = [row['quantity'] for row in user_portfolio if row['instrumentId']==asset['instrumentId']] if q!=[]: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':q[0]}) else: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) else: subportfolio['holdings'].append({'asset':asset['instrumentId'],'quantity':0}) optimization['portfolios'].append(subportfolio) #Add constraint to list optimization['constraints'].append({ 'attribute':'weight', 'portfolio':subportfolio_name, 'InPortfolio':'Universe', 'relation':ac['inequality'], 'constant':ac['allocation'], 'description':'Sets the allocation to assets with a[n] ' + str(ac['type']) + ' of ' + str(ac['value']) + ' to be ' + str(ac['inequality']) + ' to ' + str(ac['allocation']) + '.' }) #RESULT REQUIREMENTS================================================================================================ rr = req['result_requirements'] for r in rr: #Short Sale Restriction if r['type'] == 'AllowShortSales': if r['value'] == 'False': optimization['constraints'].append({ 'attribute':'weight', 'relation':'greater-or-equal', 'members':'Universe', 'constant':0, 'description':'no short-sales for assets in Universe portfolio' }) #Maximum weight of any one position if r['type'] == 'MaximumInvestmentWeight': optimization['constraints'].append({ 'attribute':'weight', 'relation':'less-or-equal', 'members':'Universe', 'constant':r['value'], 'description':'Weight of each asset from the Universe portfolio does not exceed ' + str(r['value']*100) + '%.' }) #Cash infusions if r['type'] == 'CashInfusion': optimization['constraints'].append({ 'attribute:': 'value', 'portfolio': 'Universe', 'cashadjust': float(r['value']), 'description': 'cash inflow of ' + str(r['value']) +' monetary units to the Universe portfolio'}) optimized_portfolio = portfoliooptimization.Optimize(optimization) #ASSEMBLE PORTFOLIO ATTRIBUTES================================================================================================ #We tack on attributes from instrument universe for each optimized holding (oh) in the response from the instrument universe (iu) try: #only instruments involved in the before or after optimized_portfolio['Holdings'] = [row for row in optimized_portfolio['Holdings'] if (row['OptimizedQuantity']!=0 or row['Quantity']!=0)] for oh in range(0,len(optimized_portfolio['Holdings'])): #Name optimized_portfolio['Holdings'][oh]['Name'] = [row['asset'] for row in iu if row['instrumentId']==optimized_portfolio['Holdings'][oh]['Asset']][0] #Market Price optimized_portfolio['Holdings'][oh]['Price'] = [float(row['Price']) for row in iu if row['instrumentId']==optimized_portfolio['Holdings'][oh]['Asset']][0] #Aggregate Value optimized_portfolio['Holdings'][oh]['Total Value'] = optimized_portfolio['Holdings'][oh]['Price'] * optimized_portfolio['Holdings'][oh]['OptimizedQuantity'] #hard constraints try: for x in req['hard_constraints']: optimized_portfolio['Holdings'][oh][x] = [row[x] for row in iu if row['instrumentId']==optimized_portfolio['Holdings'][oh]['Asset']][0] except: pass #esg constraints try: for x in req['esg_constraints']: optimized_portfolio['Holdings'][oh][x['type']] = [row[x['type']] for row in iu if row['instrumentId']==optimized_portfolio['Holdings'][oh]['Asset']][0] except: pass #allocation constraints try: for x in req['allocation_constraints']: optimized_portfolio['Holdings'][oh][x['type']] = [row[x['type']] for row in iu if row['instrumentId']==optimized_portfolio['Holdings'][oh]['Asset']][0] except: pass return Response(json.dumps(optimized_portfolio, indent=4,sort_keys=True), mimetype='application/json') except: return Response(json.dumps(optimized_portfolio), mimetype='application/json')
def compute_unit_tests(): ''' Calculates analytics for a portfolio. Breaks into 500 instrument chunks to comply with container constraints. ''' if request.method == 'POST': portfolios = [] data = json.loads(request.data) portfolios.append(data["portfolio"]) #Stopwatch start_time = datetime.datetime.now() if data: analytics = data["analytics"] else: analytics = ['Price', 'Value'] results = [] for p in portfolios: portfolio_start = datetime.datetime.now() holdings = investmentportfolio.Get_Portfolio_Holdings( p, False)['holdings'] #Since the payload is too large, odds are there are 500-instrument chunks added to the portfolio. for ph in range(0, len(holdings)): #TEMPORARY WORKAROUND SINCE ID-TYPE not in legacy holdings... #Strip out "CX_" and "_XYZ" using split on underscore instruments = [{ 'id': row['instrumentId'].split("_")[1], "type": "isin" } for row in holdings[ph]['holdings']] print("Processing " + str(p) + " portfolio segment #" + str(ph) + ".") #send 500 IDs at a time to Instrument Analytics Service: #for i in instruments... for i in range(0, len(instruments), 500): ids = instruments[i:i + 500] ia = simulated_instrument_analytics.simulate(ids, analytics) #for j in results... if 'error' not in ia: for j in ia['results']: r = { "portfolio": p, "ID": j['ID'], "Currency": j["Currency"], "Simulation Date": j["Simulation Date"], "Name": j["Name"] } for a in analytics: r[a] = j[a] h = [ row for row in holdings[0]['holdings'] if j['ID'] in row['instrumentId'] ][0] #Note: "in" is a workaround as we transition from old IDs ("CX_<id>_<XYZ>") to new ids for key, value in h.items(): if key not in [ 'instrumentId', 'Currency', 'Name', 'name', 'CURRENCY', 'PRICE' ]: r[key] = value print(r) results.append(r) print("Processed securities " + str(i) + " through " + str(i + 500) + ". Time elapsed on this portfolio: " + str(datetime.datetime.now() - portfolio_start)) print("Unit testing completed. Total time elapsed: " + str(datetime.datetime.now() - start_time)) return Response(json.dumps(results), mimetype='application/json')