Source code for portfolio_toolkit.asset.portfolio_asset

from dataclasses import dataclass, field
from typing import List

import pandas as pd

from .market_asset import MarketAsset
from .portfolio_asset_transaction import PortfolioAssetTransaction


[docs] @dataclass class PortfolioAsset(MarketAsset): transactions: List[PortfolioAssetTransaction] = field(default_factory=list)
[docs] @classmethod def to_dataframe(cls, assets: List["PortfolioAsset"]) -> pd.DataFrame: """Convert a list of PortfolioAsset objects to a pandas DataFrame.""" data = pd.DataFrame() if not assets: return data for asset in assets: transactions = PortfolioAssetTransaction.to_dataframe( asset.transactions, asset.ticker ) data = pd.concat([data, transactions], ignore_index=True) data.sort_values(by=["date", "ticker"], inplace=True) data.reset_index(drop=True, inplace=True) return data
[docs] def add_transaction(self, transaction: PortfolioAssetTransaction): """ Adds a transaction to the portfolio asset. """ self.transactions.append(transaction)
[docs] def add_transaction_from_dict(self, transaction_dict: dict): """ Adds a transaction to the account from a dictionary. """ transaction = PortfolioAssetTransaction( date=transaction_dict["date"], transaction_type=transaction_dict["type"], quantity=transaction_dict["quantity"], price=transaction_dict["price"], currency=transaction_dict["currency"], total=transaction_dict["total"], exchange_rate=transaction_dict["exchange_rate"], subtotal_base=transaction_dict["subtotal_base"], fees_base=transaction_dict["fees_base"], total_base=transaction_dict["total_base"], ) self.add_transaction(transaction)
[docs] def add_split(self, split_dict: dict) -> float: """ Adds a stock split to the portfolio asset by simulating sell all + buy equivalent. Creates a sell transaction for all held shares and a buy transaction for split-adjusted quantity. Args: split_dict: Dictionary containing split information with keys: - date: Split date (str) - split_factor: Split ratio as float (e.g., 2.0 for 2:1 split, 0.1 for 1:10 reverse split) Returns: float: Cash amount to be added to account due to fractional shares sold (only applies to reverse splits where shares are lost) """ from portfolio_toolkit.position.get_asset_open_positions import ( get_asset_open_positions, ) split_date = split_dict["date"] split_factor = split_dict["split_factor"] # Get open positions at split date (day before split) open_positions = get_asset_open_positions(self, split_date) if not open_positions: # No open positions to split return 0.0 # Calculate total quantity held and average cost total_quantity = open_positions.quantity total_cost_base = open_positions.cost average_cost_per_share = ( total_cost_base / total_quantity if total_quantity > 0 else 0 ) # Calculate new quantities after split exact_new_quantity = total_quantity * split_factor new_total_quantity = int(exact_new_quantity) # Integer part only fractional_shares = ( exact_new_quantity - new_total_quantity ) # Shares that become cash # Calculate prices and costs new_price_per_share = average_cost_per_share / split_factor new_total_cost_base = new_total_quantity * new_price_per_share # Calculate cash from fractional shares (at post-split price) cash_from_fractional_shares = fractional_shares * new_price_per_share # Create sell transaction for all current holdings sell_transaction = PortfolioAssetTransaction( date=split_date, transaction_type="sell", quantity=total_quantity, price=average_cost_per_share, currency=self.currency, total=total_cost_base, exchange_rate=1.0, # Assuming same currency subtotal_base=total_cost_base, fees_base=0.0, # No fees for split total_base=total_cost_base, ) # Create buy transaction for split-adjusted quantity (only whole shares) buy_transaction = PortfolioAssetTransaction( date=split_date, transaction_type="buy", quantity=new_total_quantity, price=new_price_per_share, currency=self.currency, total=new_total_cost_base, exchange_rate=1.0, # Assuming same currency subtotal_base=new_total_cost_base, fees_base=0.0, # No fees for split total_base=new_total_cost_base, ) # Add transactions to the asset self.add_transaction(sell_transaction) self.add_transaction(buy_transaction) # Sort transactions by date to maintain chronological order self.transactions.sort(key=lambda x: x.date) # Return cash amount from fractional shares return cash_from_fractional_shares
def __repr__(self): return ( f"PortfolioAsset(ticker={self.ticker}, sector={self.sector}, currency={self.currency}, " f"prices_length={len(self.prices)}, transactions_count={len(self.transactions)}, " f"info_keys={list(self.info.keys())})" )