def test_plan_purchases_knapsack_test_2(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec1: Security = Security("sec1", "SEC1", price=33.0, buy_restricted=0) sec2: Security = Security("sec2", "SEC2", price=49.0, buy_restricted=0) ac.add_security(sec1) ac.add_security(sec2) purchases: List[Purchase] = ac.plan_purchases(98.5) self.assertEqual(purchases, {"sec2": Purchase(sec2, 2)})
def test_plan_purchases_buy_restricted_test_1(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec1: Security = Security("sec1", "SEC1", price=33.0, buy_restricted=0) sec2: Security = Security("sec2", "SEC2", price=49.0, buy_restricted=1) ac.add_security(sec1) ac.add_security(sec2) purchases: List[Purchase] = ac.plan_purchases(98.5) p1: Purchase = Purchase(sec1, 2) self.assertEqual(len(purchases), 1) self.assertEqual(purchases["sec1"], p1)
def test_get_asset_class_expenditures(self): d: Deposit = Deposit() sec1: Security = Security( "sec1", "SEC1", price=10.0, buy_restricted=False ) sec2: Security = Security( "sec2", "SEC2", price=5.0, buy_restricted=False ) d.add_purchase("ac", Purchase(sec1, 5)) d.add_purchase("ac", Purchase(sec2, 10)) self.assertEqual(d.get_asset_class_expenditures("ac"), 100.0)
def test_get_dividends(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec1: Security = Security("sec1", "SEC1", "sec1_name", 10.0) sec2: Security = Security("sec2", "SEC2", "sec2_name", 10.0) ac.add_security(sec1) ac.add_security(sec2) ac.buy(sec1, 3, False) ac.buy(sec2, 3, False) hol1: Holding = ac.get_holding("sec1") hol2: Holding = ac.get_holding("sec2") hol1.set_dividends(1.0) hol2.set_dividends(2.0) self.assertEqual(ac.get_dividends(), 3.0)
def test_add_holding_hs_hh(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", price=10.0) ac.add_security(sec) ac.buy(sec, 3, False) hol: Holding = Holding(sec, 3, 10.0) self.assertRaises(Exception, ac.add_holding, hol)
def test_get_return_negative(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", "sec_name", 10.0) ac.add_security(sec) ac.buy(sec, 3, False) sec.set_price(9.0) self.assertEqual(ac.get_return(), -0.1)
def test_buy_new(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", price=5.0) ac.add_security(sec) ac.buy(sec, 3, False) self.assertEqual(ac.get_holding("sec"), Holding(sec, 3, 5.0)) self.assertEqual(ac.get_value(), 15.0)
def test_equality(self): ac1: AssetClass = AssetClass("ac", target_percentage=1.0) ac2: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC") ac1.add_security(sec) ac2.add_security(sec) self.assertEqual(ac1, ac2)
def test_get_num_shares(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", "sec_name", 15.0) ac.add_security(sec) ac.buy(sec, 3, False) self.assertEqual(ac.get_num_shares(), 3) ac.buy(sec, 7, False) self.assertEqual(ac.get_num_shares(), 10)
def test_add_purchase(self): d: Deposit = Deposit() sec: Security = Security("sec", "SEC", price=10.0) pur: Purchase = Purchase(sec, 5) d.add_purchase("ac", pur) self.assertEqual(d.get_total(), 50.0) self.assertEqual(d.get_num_shares(), 5) self.assertTrue(pur in d.get_purchases_for_asset_class("ac"))
def test_update_security(self): ac: AssetClass = AssetClass("ac", target_percentage=0.5) sec: Security = Security("sec", "SEC") ac.add_security(sec) ac.update_security("sec", "SEC", "Security A", 100.0) new_sec: Security = ac.get_security("sec") self.assertEqual(new_sec.get_name(), "Security A") self.assertEqual(new_sec.get_price(), 100.0)
def test_add_holding_hs_not_hh(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", price=10.0) ac.add_security(sec) ac.add_holding(Holding(sec, 3, 10.0)) holding: Holding = ac.get_holding("sec") self.assertEqual(holding.get_value(), 30.0) self.assertEqual(holding.get_num_shares(), 3) self.assertEqual(holding.get_average_buy_price(), 10.0)
def test_get_return_with_dividends(self): sec: Security = Security("sec", "SEC", price=11.0) # Value: $33 # Dividends: $7 # Cost: $30 # Return = (Value + Dividends - Cost) / Cost # = (33 + 7 - 30) / 30 # = 10 / 30 = 0.33 hol: Holding = Holding(sec, 3, 10.0, 7.0) self.assertEqual(hol.get_return(), 1.0 / 3.0)
def add_security(self, security: Security) -> None: """ Adds the given security to this asset class. """ sec_id: str = security.get_id() if not self.contains_security(sec_id): self.__securities[sec_id] = security else: raise Exception("add_security(): {} was already added to the '{}'" "asset class.".format(sec_id, self.get_name()))
def test_equality(self): sec: Security = Security( "sec", "SEC", price=10.0, buy_restricted=False ) pur: Purchase = Purchase(sec, 5) d1: Deposit = Deposit() d1.add_purchase("ac", pur) d2: Deposit = Deposit() d2.add_purchase("ac", pur) self.assertEqual(d1, d2)
def buy(self, security: Security, num_shares: int, online: bool) -> str: """ Adds num_shares of the given security to the holdings of this asset class. Returns the state of the buy transaction. """ price: Optional[float] = security.get_price() if price is None: raise Exception("Can't buy security with undefined price") security_id: str = security.get_id() if self.contains_holding(security_id): hol: Holding = self.get_holding(security_id) old_num_shares: int = hol.get_num_shares() old_abp: float = hol.get_average_buy_price() old_div: float = hol.get_dividends() new_num_shares: int = old_num_shares + num_shares new_abp: float = (old_abp * old_num_shares + price * num_shares) / (old_num_shares + num_shares) self.update_holding(security_id, new_num_shares, new_abp, old_div) else: self.update_holding(security_id, num_shares, price, 0.0) log.info("Buy {n} {s} of {c} at {p} for a total of {t}? (Y/n) ".format( n=num_shares, s="shares" if num_shares > 1 else "share", c=security.get_symbol(), p=dollar_str(price), t=dollar_str(price * num_shares), )) order_state: str = "confirmed" if online: # Actually buy the ETFs user_choice: str = input("").lower() if user_choice in ["", "y"]: resp: Dict[str, Any] = r.order_buy_limit(security.get_symbol(), num_shares, security.get_price()) if resp is None: order_state = "failed" else: order_state = resp["state"] return order_state
def load_configuration( self, portfolio_config: List[Dict[str, Any]] ) -> None: """ Loads the target investment portfolio (weights for asset classes and the securities underlying those asset classes) from the given config file. """ total_target_pct: float = 0.0 for a in portfolio_config: # Sanity check for portfolio config format assert "name" in a assert "target_percentage" in a assert "securities" in a assert "buy_restrictions" in a ac_name: str = a["name"] ac_target_pct: float = float(a["target_percentage"]) ac_securities: List[str] = a["securities"] ac_buy_restrictions: List[str] = a["buy_restrictions"] for sec_id in ac_buy_restrictions: assert sec_id in ac_securities ac: AssetClass = ( self.get_asset_class(ac_name) if self.contains_asset_class(ac_name) else AssetClass(ac_name, ac_target_pct) ) ac.set_target_percentage(ac_target_pct) total_target_pct += ac_target_pct for s_id in ac_securities: buy_restricted: int = int(s_id in ac_buy_restrictions) s: Security = Security(s_id, buy_restricted=buy_restricted) if not ac.contains_security(s_id): ac.add_security(s) else: sec: Security = ac.get_security(s_id) # Potentially update buy restriction status if buy_restricted: sec.restrict_buy() else: sec.enable_buy() if not self.contains_asset_class(ac_name): self.add_asset_class(ac) self.add_asset_class(AssetClass("Other", 0.0)) if abs(total_target_pct - 1.0) >= 1e-9: raise Exception( "load_configuration(): asset class target percentages do not " "add up to 1." )
def test_to_dict_with_nones(self): sec: Security = Security("sec", "SEC") test = { "id": "sec", "symbol": "SEC", "name": None, "price": None, "purchase_buffer": 0.0, "buy_restricted": 1, } self.assertEqual(sec.to_dict(), test)
def __init__(self, security: Security, num_shares: int) -> None: sec_price = security.get_price() if sec_price is None: raise Exception( "Purchase must be instantiated with a Security " "that has a non-None price" ) if num_shares <= 0: raise Exception( "Purchase must be instantiated with num_shares >= 0" ) self.__security: Security = security self.__num_shares: int = num_shares self.__cost: float = sec_price * self.__num_shares
def test_update_holding(self): ac: AssetClass = AssetClass("ac", target_percentage=0.5) sec: Security = Security("sec", "SEC", name="sec_name", price=40.0, buy_restricted=0) ac.add_security(sec) ac.buy(sec, 3, False) ac.update_holding("sec", 3, 45.0, 15.0) new_hol: Holding = ac.get_holding("sec") self.assertEqual(new_hol.get_num_shares(), 3) self.assertEqual(new_hol.get_average_buy_price(), 45.0) self.assertEqual(new_hol.get_dividends(), 15.0)
def update( self, account_profile: AccountProfile, securities: Dict[str, SecurityInfo], holdings: Dict[str, HoldingInfo], dividends: Dict[str, DividendInfo], ) -> None: """ Updates this portfolio (and its underlying asset classes and securities). """ cash: float = account_profile.get_buying_power() self.set_cash(cash) # Group securities that aren't defined in portfolio configuration in # "Other" asset class for sec_id in holdings.keys(): # Extract refreshed data for this security hol_info: HoldingInfo = holdings[sec_id] sec_info: SecurityInfo = securities[sec_id] div_info: DividendInfo = dividends[sec_id] updated_shares: int = hol_info.get_quantity() updated_average_buy_price: float = ( hol_info.get_average_buy_price() ) updated_name: str = sec_info.get_name() updated_symbol: str = sec_info.get_symbol() updated_price: float = sec_info.get_price() updated_dividends: float = div_info.get_amount() # Update the state for this security ac: AssetClass = self.get_asset_class_for_security(sec_id) if not ac.contains_security(sec_id): ac.add_security(Security(sec_id)) ac.update_security( sec_id, updated_symbol, updated_name, updated_price ) ac.update_holding( sec_id, updated_shares, updated_average_buy_price, updated_dividends, )
def test_add_security_not_exists(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) ac.add_security(Security("sec", "SEC")) self.assertEqual(ac.get_security("sec"), Security("sec", "SEC"))
def test_contains_holding_true(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec = Security("sec", "SEC", price=30.0) ac.add_security(sec) ac.buy(sec, 1, False) self.assertTrue(ac.contains_holding("sec"))
def test_inequality(self) -> None: ac1: AssetClass = AssetClass("ac", target_percentage=1.0) ac2: AssetClass = AssetClass("ac", target_percentage=1.0) ac2.add_security(Security("sec", "SEC")) self.assertNotEqual(ac1, ac2)
def test_get_return_zero(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", "sec_name", 15.0) ac.add_security(sec) ac.buy(sec, 3, False) self.assertEqual(ac.get_return(), 0.0)
def test_get_holding_not_exists(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", "sec_name", 15.0) ac.add_security(sec) self.assertRaises(Exception, ac.get_holding, "sec")
def test_get_holding_exists(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC", "sec_name", 15.0) ac.add_security(sec) ac.buy(sec, 3, False) self.assertEqual(ac.get_holding("sec"), Holding(sec, 3, 15.0))
def test_get_security_exists(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec", "SEC") ac.add_security(sec) self.assertEqual(ac.get_security("sec"), sec)
def test_get_purchase_buffer_update(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec1", "SEC1", "sec1_name", 50.0, 0) ac.add_security(sec) sec.set_price(100.0) self.assertTrue(abs(ac.get_purchase_buffer() - 15.0) < 10e-10)
def test_get_purchase_buffer_new(self): ac: AssetClass = AssetClass("ac", target_percentage=1.0) sec: Security = Security("sec1", "SEC1", price=50.0) ac.add_security(sec) self.assertTrue(abs(ac.get_purchase_buffer() - 7.5) < 10e-10)