Package: py-portfolio-index
Py Portfolio Indexpy-portfolio-indexis a python library to make it easier to mantain a broad, index-based approach to stock investing while being able to layer in person preferences, such as to exclude or reweight certain kinds of stocks.For example, a user could construct a portfolio that matches the composition of the S&P 500, but excludes oil companies and overweights semiconductor companies.To do that, it provides tools for constructing and managing portfolios that are modeled off indexes. These ideal
portfolios can be efficiently converted into actual portfolios by API, using commission free platforms like Robinhood, Alpaca, or Webull. Since constructing an index analogue typically requires many small stock purchases, a
commission free platform is important to minimizing overhead. For small investment sizes, the ability of the platform to support fractional shares is critical to being able to accurately map to the index.Indexespy-portfolio-index contains a default set of indexes, which can be access via the INDEXES dictionary:from py_portfolio_index import INDEXES. These indexes are based on the common industry index cuts such as
Large Cap or real-estate and are updated quarterly.Lists/Themespy-portfolio-index also contains a default list of stock lists, which can be access via the STOCK_LISTS dictionary:from py_portfolio_index import STOCK_LISTS. These lists are thematic groupings, such as by industry (space)
or by other criteria (vice). Lists can be applied to modify indexes or reweight them to create customized
portfolios.InstallThe package supports Python 3.9 plus.pip install py-portfolio-indexNote that provider dependencies must be installed independently, for each provider you wish to use.alpaca -pip install alpaca-trade-apiorpip install py-portfolio-index[alpaca]robinhood -pip install robin_stocksorpip install py-portfolio-index[robinhood]webull -pip install webullorpip install py-portfolio-index[webull]ConsiderationsSome providers may take some time to place an order. Keep this in mind when running repeated rebalances, as the
portfolio balance may not have updated to reflect your last order.Also remember that the stock markets are not always open!Basic ExampleThis example shows a basic example using the Alpaca API in paper trading mode.It constructs an ideal portfolio based on the composition of the Vanguard ESG index fund in Q4 2020, then uses the
Alpaca API to construct a matching portfolio based on an initial investment of 10000 dollars.frompy_portfolio_indeximportINDEXES,STOCK_LISTS,Logger,AlpacaProvider,PurchaseStrategy,generate_order_planfromloggingimportINFO,StreamHandlerLogger.addHandler(StreamHandler())Logger.setLevel(INFO)# The size of our paper portfolioTARGET_PORTFOLIO_SIZE=10000# instantiate the Alpaca provider with identity information# and set it to use the paper provider# this expects the environment variables ALPACA_API_KEY and ALPACA_API_SECRET to be set,# or they can be passed in directly, using AlpacaProvider(key_id=..., secret_key=...)provider=AlpacaProvider()# get an example indexideal_portfolio=INDEXES['small_cap']# exclude all stocks from the oil, vice, and cruise listsideal_portfolio.exclude(STOCK_LISTS['oil']).exclude(STOCK_LISTS['vice']).exclude(STOCK_LISTS['cruises'])# double the weighting of stocks in the renewable and semiconductor lists, and set them to a minimum weight of .1%ideal_portfolio.reweight(STOCK_LISTS['renewable'],weight=2.0,min_weight=.001)ideal_portfolio.reweight(STOCK_LISTS['semiconductor'],weight=2.0,min_weight=.001)# get actual holdingsreal_port=provider.get_holdings()# compare actual holdings to this ideal portfolio to produce a buy and sell listplanned_orders=generate_order_plan(ideal=ideal_portfolio,real=real_port,buy_order=PurchaseStrategy.LARGEST_DIFF_FIRST,target_size=TARGET_PORTFOLIO_SIZE)# review the ordersforiteminplanned_orders.to_buy:print(item)# purchase the buy listprovider.purchase_order_plan(plan=planned_orders,fractional_shares=False,skip_errored_stocks=False)[!TIP]
You can set environment variables to avoid having to pass in your credentials each time. THese are specified per provider. For Alpaca, you can set ALPACA_API_KEY and ALPACA_API_SECRET.RobinhoodRobinhood is also commission free and supports fractional shares.frompy_portfolio_indeximportRobinhoodProvider,PurchaseStrategy,compare_portfolios,Logger,INDEXES,STOCK_LISTSfromloggingimportINFO,StreamHandlerLogger.addHandler(StreamHandler())Logger.setLevel(INFO)ideal_port=INDEXES['small_cap']# create a stock listSTOCK_LISTS.add_list('manual_override',['MDLZ'])# modify the indexideal_port.exclude(STOCK_LISTS['oil']).exclude(STOCK_LISTS['vice']).exclude(STOCK_LISTS['cruises']).exclude(STOCK_LISTS['manual_override'])# overweight on stonksideal_port.reweight(STOCK_LISTS['renewable'],weight=2.0,min_weight=.001)ideal_port.reweight(STOCK_LISTS['semiconductor'],weight=2.0,min_weight=.001)provider=RobinhoodProvider(username='#####',password='#########')real_port=provider.get_holdings()TARGET_SIZE=10000planned_orders=generate_order_plan(ideal=ideal_portfolio,real=real_port,buy_order=PurchaseStrategy.LARGEST_DIFF_FIRST,target_size=TARGET_PORTFOLIO_SIZE)# review the ordersforiteminplanned_orders.to_buy:print(item)# purchase the buy listprovider.purchase_order_plan(plan=planned_orders,fractional_shares=False,skip_errored_stocks=False)WebullWebull support is experimental. Follow similar patterns to the above examples, but use the WebullProvider.This currently usesthis unoffical API package, and requires you
to follow device-id authorization path from their docs.frompy_portfolio_indeximportWebullProviderTestingTo avoid actually purchasing a stock, use the plan_only option to log what trades would have occurred.provider.purchase_order_plan(plan=planned_orders,plan_only=True)Example ScriptsCan be found in the examples folder.LoggingIt can be helpful to configure the logger to print messages. You can either configure the standard python logger or use the portfolio specific one using an example like the below.frompy_portfolio_index.constantsimportLoggerfromloggingimportINFO,StreamHandlerLogger.addHandler(StreamHandler())Logger.setLevel(INFO)