### Example of wash sales over multiple repurchases Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates how a loss is washed over multiple repurchases. This example initializes the tracker, performs trades that result in wash sales, and then displays the capital gains, wash sales, closed positions, and open positions. ```python tracker = CapGainsTracker() tracker.trade(pd.Timestamp('2023-01-15'), 'XYZ', shares=100, price=20) tracker.trade(pd.Timestamp('2023-02-10'), 'XYZ', shares=-100, price=15) tracker.trade(pd.Timestamp('2023-02-25'), 'XYZ', shares=40, price=17) display(tracker.capital_gains_df) print("Wash sales:\n"+tracker.washed_lots_str) print("\nClosed positions:\n"+tracker.closed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) ``` ```python tracker.trade(pd.Timestamp('2023-03-08'), 'XYZ', shares=60, price=19) display(tracker.capital_gains_df) print("Wash sales:\n"+tracker.washed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) ``` ```python # Make sure we get the same result if we use buy/sell methods tracker = CapGainsTracker() tracker.buy(pd.Timestamp('2023-01-15'), 'XYZ', 100, price=20) tracker.sell(pd.Timestamp('2023-02-10'), 'XYZ', -100, price=15) tracker.buy(pd.Timestamp('2023-02-25'), 'XYZ', 40, price=17) tracker.buy(pd.Timestamp('2023-03-08'), 'XYZ', 60, price=19) display(tracker.capital_gains_df) print("Wash sales:\n"+tracker.washed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) ``` -------------------------------- ### Process Further Partial Wash Sales and Get Total Open Shares Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Continues the partial wash sale example by executing another sell order and validating the tracker. Displays the updated closed and open positions, and the total number of open shares. ```python tracker.sell(pd.Timestamp('2023-02-12'), 'XYZ', -40, price=14) tracker.validate() #print("Wash sales:\n"+tracker.WashTrades) print("\nClosed positions:\n"+tracker.closed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) print(f'\nTotal open shares = {tracker.get_position("XYZ")}') ``` -------------------------------- ### Distribution Tracking Example Source: https://github.com/dbookstaber/tax_tracker/blob/main/README.md Initialize DistributionTracker with distribution data, record trades, run through a date, and view dividend characterization. Requires pandas. ```python import pandas as pd from tax_tracker import DistributionTracker # Example distribution data distribution_data = pd.DataFrame({ 'Ticker': ['ABC', 'ABC', 'ABC'], 'Date': [pd.Timestamp('2023-02-01'), pd.Timestamp('2023-03-01'), pd.Timestamp('2023-03-15')], 'Type': ['Dividend', 'Return of Capital', 'Preferred Dividend'], 'Distribution': [0.22, 0.15, 0.30] }).set_index(['Ticker', 'Date', 'Type']) # Initialize the tracker tracker = DistributionTracker(distribution_data) # Record trades tracker.trade(pd.Timestamp('2023-01-01'), 'ABC', 100, 150.0) # Carry trade through April 1 tracker.run_beginning_of_day(pd.Timestamp('2023-04-01')) # Show dividend characterization as of April 1 print(tracker.dividends_df) ``` -------------------------------- ### Install TaxTracker Library Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Clone the repository and install dependencies. Requires pandas version 2.0.0 or higher and Python 3.10 or higher. ```bash git clone https://github.com/dbookstaber/tax_tracker.git cd tax_tracker pip install -r requirements.txt # requires pandas >=2.0.0, Python >=3.10 ``` -------------------------------- ### Capital Gains Tracking Example Source: https://github.com/dbookstaber/tax_tracker/blob/main/README.md Initialize CapGainsTracker, record trades, and view open/closed lots and capital gains. Requires pandas. ```python import pandas as pd from tax_tracker import CapGainsTracker # Initialize the tracker tracker = CapGainsTracker() # Record trades tracker.trade(pd.Timestamp('2023-01-01'), 'XYZ', 100, 150.0) # Buy 100 shares of XYZ at $150 tracker.trade(pd.Timestamp('2023-02-01'), 'XYZ', -50, 160.0) # Sell 50 shares of XYZ at $160 # View open positions print('Open Lots:\n' + tracker.open_lots_str) # View closed trades print('\nClosed Lots:\n' + tracker.closed_lots_str) # View capital gains print('\nCapital Gains:\n' + str(tracker.capital_gains_df)) ``` -------------------------------- ### Install TaxTracker Dependencies Source: https://github.com/dbookstaber/tax_tracker/blob/main/README.md Install the required Python dependencies for TaxTracker using pip. ```bash pip install -r requirements.txt ``` -------------------------------- ### Daily P&L and NAV Calculation Example Source: https://github.com/dbookstaber/tax_tracker/blob/main/README.md Initialize PNLtracker with price data, record trades, run through a date, and view daily returns. Requires pandas. ```python import pandas as pd from tax_tracker import PNLtracker # Example price data price_data = pd.DataFrame({ 'Ticker': ['XYZ'] * 5, 'Date': pd.date_range(start='2025-03-03', end='2025-03-07', freq='D'), 'Px': [15.0, 16.0, 17.0, 18.0, 19.0] }).set_index(['Ticker', 'Date']) # Initialize the tracker tracker = PNLtracker(price_data) # Record trades tracker.trade(pd.Timestamp('2025-03-03'), 'XYZ', 100, 15.1) tracker.trade(pd.Timestamp('2025-03-05'), 'XYZ', -50, 16.9) # Carry trade through March 7 tracker.run_end_of_day(pd.Timestamp('2025-03-07')) # View daily P&L and NAV print(tracker.daily_returns_df) ``` -------------------------------- ### Simulate Short Selling Trades with Tax Tracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates simulating short selling trades and tracking closed and open positions. This example is similar to 1.7b but focuses on short sales. ```python tracker = CapGainsTracker() tracker.trade(pd.Timestamp('2023-01-15'), 'XYZ', -60, price=16) tracker.trade(pd.Timestamp('2023-01-17'), 'XYZ', -50, price=15) tracker.trade(pd.Timestamp('2023-01-19'), 'XYZ', -40, price=14) tracker.trade(pd.Timestamp('2023-02-10'), 'XYZ', 20, price=20) tracker.validate() tracker.trade(pd.Timestamp('2023-02-11'), 'XYZ', 20, price=21) tracker.validate() #print("Wash sales:\n"+tracker.WashTrades) print("\nClosed positions:\n"+tracker.closed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) #print(f'\nTotal open shares = {tracker.get_position("XYZ")}') ``` -------------------------------- ### Initial Buy, Sell, and Display Status (No Wash Sale) Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Initializes the tracker, buys shares, and then sells a portion, realizing a short-term capital loss. This example demonstrates basic transaction recording and capital gains reporting without triggering wash sale rules due to sufficient time between transactions. ```python tracker = CapGainsTracker() # Buy 10 shares of ABC at $10 start_date = pd.Timestamp('2022-01-05') tracker.buy(start_date, 'ABC', shares=10, price=10) # Sell 5 shares of ABC at $9, realizing capital loss of $1/share sell_date = start_date + pd.Timedelta(days=1) tracker.sell(sell_date, 'ABC', shares_to_sell=5, price=9) assert tracker.capital_gains[sell_date]['ShortTerm'] == -5.0, "Realized -$5 ST gain" print("Closed position:" +tracker.closed_lots_str) display(tracker.capital_gains_df) print("Open position:" +tracker.open_lots_str) ``` -------------------------------- ### Initial trade and Short-Term loss realization Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates an initial purchase of shares followed by a sale that results in a Short-Term capital loss. This example also shows how to assert the calculated capital gains and display the tracker's state. ```python tracker = CapGainsTracker() # Buy 10 shares of ABC at $10 start_date = pd.Timestamp('2022-01-05') tracker.trade(start_date, 'ABC', shares=10, price=10) # Sell 5 shares of ABC at $9, realizing capital loss of $1/share sell_date = start_date + pd.Timedelta(days=1) tracker.trade(sell_date, 'ABC', -5, price=9) assert tracker.capital_gains[sell_date]['ShortTerm'] == -5.0, "Realized -$5 ST gain" display(tracker.capital_gains_df) print("Closed position:\n"+tracker.closed_lots_str) print("\nOpen position:\n"+tracker.open_lots_str) ``` -------------------------------- ### Clone TaxTracker Repository Source: https://github.com/dbookstaber/tax_tracker/blob/main/README.md Clone the TaxTracker repository locally and navigate into the directory. Ensure Python 3.10+ is installed. ```bash git clone https://github.com/dbookstaber/tax_tracker.git cd tax_tracker ``` -------------------------------- ### Get All Daily Distributions (DataFrame) Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a DataFrame of daily distributions, including any columns that might be empty. Creates a DataFrame from a dictionary of daily dividends. ```python return pd.DataFrame.from_dict(self.daily_dividends, orient='index') ``` -------------------------------- ### Short sale, cover, and realize gain Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates a short sale followed by covering part of the position, realizing a short-term capital gain. This example avoids the wash sale rule by covering within a day. ```python tracker = CapGainsTracker() # Short 10 shares of ABC at $10 start_date = pd.Timestamp('2022-01-05') tracker.trade(start_date, 'ABC', shares=-10, price=10) # Cover 5 shares of ABC at $9, realizing capital gain of $1/share cover_date = start_date + pd.Timedelta(days=1) tracker.trade(cover_date, 'ABC', 5, price=9) assert tracker.capital_gains[cover_date]['ShortTerm'] == 5.0, "Realized $5 ST gain" display(tracker.capital_gains_df) print("Closed position:\n"+tracker.closed_lots_str) print("\nOpen position:\n"+tracker.open_lots_str) ``` -------------------------------- ### Initialize tracker and execute initial trades Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Initializes a CapGainsTracker and executes a series of buy and sell trades to demonstrate capital gains calculation and position tracking. This is a foundational example for understanding trade mechanics. ```python tracker = CapGainsTracker() # Buy 5 shares of ABC at $10 start_date = pd.Timestamp('2022-01-05') tracker.trade(start_date, 'ABC', shares=5, price=10) # Sell 3 shares of ABC at $9, realizing capital loss of $1/share sell_date = start_date + pd.Timedelta(days=1) tracker.trade(sell_date, 'ABC', -3, price=9) display(tracker.capital_gains_df) print("Closed position:\n"+tracker.closed_lots_str) print("\nOpen position:\n"+tracker.open_lots_str) ``` -------------------------------- ### Record Trades and Apply Wash Sale Rule Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Shows how to record buy and sell trades using `CapGainsTracker.trade()`. This example demonstrates how a wash sale is triggered and how subsequent repurchases adjust the basis of open lots. ```python from tax_tracker import CapGainsTracker import pandas as pd tracker = CapGainsTracker() # 1. Buy 100 shares tracker.trade(pd.Timestamp('2023-01-15'), 'XYZ', 100, price=20) # 2. Sell at a loss — triggers potential wash sale tracker.trade(pd.Timestamp('2023-02-10'), 'XYZ', -100, price=15) print(tracker.capital_gains_df) # ShortTerm LongTerm # 2023-02-10 -500.0 0.0 # 3. Repurchase within 30 days — wash sale rule kicks in tracker.trade(pd.Timestamp('2023-02-25'), 'XYZ', 40, price=17) tracker.trade(pd.Timestamp('2023-03-08'), 'XYZ', 60, price=19) tracker.validate() print(tracker.capital_gains_df) # (all losses washed — zeroed out) print(len(tracker.wash_trades)) # 2 for wash in tracker.wash_trades: print(wash) # XYZ: 2023-01-15 100 @ $20.00 >> 2023-02-10 -100 @ $15.00 = $-500.00 ST (...) on 2023-02-25 # XYZ: 2023-01-15 100 @ $20.00 >> ... # Inspect open lots (replacement shares carry the washed basis) print(tracker.open_lots_str) # XYZ: 40 @ $17.00 (2023-02-25) Basis $-200.00 term+26 days # XYZ: 60 @ $19.00 (2023-03-08) Basis $-300.00 term+52 days print(tracker.get_position('XYZ')) # 100.0 ``` -------------------------------- ### Washed loss and gain in the same period Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates a scenario where a washed loss and a subsequent gain occur within the same tracking period. This example uses buy and sell methods to simulate the trades. ```python tracker = CapGainsTracker() tracker.buy(pd.Timestamp('2023-01-15'), 'XYZ', 100, price=20) tracker.sell(pd.Timestamp('2023-02-10'), 'XYZ', -100, price=15) tracker.buy(pd.Timestamp('2023-02-25'), 'XYZ', 100, price=18) tracker.sell(pd.Timestamp('2023-03-15'), 'XYZ', 100, price=25) display(tracker.capital_gains_df) print("Wash sales:\n"+tracker.washed_lots_str) print("\nClosed positions:\n"+tracker.closed_lots_str) ``` -------------------------------- ### Sell shares and display capital gains Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Sells shares and displays the current open lots and capital gains data frame. This example demonstrates selling shares more than a year after purchase. ```python second_sell_date = start_date + pd.DateOffset(days=367) tracker.sell(second_sell_date, 'ABC', shares_to_sell=10, price=8) print(tracker.open_lots_str) display(tracker.capital_gains_df) print("\nClosed position records:\n"+tracker.closed_lots_str) ``` -------------------------------- ### Capital Gains Calculation with LOFO Lot Selection Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Calculates capital gains using the LOFO (Lowest-In, First-Out) lot selection method. This example shows the results when prioritizing the lowest cost basis lots. ```python Config.LOT_SELECTION = TaxLotSelection.LOFO trade_sequence(CapGainsTracker()) ``` -------------------------------- ### Capital Gains Calculation with LIFO Lot Selection Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Calculates capital gains using the LIFO (Last-In, First-Out) lot selection method. This example demonstrates how different lot selection methods affect the results. ```python Config.LOT_SELECTION = TaxLotSelection.LIFO trade_sequence(CapGainsTracker()) ``` -------------------------------- ### Execute a second sell to realize more losses Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Executes a subsequent sell order to realize further capital losses. This example shows how multiple sell orders affect the capital gains and open positions. ```python # Sell 7 shares of ABC at $8, realizing capital loss of $2/share on 2 shares, but not washing the loss second_sell_date = start_date + pd.Timedelta(days=2) tracker.trade(second_sell_date, 'ABC', -7, price=8) display(tracker.capital_gains_df) print("Closed positions:\n"+tracker.closed_lots_str) print("\nOpen position:\n"+tracker.open_lots_str) ``` -------------------------------- ### PNLtracker.__init__ Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Initializes the PNLtracker with price data and optional distribution data. It allows for tracking trades and running end-of-day calculations. ```APIDOC ## `PNLtracker.__init__(price_data, distribution_data=None)` ### Description Initializes the PNLtracker with price data and optional distribution data. Requires a `price_data` DataFrame indexed by `[Ticker, Date]` with a `Px` column. ### Parameters - **price_data** (DataFrame) - Required - DataFrame indexed by `[Ticker, Date]` with a `Px` column. - **distribution_data** (DataFrame) - Optional - DataFrame containing distribution data. ### Request Example ```python import pandas as pd from tax_tracker import PNLtracker price_data = pd.DataFrame({ 'Ticker': ['XYZ'] * 5, 'Date': pd.date_range(start='2025-03-03', end='2025-03-07', freq='D'), 'Px': [15.0, 16.0, 17.0, 18.0, 19.0] }).set_index(['Ticker', 'Date']) tracker = PNLtracker(price_data) tracker.trade(pd.Timestamp('2025-03-03'), 'XYZ', 100, 15.1) tracker.trade(pd.Timestamp('2025-03-05'), 'XYZ', -50, 16.9) tracker.run_end_of_day(pd.Timestamp('2025-03-07')) print(tracker.daily_returns_df) ``` ### Response #### Success Response Initializes the PNLtracker object. #### Response Example ``` # PNL NAV Return LogReturn # 2025-03-04 ... ... ... ... # ... ``` ``` -------------------------------- ### current_date Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Property to get the current date. ```APIDOC ## current_date ### Description Property to get the current date. ### Returns - **Optional[pd.Timestamp]** - The current date. ``` -------------------------------- ### Inspect Lot Properties and Calculate Gains Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Demonstrates how to create a tax lot, access its properties, and calculate gains after closing the lot. Ensure the `CapGainsTracker` is initialized and trades are recorded. ```python from tax_tracker import CapGainsTracker import pandas as pd tracker = CapGainsTracker() new_lots, _ = tracker.trade(pd.Timestamp('2023-01-15'), 'XYZ', 100, price=20.00) lot = new_lots[0] print(lot.ticker) # 'XYZ' print(lot.shares) # 100 print(lot.open_date) # 2023-01-15 print(lot.open_px) # 20.0 print(lot.is_open) # True print(lot.basis_add) # 0.0 (adjusted if lot carries a washed loss) print(lot.basis_days) # 0 (adjusted holding period from prior wash) # Close the lot and inspect computed properties _, closed = tracker.trade(pd.Timestamp('2024-02-20'), 'XYZ', -100, price=25.00) closed_lot = closed[0] print(closed_lot.duration) # number of calendar days held print(closed_lot.term) # duration + basis_days (tax holding period) print(closed_lot.price_pnl) # 500.0 print(closed_lot.tax_gain) # 500.0 (price_pnl + basis_add) print(closed_lot.gain_type) # 'LT' — held > 366 days print(closed_lot.gain_type_verbose)# 'LongTerm' print(str(closed_lot)) # XYZ: 2023-01-15 100 @ $20.00 >> 2024-02-20 -100 @ $25.00 = $500.00 LT (401 days) ``` -------------------------------- ### last_date Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Property to get the last date on which activity was recorded. ```APIDOC ## last_date ### Description Property to get the last date on which activity was recorded. ### Returns - **Optional[pd.Timestamp]** - The last date on which we received activity. ``` -------------------------------- ### Get Closed Lots List Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves a list of all completed trades. ```APIDOC ## Get Closed Lots List ### Description Retrieves a list of all completed trades. ### Method GET ### Endpoint /tax_tracker/closed_lots_list ### Returns - **List[Lot]**: A list of all completed trades. ``` -------------------------------- ### Get Washed Lots String Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves a string summarizing wash sales. ```APIDOC ## Get Washed Lots String ### Description Retrieves a string summarizing wash sales. ### Method GET ### Endpoint /tax_tracker/washed_lots_str ### Returns - **string**: A string summarizing wash sales. ``` -------------------------------- ### Track Payments-in-Lieu for Shorted Stocks (Initial) Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Simulates shorting a stock and covering part of the position within 45 days, showing initial payments-in-lieu as short-term capital gains. Requires a DistributionTracker instance. ```python tracker = DistributionTracker(distribution_data) start_date = pd.Timestamp('2022-01-05') tracker.trade(start_date, 'NEA', shares=-2, price=11) cover_date = start_date + pd.Timedelta(days=45) tracker.trade(cover_date, 'NEA', 1, price=11) display(tracker.dividends_df) display(tracker.taxable_cashflows_df) ``` -------------------------------- ### Get Closed Lots String Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves a string summarizing all completed trades. ```APIDOC ## Get Closed Lots String ### Description Retrieves a string summarizing all completed trades. ### Method GET ### Endpoint /tax_tracker/closed_lots_str ### Returns - **string**: A string summarizing all completed trades. ``` -------------------------------- ### Get Current Date Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the most recent date for which Begin-of-Day (BoD) processing has been completed. ```APIDOC ## current_date ### Description Retrieves the most recent date for which Begin-of-Day (BoD) processing has been completed. ### Returns Optional[pd.Timestamp]: The most recent BoD date. ``` -------------------------------- ### DistributionTracker.__init__ Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Initializes the DistributionTracker with distribution data. The distribution data should be a pandas DataFrame indexed by [Ticker, Date, Type]. ```APIDOC ## `DistributionTracker.__init__(distribution_data)` ### Description Initializes the DistributionTracker with distribution data. The distribution data should be a pandas DataFrame indexed by [Ticker, Date, Type]. ### Parameters #### Request Body - **distribution_data** (pandas.DataFrame) - Required - DataFrame containing distribution information indexed by [Ticker, Date, Type]. ``` -------------------------------- ### Get Dividends DataFrame Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves a DataFrame containing daily distributions, with any columns that contain only zero values excluded. ```APIDOC ## dividends_df ### Description Return a DataFrame of daily distributions, excluding empty columns. ### Method Get (Property Access) ### Endpoint N/A (Python Property) ### Returns - pd.DataFrame: A DataFrame of daily distributions. ``` -------------------------------- ### Initialize Synthetic Stock Data Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Creates synthetic stock data for 'ABC' with preferred and normal dividends, setting up for qualification date calculations. Requires pandas. ```python # We'll buy stock on Jan 2, 2025. start_date = pd.Timestamp('2025-01-02') qualified_date = start_date + pd.Timedelta(days=91) print(f'Preferred qualified_date: {qualified_date}') syn_data = { 'Ticker': ['ABC', 'ABC'], 'Date': ['2025-02-01', '2025-02-01'], 'Type': ['P', 'N'], 'Distribution': [0.10, 0.05] } test_data = pd.DataFrame(syn_data) test_data['Date'] = pd.to_datetime(test_data['Date']) test_data.set_index(['Ticker', 'Date', 'Type'], inplace=True) test_data ``` -------------------------------- ### Get Daily Distributions (DataFrame, Cleaned) Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a DataFrame of daily distributions, excluding any columns that are entirely empty (all zeros). ```python return self.dividends_all_df ``` -------------------------------- ### Demonstrate Tax Lot Selection Methods Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Illustrates how different tax lot selection methods (FIFO, LIFO, HIFO, LOFO) affect capital gains and losses by simulating a trade sequence. ```python from tax_tracker import Config, TaxLotSelection, CapGainsTracker import pandas as pd def trade_sequence(tracker): tracker.trade(pd.Timestamp('2024-01-15'), 'XYZ', 50, price=10) tracker.trade(pd.Timestamp('2024-01-30'), 'XYZ', 50, price=20) tracker.trade(pd.Timestamp('2024-02-17'), 'XYZ', 50, price=16) tracker.trade(pd.Timestamp('2024-02-27'), 'XYZ', 50, price=12) tracker.trade(pd.Timestamp('2025-01-20'), 'XYZ', -50, price=15) tracker.trade(pd.Timestamp('2025-01-25'), 'XYZ', -50, price=15) tracker.trade(pd.Timestamp('2025-02-25'), 'XYZ', -100, price=15) return tracker.capital_gains_df # FIFO: sell oldest lots first Config.LOT_SELECTION = TaxLotSelection.FIFO cg = trade_sequence(CapGainsTracker()) print(cg.sum()) # ShortTerm -100 # LongTerm 200 # LIFO: sell newest lots first Config.LOT_SELECTION = TaxLotSelection.LIFO cg = trade_sequence(CapGainsTracker()) print(cg.sum()) # ShortTerm 100 # LongTerm 0 # HIFO: sell highest-cost lots first Config.LOT_SELECTION = TaxLotSelection.HIFO cg = trade_sequence(CapGainsTracker()) print(cg.sum()) # ShortTerm -150 # LongTerm 250 # LOFO: sell lowest-cost lots first Config.LOT_SELECTION = TaxLotSelection.LOFO cg = trade_sequence(CapGainsTracker()) print(cg.sum()) # ShortTerm 150 # LongTerm -50 ``` -------------------------------- ### Initialize PNLtracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Initializes the PNLtracker with price and optional distribution data. Requires price_data to be a DataFrame multi-indexed by Ticker and Date, with 'Px' and optionally 'AdjPx' columns. ```python def __init__(self, price_data: pd.DataFrame, distribution_data: Optional[pd.DataFrame] = None): """Initialize the PNLtracker. Args: price_data (pd.DataFrame): DataFrame multi-indexed by [Ticker, Date] with columns [Px, (AdjPx)] of type float. * `Px` is the split-adjusted closing price for the date. * Optional `AdjPx` is a closing price adjusted to reflect the total return. (If provided then `AdjPx` can be used to error-check P&L calculations.) distribution_data (Optional[pd.DataFrame]): DataFrame of distribution data. """ super().__init__(distribution_data) #region Prepare market data self.trading_dates: pd.DatetimeIndex = price_data.index.get_level_values('Date').unique().sort_values() # # Fill price forward on non-trading dates so we have entries for all calendar dates # self.data = price_data.groupby(level=0).apply(lambda x: x.reset_index(level=0, drop=True).asfreq("D")) # self.data = self.data.groupby('Ticker').ffill() self.data = price_data self.data['PrevPx'] = self.data.groupby(['Ticker'])['Px'].shift(1) #self.data['PxReturn'] = (self.data['Px'] / self.data['PrevPx']) - 1.0 #endregion self.warned_no_price: Set = set() #region Dictionaries indexed by date self.pnl: DefaultDict[pd.Timestamp, float] = defaultdict(float) self.nav: DefaultDict[pd.Timestamp, float] = defaultdict(float) self.opened_tickers_count: DefaultDict[pd.Timestamp, int] = defaultdict(int) self.closed_tickers_count: DefaultDict[pd.Timestamp, int] = defaultdict(int) #endregion Dictionaries indexed by date # Detailed activity indexed by date, ticker. Values as of EoD. PNL does not include Divs. self.detail: DefaultDict[pd.Timestamp, DefaultDict[str, dict]] = defaultdict(lambda: defaultdict(lambda: {'Lots': 0, 'SharesEoD': .0, 'SharesClosed': .0, 'Px': .0, 'PNL': .0, 'Divs': .0})) ``` -------------------------------- ### Get Closed Lots as List - Tax Tracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a list of all completed trades. Each item in the list is a Lot object. ```python @property def closed_lots_list(self) -> List[Lot]: """Return a list of all completed trades. Returns: List[Lot]: A list of all completed trades. """ return [trade for trades in self.complete_trades.values() for trade in trades] ``` -------------------------------- ### Get Last End of Day Timestamp Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the last date on which the tracker performed its end-of-day processing. Returns an optional Timestamp. ```python @property def last_end_of_day(self) -> Optional[pd.Timestamp]: return self._last_EoD ``` -------------------------------- ### Initialize DistributionTracker with Distribution Data Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Initialize the tracker with a DataFrame of distribution data. Ensure the DataFrame is indexed by Ticker, Date, and Type. Trades can be recorded before or after initialization. ```python import pandas as pd from tax_tracker import DistributionTracker distribution_data = pd.DataFrame({ 'Ticker': ['ABC', 'ABC', 'ABC'], 'Date': [pd.Timestamp('2023-02-01'), pd.Timestamp('2023-03-01'), pd.Timestamp('2023-03-15')], 'Type': ['D', 'R', 'P'], 'Distribution': [0.22, 0.15, 0.30] }).set_index(['Ticker', 'Date', 'Type']) tracker = DistributionTracker(distribution_data) tracker.trade(pd.Timestamp('2023-01-01'), 'ABC', 100, 150.0) # Advance to April 1 — runs BoD/EoD for every intervening day tracker.run_beginning_of_day(pd.Timestamp('2023-04-01')) print(tracker.dividends_df) # Shows Regular / Qualified / Exempt columns per ex-date ``` -------------------------------- ### Get Last End-of-Day Timestamp Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the timestamp of the last completed end-of-day run. Returns None if EoD has not yet been run. ```python @property def last_end_of_day(self) -> Optional[pd.Timestamp]: """Return the last date on which the tracker ran EoD. Returns: Optional[pd.Timestamp]: The last date on which the tracker ran EoD. """ return self._last_EoD ``` -------------------------------- ### Initialize PNLtracker and perform trades Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Initializes the PNLtracker with price data and demonstrates basic trade operations and end-of-day processing. Ensure price_data is a DataFrame indexed by [Ticker, Date] with a 'Px' column. ```python import pandas as pd from tax_tracker import PNLtracker price_data = pd.DataFrame({ 'Ticker': ['XYZ'] * 5, 'Date': pd.date_range(start='2025-03-03', end='2025-03-07', freq='D'), 'Px': [15.0, 16.0, 17.0, 18.0, 19.0] }).set_index(['Ticker', 'Date']) tracker = PNLtracker(price_data) tracker.trade(pd.Timestamp('2025-03-03'), 'XYZ', 100, 15.1) tracker.trade(pd.Timestamp('2025-03-05'), 'XYZ', -50, 16.9) tracker.run_end_of_day(pd.Timestamp('2025-03-07')) print(tracker.daily_returns_df) # PNL NAV Return LogReturn # 2025-03-04 ... ... ... ... # ... ``` -------------------------------- ### Get Closed Lots as String - Tax Tracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a string summarizing all completed trades. If no trades are found, it returns 'No trades'. ```python @property def closed_lots_str(self) -> str: """Return a string summarizing all completed trades. Returns: str: A string summarizing all completed trades. """ return '\n'.join([str(trade) for trades in self.complete_trades.values() for trade in trades] or ['No trades']) ``` -------------------------------- ### Get Daily Dividends DataFrame Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a DataFrame of daily distributions. It excludes columns where all values are zero. This is useful for analyzing active distribution days. ```python def dividends_df(self) -> pd.DataFrame: """Return a DataFrame of daily distributions, excluding empty columns. Returns: pd.DataFrame: A DataFrame of daily distributions. """ divs = self.dividends_all_df if len(divs) > 0: return divs.loc[:, (divs != 0).any()] return divs ``` -------------------------------- ### Initialize Tax Tracking Structures Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Initializes data structures for tracking tax-related information, including qualified shares, pending payments-in-lieu, and pending qualified dividends. ```python self.already_qualified: DefaultDict[Tuple[pd.Timestamp, str], float] = defaultdict(float) # Open shorts with payments-in-lieu (PIL) that have not yet qualified as interest expenses. # Indexed by Lot .open_date self.pending_pil_positions: DefaultDict[pd.Timestamp, List[Lot]] = defaultdict(list) # Dividends that can be qualified if/when holding period is satisfied. # Deque of (ex-date, Lot, $dividend/share) self.pending_qualified: Deque[Tuple[pd.Timestamp, Lot, float]] = deque() self.pending_qualified_preferred: Deque[Tuple[pd.Timestamp, Lot, float]] = deque() ``` -------------------------------- ### Track Payments-in-Lieu for Shorted Stocks (After 45 Days) Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates covering the remaining short position after 45 days, reclassifying payments-in-lieu from capital gains to expenses. Requires a DistributionTracker instance. ```python tracker.trade(cover_date+pd.Timedelta(days=1), 'NEA', 1, price=11) tracker.validate() print('Now covering the other share after more than 45 days, PIL account to Expenses:') display(tracker.dividends_df) display(tracker.taxable_cashflows_df) for divs in tracker.daily_dividends.values(): assert divs['PayInLieu'] == divs['Expense'], 'PIL correct' ``` -------------------------------- ### Execute a trade and display results Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Simulates a trade and displays the current open positions, capital gains, and closed lots. Use this to see the immediate impact of a trade. ```python date_out = second_short_date + pd.Timedelta(days=1) tracker.trade(date_out, 'ABC', 10, price=8.5) print("After final cover: "+tracker.open_lots_str) display(tracker.capital_gains_df) print("Trade record:\n"+tracker.closed_lots_str) ``` -------------------------------- ### Get Current Asset Position Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the current number of shares held for a given asset ticker. Returns 0.0 if the ticker is not found in the positions. ```python def get_position(self, ticker: str) -> float: if ticker not in self.positions: return .0 return sum(position.shares for position in self.positions[ticker]) ``` -------------------------------- ### Get End of Day Shares Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the number of shares held for a specific ticker at the end of a given day. Returns 0.0 if the date or ticker is not found. ```python if date not in self.holdings.index: return .0 if ticker not in self.holdings.columns: return .0 return self.holdings.loc[date, ticker] ``` -------------------------------- ### Get Washed Lots as String - Tax Tracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a string summarizing all wash sales. If no wash trades are found, it returns 'No washed trades'. ```python @property def washed_lots_str(self) -> str: """Return a string summarizing wash sales. Returns: str: A string summarizing wash sales. """ return '\n'.join([str(trade) for trade in self.wash_trades] or ['No washed trades']) ``` -------------------------------- ### Track Partial Wash Sales with CapGainsTracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates a scenario with multiple buy orders and subsequent partial sell orders that result in wash sales. Shows the closed and open positions after each validation step. ```python tracker = CapGainsTracker() tracker.buy(pd.Timestamp('2023-01-15'), 'XYZ', 60, price=20) tracker.buy(pd.Timestamp('2023-01-17'), 'XYZ', 50, price=21) tracker.buy(pd.Timestamp('2023-01-19'), 'XYZ', 40, price=22) tracker.sell(pd.Timestamp('2023-02-10'), 'XYZ', shares_to_sell=-20, price=16) tracker.validate() # print("\nOpen positions:\n"+tracker.Positions) tracker.sell(pd.Timestamp('2023-02-11'), 'XYZ', -20, price=15) # print("\nOpen positions:\n"+tracker.Positions) tracker.validate() #print("Wash sales:\n"+tracker.WashTrades) print("\nClosed positions:\n"+tracker.closed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) #print(f'\nTotal open shares = {tracker.get_position("XYZ")}') ``` -------------------------------- ### Get Prior Trading Date - PNLTracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the trading date immediately preceding the specified date. Returns None if no prior trading date exists in the data. ```python def prior_trading_date(self, date: pd.Timestamp) -> Optional[pd.Timestamp]: """Get the trading date before the specified date. Args: date (pd.Timestamp): The date for which to find the prior trading date. Returns: Optional[pd.Timestamp]: The trading date before the specified date, or None if there are no prior trading dates listed in data. """ try: return self.trading_dates[self.trading_dates < date][-1] except IndexError: return None ``` -------------------------------- ### Get Prior Trading Date Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the trading date immediately preceding a given date from a sorted list. Returns None if no prior trading dates are found. ```python def prior_trading_date(self, date: pd.Timestamp) -> Optional[pd.Timestamp]: """Get the trading date before the specified date. Args: date (pd.Timestamp): The date for which to find the prior trading date. Returns: Optional[pd.Timestamp]: The trading date before the specified date, or None if there are no prior trading dates listed in data. """ try: return self.trading_dates[self.trading_dates < date][-1] except IndexError: return None ``` -------------------------------- ### Track Dividends Before and After Qualification Date Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Initializes a DistributionTracker with synthetic data, buys stock, and tracks dividend qualification status before and on a specific qualification date. Requires pandas and DistributionTracker. ```python tracker = DistributionTracker(test_data) tracker.buy(start_date, 'ABC', shares=1, price=10) pre_qualification_date = qualified_date - pd.Timedelta(days=1) tracker.BoD(pre_qualification_date) print(f'Dividends as of {pre_qualification_date.date()}, just before qualification date:') display(tracker.dividends_df) tracker.BoD(qualified_date) tracker.validate() print('Dividends on qualification date:') display(tracker.dividends_df) ``` -------------------------------- ### Accessing daily PNL, NAV, and return DataFrames Source: https://context7.com/dbookstaber/tax_tracker/llms.txt Provides access to key reporting DataFrames: `daily_pnl_df`, `daily_nav_df`, and `daily_returns_df`. `daily_returns_df` calculates daily return and log-return using PNL and prior-day NAV. Ensure price data is correctly formatted. ```python import pandas as pd from tax_tracker import PNLtracker price_data = pd.DataFrame({ 'Ticker': ['XYZ'] * 5, 'Date': pd.date_range('2025-01-02', periods=5, freq='B'), 'Px': [100.0, 102.0, 101.0, 103.0, 105.0] }).set_index(['Ticker', 'Date']) tracker = PNLtracker(price_data) for date in tracker.trading_dates: tracker.BoD(date) if date == tracker.trading_dates[0]: tracker.buy(date, 'XYZ', 100) tracker.EoD(date) print(tracker.daily_pnl_df) # PNL # 2025-01-02 0.0 (bought at close, no prior-day position) # 2025-01-03 200.0 (100 shares * $2 gain) # ... print(tracker.daily_nav_df) # NAV # 2025-01-02 10000.0 # ... print(tracker.daily_returns_df) # PNL NAV Return LogReturn # 2025-01-03 200.0 10000.0 0.02 0.0198... # ... print(tracker.detail_df) # Multi-indexed by (Date, Ticker); columns: Lots, SharesEoD, SharesClosed, Px, PNL, Divs ``` -------------------------------- ### Run Beginning of Day Processing Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Initializes daily processing, ensuring holdings are updated and distributions are accounted for. It handles skipped days and calls EoD for the previous day if necessary. ```python def run_beginning_of_day(self, date: pandas._libs.tslibs.timestamps.Timestamp) -> Dict[str, float]: ``` -------------------------------- ### Get Asset-Level Detail DataFrame Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Constructs and returns a DataFrame of asset-level detail values, indexed by Date and Ticker. Requires data to be stored in a specific nested dictionary format. ```python data = [ {'Date': date, 'Ticker': ticker, **details} for date, tickers in self.detail.items() for ticker, details in tickers.items() ] df = pd.DataFrame(data) df.set_index(['Date', 'Ticker'], inplace=True) return df ``` -------------------------------- ### Get Daily NAV DataFrame Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a pandas DataFrame containing the daily Net Asset Value (NAV) for each recorded date. The DataFrame has a single column named 'NAV'. ```python @property def daily_nav_df(self) -> pd.DataFrame: """Return a DataFrame of daily NAV. Returns: pd.DataFrame: A DataFrame of daily NAV. """ return pd.DataFrame.from_dict(self.nav, orient='index', columns=['NAV']) ``` -------------------------------- ### Get Next Trading Date Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Retrieves the next trading date after a given date from a sorted list of trading dates. Returns None if no future trading dates exist. ```python def next_trading_date(self, date: pd.Timestamp) -> Optional[pd.Timestamp]: """Get the next trading date after the specified date. Args: date (pd.Timestamp): The date for which to find the next trading date. Returns: Optional[pd.Timestamp]: The next trading date after the specified date, or None if there are no more trading dates. """ try: return self.trading_dates[self.trading_dates > date][0] except IndexError: return None ``` -------------------------------- ### Run Tracker Until Specific Date Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Initializes and runs the tracker up to a specified date, then validates and displays dividend data. ```python tracker.BoD(pd.Timestamp('2022-07-01')) tracker.validate() tracker.dividends_df[['Regular', 'Qualified']] ``` -------------------------------- ### Track Full Wash Sale with CapGainsTracker Source: https://github.com/dbookstaber/tax_tracker/blob/main/Examples.ipynb Demonstrates a full wash sale scenario where a sell order closes a prior buy order, resulting in a wash. Shows the closed and open positions after the transaction. ```python tracker = CapGainsTracker() tracker.buy(pd.Timestamp('2023-01-15'), 'XYZ', 100, price=20) tracker.buy(pd.Timestamp('2023-01-16'), 'XYZ', 100, price=20) tracker.sell(pd.Timestamp('2023-02-10'), 'XYZ', -100, price=15) tracker.validate() print("\nClosed positions:\n"+tracker.closed_lots_str) print("\nOpen positions:\n"+tracker.open_lots_str) ``` -------------------------------- ### Get Taxable Cash Flows DataFrame (Filtered) Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a DataFrame of daily capital gains and distributions, excluding rows and columns that contain only zero values. This is derived from `taxable_cashflows_all_df`. ```python df = self.taxable_cashflows_all_df return df.loc[:, (df != 0).any(axis=0) # Drop zero columns ].loc[(df != 0).any(axis=1), :] ``` -------------------------------- ### Get Daily P&L DataFrame Source: https://github.com/dbookstaber/tax_tracker/blob/main/docs/tax_tracker.html Returns a pandas DataFrame containing the daily Profit & Loss (P&L) for each recorded date. The DataFrame has a single column named 'PNL'. ```python @property def daily_pnl_df(self) -> pd.DataFrame: """Return a DataFrame of daily P&L. Returns: pd.DataFrame: A DataFrame of daily P&L. """ return pd.DataFrame.from_dict(self.pnl, orient='index', columns=['PNL']) ```