def create_simulated_portfolio( portfolio: pd.DataFrame, sim_symbol: str = "SWPPX" ) -> pd.DataFrame: """Given a portfolio, create a parallel portfolio which has all of the same contributions and withdrawals. In the parallel simulated portfolio, all contributions are immediately used to purchase the security indicated by the `sim_symbol`, and all withdrawals are financed by selling this security. """ sim_port = port.init_portfolio(portfolio.index) sim_port = port.init_symbol(sim_port, sim_symbol) prices = quotes.get_price(sim_symbol, sim_port.index) for column in ["contributions", "withdrawals"]: sim_port[column] = portfolio[column].copy() deposits = port.get_deposits(portfolio) for date, amt in deposits[deposits != 0].iteritems(): qty = np.abs(amt) / prices.loc[date] sign = -1 if amt < 0 else 1 sim_port.loc[date:, f"{sim_symbol}"] += sign * qty # We've recorded simulated purchases, but the simulated security # would also have generated dividends. sim_port = add_sim_dividend(sim_port, sim_symbol) port.total_portfolio(sim_port) return sim_port
def total_portfolio(portfolio: pd.DataFrame) -> pd.DataFrame: symbols = [c for c in portfolio.columns if c == c.upper()] portfolio["_total"] = 0.0 for symbol in symbols: price = quotes.get_price(symbol, portfolio.index) portfolio[f"{symbol}_value"] = portfolio[symbol] * price portfolio["_total"] += portfolio[f"{symbol}_value"] return portfolio
def record_inv_action(portfolio: pd.DataFrame, action: pd.Series) -> pd.DataFrame: """Mark the results of an AceMoney investment transaction in a portfolio `portfolio`: pd.DataFrame Initialized as per `init_portfolio` `action`: pd.Series A row from the all-investment-transactions AceMoney export """ port.init_symbol(portfolio, symbol=action['Symbol']) if not pd.isnull(action['Dividend']) and action['Dividend'] != 0: portfolio.loc[action.Date:, f"{action.Symbol}_dist"] += action['Dividend'] portfolio.loc[action.Date:, f"_total_dist"] += action.Dividend if not pd.isnull(action['Quantity']) and action['Quantity'] != 0: sign = -1 if action['Action'] in ['Sell', 'Remove Shares'] else 1 portfolio.loc[action.Date:, f"{action.Symbol}"] += sign * action['Quantity'] if not pd.isnull(action['Total']) and action['Total'] != 0: sign = -1 if action['Action'] == 'Buy' else 1 portfolio.loc[action['Date']:, "cash"] += sign * action['Total'] if action['Action'] == 'Add Shares' and '__contribution__' in action[ 'Comment']: price = quotes.get_price(action.Symbol, [action.Date]).values[0] log.debug(f"Contribution of {action.Quantity} shares of " f"{action.Symbol} @ {price} per share.") portfolio.loc[action.Date:, 'contributions'] += price * action['Quantity'] if action['Action'] == 'Add Shares' and '__dividend__' in action['Comment']: value = (quotes.get_price(action.Symbol, [action.Date]).values[0] * action['Quantity']) log.debug(f"Dividend of {action.Quantity} shares of {action.Symbol} " f"of {action.Date} is ${value}.") portfolio.loc[action.Date:, f"{action.Symbol}_dist"] += value portfolio.loc[action.Date:, f"_total_dist"] += value return portfolio
def record_inv_action(portfolio: pd.DataFrame, action: pd.Series) -> pd.DataFrame: """Mark the results of an AceMoney investment transaction in a portfolio `portfolio`: pd.DataFrame Initialized as per `init_portfolio` `action`: pd.Series A row from the all-investment-transactions AceMoney export """ port.init_symbol(portfolio, symbol=action["Symbol"]) if not pd.isnull(action["Dividend"]) and action["Dividend"] != 0: portfolio.loc[action.Date:, f"{action.Symbol}_dist"] += action["Dividend"] portfolio.loc[action.Date:, f"_total_dist"] += action.Dividend if not pd.isnull(action["Quantity"]) and action["Quantity"] != 0: sign = -1 if action["Action"] in ["Sell", "Remove Shares"] else 1 portfolio.loc[action.Date:, f"{action.Symbol}"] += sign * action["Quantity"] if not pd.isnull(action["Total"]) and action["Total"] != 0: sign = -1 if action["Action"] == "Buy" else 1 portfolio.loc[action["Date"]:, "cash"] += sign * action["Total"] if action["Action"] == "Add Shares" and "__contribution__" in action[ "Comment"]: price = quotes.get_price(action.Symbol, [action.Date]).values[0] log.debug(f"Contribution of {action.Quantity} shares of " f"{action.Symbol} @ {price} per share.") portfolio.loc[action.Date:, "contributions"] += price * action["Quantity"] if action["Action"] == "Add Shares" and "__dividend__" in action["Comment"]: value = (quotes.get_price(action.Symbol, [action.Date]).values[0] * action["Quantity"]) log.debug(f"Dividend of {action.Quantity} shares of {action.Symbol} " f"of {action.Date} is ${value}.") portfolio.loc[action.Date:, f"{action.Symbol}_dist"] += value portfolio.loc[action.Date:, f"_total_dist"] += value return portfolio
def add_sim_dividend(portfolio: pd.DataFrame, sim_symbol: str) -> pd.DataFrame: """Look at a portfolio and add the dividends that you would have gotten from a particular security. For simulated portfolios. """ div = quotes.get_dividend(sim_symbol, portfolio.index) for date, amount in div.iteritems(): if date in portfolio.index: price = quotes.get_price(sim_symbol, [date]).values[0] # Create a Series with 0s before the date, and the value of # the distribution at and after the date. value = portfolio.loc[[date], sim_symbol] * amount value = value.reindex(portfolio.index, method="ffill").fillna(0) qty = value / price portfolio[f"{sim_symbol}_dist"] += value portfolio[sim_symbol] += qty portfolio["_total_dist"] += value return portfolio
def _include_reinvest_dividend( buy_transactions: pd.DataFrame, dates: pd.DatetimeIndex ) -> pd.DataFrame: """Include "Reinvest Dividend" transactions in a buy transactions DataFrame This assumes that the `buy_transactions` input is a single security in a single account. """ trans = buy_transactions.set_index("Date").sort_index(ascending=True) assert len(buy_transactions["Symbol"].unique()) == 1 assert len(buy_transactions["Account"].unique()) == 1 symbol = buy_transactions["Symbol"].unique()[0] div = quotes.get_dividend(symbol, dates).sort_index() prices = quotes.get_price(symbol, div.index) div_trans = pd.DataFrame(columns=TRANS_HEADER) # Empty DF for appending for date, div_value in div.iteritems(): n_shares = trans.loc[:date, "Quantity"].sum() + div_trans["Quantity"].sum() div_trans_row = pd.DataFrame( { "Date": date, "Action": "Reinvest Dividend", "Symbol": symbol, "Commission": 0.0, "Total": 0.0, "Comment": "", "Account": trans["Account"].unique()[0], "Dividend": round(n_shares * div_value, 2), "Price": prices.loc[date], "Quantity": round((n_shares * div_value) / prices.loc[date], 4), }, columns=TRANS_HEADER, index=[len(div_trans)], ) div_trans = div_trans.append(div_trans_row) assert len(div_trans) == len(div) == len(prices) return ( buy_transactions.append(div_trans) .sort_values("Date", ascending=True) .reset_index(drop=True) )
def _create_single_buy_transactions(dates: pd.DatetimeIndex, contribution: float, symbol: str, account: str) -> pd.DataFrame: """Create buy transactions in a format matching AceMoney's investment transactions """ prices = quotes.get_price(symbol, dates) trans = pd.DataFrame( { "Date": dates, "Action": "Buy", "Symbol": symbol, "Account": account, "Dividend": 0., "Price": prices, "Quantity": np.round(contribution / prices, 4), "Commission": 0., "Total": contribution, "Comment": "" }, columns=TRANS_HEADER) return trans.reset_index(drop=True)