import csv from datetime import datetime # Place your downloaded CoinSpot CSV file name here CSV_FILE = 'coinspot_transactions.csv' def calculate_crypto_cgt(csv_filepath): # Queues to hold purchased coins for FIFO tracking: { 'BTC': [{'amount': 0.01, 'cost_base': 500, 'date': datetime}], ... } buy_inventory = {} total_short_term_gains = 0 total_long_term_gains = 0 total_losses = 0 with open(csv_filepath, mode='r', encoding='utf-8-sig') as f: reader = csv.DictReader(f) # Sort transactions chronologically (oldest first) transactions = sorted(list(reader), key=lambda x: datetime.strptime(x['DATE'], '%d/%m/%Y %I:%M %p')) for row in transactions: coin = row['COIN'].upper() tx_type = row['TYPE'].lower() amount = float(row['AMOUNT']) fee = float(row['FEE (AUD)']) total_aud = float(row['TOTAL (AUD)']) date = datetime.strptime(row['DATE'], '%d/%m/%Y %I:%M %p') if coin not in buy_inventory: buy_inventory[coin] = [] # 1. PROCESS BUYS if 'buy' in tx_type: # Cost base includes the purchase amount + the fee cost_base = total_aud + fee buy_inventory[coin].append({ 'amount': amount, 'cost_per_unit': cost_base / amount, 'date': date }) # 2. PROCESS SELLS (CGT Events) elif 'sell' in tx_type: # Proceeds are total received minus the sell fee proceeds = total_aud - fee price_per_unit_sold = proceeds / amount remaining_sell_amount = amount while remaining_sell_amount > 0: if not buy_inventory[coin]: print(f"Warning: Sold {coin} on {date} without a matching buy record!") break oldest_buy = buy_inventory[coin][0] # Determine how much of this buy batch we are exhausting amount_to_match = min(remaining_sell_amount, oldest_buy['amount']) # Calculate proportional cost and proceeds for this matched chunk matched_cost = amount_to_match * oldest_buy['cost_per_unit'] matched_proceeds = amount_to_match * price_per_unit_sold gain_loss = matched_proceeds - matched_cost # Check if held for > 12 months for the 50% ATO discount days_held = (date - oldest_buy['date']).days is_long_term = days_held >= 365 if gain_loss > 0: if is_long_term: total_long_term_gains += gain_loss else: total_short_term_gains += gain_loss else: total_losses += abs(gain_loss) # Losses are kept absolute # Update remaining amounts remaining_sell_amount -= amount_to_match oldest_buy['amount'] -= amount_to_match # If this buy batch is fully used up, remove it from queue if oldest_buy['amount'] <= 0: buy_inventory[coin].pop(0) # Final ATO Report Outputs net_gain_before_discount = total_short_term_gains + total_long_term_gains - total_losses taxable_long_term = total_long_term_gains * 0.5 if total_long_term_gains > 0 else 0 net_capital_gain = max(0, total_short_term_gains + taxable_long_term - (total_losses if total_losses < total_short_term_gains else total_short_term_gains)) print("------ ATO CRYPTO CGT REPORT ------") print(f"Total Short-Term Capital Gains: ${total_short_term_gains:.2f}") print(f"Total Long-Term Capital Gains (Pre-Discount): ${total_long_term_gains:.2f}") print(f"Total Capital Losses: ${total_losses:.2f}") print("-----------------------------------") print(f"Net Capital Gain to declare on Tax Return: ${net_capital_gain:.2f}") # To run: ensure your CoinSpot CSV file matches the name and column titles. # calculate_crypto_cgt(CSV_FILE)

Comments :