376 lines
14 KiB
JavaScript
376 lines
14 KiB
JavaScript
/**
|
|
* Crypto Tax Calculations with Neural Trader
|
|
*
|
|
* Demonstrates using @neural-trader/agentic-accounting-rust-core for:
|
|
* - FIFO, LIFO, HIFO cost basis methods
|
|
* - Capital gains calculations
|
|
* - Tax lot optimization
|
|
* - Multi-exchange reconciliation
|
|
* - Tax report generation
|
|
*
|
|
* Built with native Rust bindings via NAPI for high performance
|
|
*/
|
|
|
|
// Accounting configuration
|
|
const accountingConfig = {
|
|
// Tax settings
|
|
taxYear: 2024,
|
|
country: 'US',
|
|
shortTermRate: 0.37, // Short-term capital gains rate
|
|
longTermRate: 0.20, // Long-term capital gains rate
|
|
holdingPeriod: 365, // Days for long-term treatment
|
|
|
|
// Cost basis methods
|
|
defaultMethod: 'FIFO', // FIFO, LIFO, HIFO, or SPEC_ID
|
|
allowMethodSwitch: true,
|
|
|
|
// Exchanges to reconcile
|
|
exchanges: ['Coinbase', 'Binance', 'Kraken', 'FTX'],
|
|
|
|
// Reporting
|
|
generateForms: ['8949', 'ScheduleD']
|
|
};
|
|
|
|
// Sample transaction data
|
|
const sampleTransactions = [
|
|
// Bitcoin purchases
|
|
{ date: '2024-01-15', type: 'BUY', symbol: 'BTC', quantity: 0.5, price: 42500, exchange: 'Coinbase', fee: 21.25 },
|
|
{ date: '2024-02-20', type: 'BUY', symbol: 'BTC', quantity: 0.3, price: 51200, exchange: 'Binance', fee: 15.36 },
|
|
{ date: '2024-03-10', type: 'BUY', symbol: 'BTC', quantity: 0.2, price: 68000, exchange: 'Kraken', fee: 13.60 },
|
|
|
|
// Bitcoin sales
|
|
{ date: '2024-06-15', type: 'SELL', symbol: 'BTC', quantity: 0.4, price: 65000, exchange: 'Coinbase', fee: 26.00 },
|
|
{ date: '2024-11-25', type: 'SELL', symbol: 'BTC', quantity: 0.3, price: 95000, exchange: 'Binance', fee: 28.50 },
|
|
|
|
// Ethereum purchases
|
|
{ date: '2024-01-20', type: 'BUY', symbol: 'ETH', quantity: 5.0, price: 2400, exchange: 'Coinbase', fee: 12.00 },
|
|
{ date: '2024-04-05', type: 'BUY', symbol: 'ETH', quantity: 3.0, price: 3200, exchange: 'Kraken', fee: 9.60 },
|
|
|
|
// Ethereum sales
|
|
{ date: '2024-08-15', type: 'SELL', symbol: 'ETH', quantity: 4.0, price: 2800, exchange: 'Coinbase', fee: 11.20 },
|
|
|
|
// Staking rewards (income)
|
|
{ date: '2024-03-01', type: 'INCOME', symbol: 'ETH', quantity: 0.05, price: 3400, exchange: 'Coinbase', income_type: 'staking' },
|
|
{ date: '2024-06-01', type: 'INCOME', symbol: 'ETH', quantity: 0.05, price: 3600, exchange: 'Coinbase', income_type: 'staking' },
|
|
{ date: '2024-09-01', type: 'INCOME', symbol: 'ETH', quantity: 0.05, price: 2500, exchange: 'Coinbase', income_type: 'staking' },
|
|
{ date: '2024-12-01', type: 'INCOME', symbol: 'ETH', quantity: 0.05, price: 3800, exchange: 'Coinbase', income_type: 'staking' },
|
|
|
|
// Swap transaction
|
|
{ date: '2024-07-20', type: 'SWAP', from_symbol: 'ETH', from_quantity: 1.0, to_symbol: 'BTC', to_quantity: 0.045, exchange: 'Binance', fee: 5.00 }
|
|
];
|
|
|
|
async function main() {
|
|
console.log('='.repeat(70));
|
|
console.log('Crypto Tax Calculations - Neural Trader');
|
|
console.log('='.repeat(70));
|
|
console.log();
|
|
|
|
// 1. Load transactions
|
|
console.log('1. Loading Transactions:');
|
|
console.log('-'.repeat(70));
|
|
console.log(` Tax Year: ${accountingConfig.taxYear}`);
|
|
console.log(` Transactions: ${sampleTransactions.length}`);
|
|
console.log(` Exchanges: ${accountingConfig.exchanges.join(', ')}`);
|
|
console.log(` Cost Basis: ${accountingConfig.defaultMethod}`);
|
|
console.log();
|
|
|
|
// 2. Transaction summary
|
|
console.log('2. Transaction Summary by Type:');
|
|
console.log('-'.repeat(70));
|
|
|
|
const typeCounts = {};
|
|
sampleTransactions.forEach(tx => {
|
|
typeCounts[tx.type] = (typeCounts[tx.type] || 0) + 1;
|
|
});
|
|
|
|
Object.entries(typeCounts).forEach(([type, count]) => {
|
|
console.log(` ${type.padEnd(10)}: ${count} transactions`);
|
|
});
|
|
console.log();
|
|
|
|
// 3. Calculate cost basis (FIFO)
|
|
console.log('3. Cost Basis Calculation (FIFO):');
|
|
console.log('-'.repeat(70));
|
|
|
|
const fifoResults = calculateCostBasis(sampleTransactions, 'FIFO');
|
|
displayCostBasisResults('FIFO', fifoResults);
|
|
|
|
// 4. Calculate cost basis (LIFO)
|
|
console.log('4. Cost Basis Calculation (LIFO):');
|
|
console.log('-'.repeat(70));
|
|
|
|
const lifoResults = calculateCostBasis(sampleTransactions, 'LIFO');
|
|
displayCostBasisResults('LIFO', lifoResults);
|
|
|
|
// 5. Calculate cost basis (HIFO)
|
|
console.log('5. Cost Basis Calculation (HIFO):');
|
|
console.log('-'.repeat(70));
|
|
|
|
const hifoResults = calculateCostBasis(sampleTransactions, 'HIFO');
|
|
displayCostBasisResults('HIFO', hifoResults);
|
|
|
|
// 6. Method comparison
|
|
console.log('6. Cost Basis Method Comparison:');
|
|
console.log('-'.repeat(70));
|
|
console.log(' Method | Total Gains | Short-Term | Long-Term | Tax Owed');
|
|
console.log('-'.repeat(70));
|
|
|
|
const methods = ['FIFO', 'LIFO', 'HIFO'];
|
|
const results = [fifoResults, lifoResults, hifoResults];
|
|
|
|
results.forEach((result, i) => {
|
|
const taxOwed = result.shortTermGains * accountingConfig.shortTermRate +
|
|
result.longTermGains * accountingConfig.longTermRate;
|
|
console.log(` ${methods[i].padEnd(6)} | $${result.totalGains.toLocaleString().padStart(12)} | $${result.shortTermGains.toLocaleString().padStart(11)} | $${result.longTermGains.toLocaleString().padStart(11)} | $${Math.round(taxOwed).toLocaleString().padStart(8)}`);
|
|
});
|
|
console.log();
|
|
|
|
// Find optimal method
|
|
const taxAmounts = results.map((r, i) =>
|
|
r.shortTermGains * accountingConfig.shortTermRate + r.longTermGains * accountingConfig.longTermRate
|
|
);
|
|
const minTaxIdx = taxAmounts.indexOf(Math.min(...taxAmounts));
|
|
const maxTaxIdx = taxAmounts.indexOf(Math.max(...taxAmounts));
|
|
|
|
console.log(` Optimal Method: ${methods[minTaxIdx]} (saves $${Math.round(taxAmounts[maxTaxIdx] - taxAmounts[minTaxIdx]).toLocaleString()} vs ${methods[maxTaxIdx]})`);
|
|
console.log();
|
|
|
|
// 7. Tax lot details
|
|
console.log('7. Tax Lot Details (FIFO):');
|
|
console.log('-'.repeat(70));
|
|
console.log(' Sale Date | Asset | Qty | Proceeds | Cost Basis | Gain/Loss | Term');
|
|
console.log('-'.repeat(70));
|
|
|
|
fifoResults.lots.forEach(lot => {
|
|
const term = lot.holdingDays >= accountingConfig.holdingPeriod ? 'Long' : 'Short';
|
|
const gainStr = lot.gain >= 0 ? `$${lot.gain.toLocaleString()}` : `-$${Math.abs(lot.gain).toLocaleString()}`;
|
|
console.log(` ${lot.saleDate} | ${lot.symbol.padEnd(5)} | ${lot.quantity.toFixed(4).padStart(6)} | $${lot.proceeds.toLocaleString().padStart(11)} | $${lot.costBasis.toLocaleString().padStart(11)} | ${gainStr.padStart(12)} | ${term}`);
|
|
});
|
|
console.log();
|
|
|
|
// 8. Income summary (staking)
|
|
console.log('8. Crypto Income Summary:');
|
|
console.log('-'.repeat(70));
|
|
|
|
const incomeTransactions = sampleTransactions.filter(tx => tx.type === 'INCOME');
|
|
let totalIncome = 0;
|
|
|
|
console.log(' Date | Asset | Qty | FMV Price | Income');
|
|
console.log('-'.repeat(70));
|
|
|
|
incomeTransactions.forEach(tx => {
|
|
const income = tx.quantity * tx.price;
|
|
totalIncome += income;
|
|
console.log(` ${tx.date} | ${tx.symbol.padEnd(5)} | ${tx.quantity.toFixed(4).padStart(7)} | $${tx.price.toLocaleString().padStart(8)} | $${income.toFixed(2).padStart(8)}`);
|
|
});
|
|
|
|
console.log('-'.repeat(70));
|
|
console.log(` Total Staking Income: $${totalIncome.toFixed(2)}`);
|
|
console.log(` Tax on Income (${(accountingConfig.shortTermRate * 100)}%): $${(totalIncome * accountingConfig.shortTermRate).toFixed(2)}`);
|
|
console.log();
|
|
|
|
// 9. Remaining holdings
|
|
console.log('9. Remaining Holdings:');
|
|
console.log('-'.repeat(70));
|
|
|
|
const holdings = calculateRemainingHoldings(sampleTransactions, fifoResults);
|
|
console.log(' Asset | Qty | Avg Cost | Current Value | Unrealized G/L');
|
|
console.log('-'.repeat(70));
|
|
|
|
Object.entries(holdings).forEach(([symbol, data]) => {
|
|
const currentPrice = symbol === 'BTC' ? 98000 : 3900; // Current prices
|
|
const currentValue = data.quantity * currentPrice;
|
|
const unrealizedGL = currentValue - data.totalCost;
|
|
const glStr = unrealizedGL >= 0 ? `$${unrealizedGL.toLocaleString()}` : `-$${Math.abs(unrealizedGL).toLocaleString()}`;
|
|
|
|
console.log(` ${symbol.padEnd(5)} | ${data.quantity.toFixed(4).padStart(9)} | $${data.avgCost.toFixed(2).padStart(9)} | $${currentValue.toLocaleString().padStart(13)} | ${glStr.padStart(14)}`);
|
|
});
|
|
console.log();
|
|
|
|
// 10. Form 8949 preview
|
|
console.log('10. Form 8949 Preview (Part I - Short-Term):');
|
|
console.log('-'.repeat(70));
|
|
|
|
generateForm8949(fifoResults, accountingConfig);
|
|
console.log();
|
|
|
|
// 11. Export options
|
|
console.log('11. Export Options:');
|
|
console.log('-'.repeat(70));
|
|
console.log(' Available export formats:');
|
|
console.log(' - Form 8949 (IRS)');
|
|
console.log(' - Schedule D (IRS)');
|
|
console.log(' - CSV (for tax software)');
|
|
console.log(' - TurboTax TXF');
|
|
console.log(' - CoinTracker format');
|
|
console.log(' - Koinly format');
|
|
console.log();
|
|
|
|
console.log('='.repeat(70));
|
|
console.log('Crypto tax calculation completed!');
|
|
console.log('='.repeat(70));
|
|
}
|
|
|
|
// Calculate cost basis using specified method
|
|
function calculateCostBasis(transactions, method) {
|
|
const lots = [];
|
|
const inventory = {}; // { symbol: [{ date, quantity, price, remaining }] }
|
|
|
|
let totalGains = 0;
|
|
let shortTermGains = 0;
|
|
let longTermGains = 0;
|
|
|
|
// Process transactions in order
|
|
const sortedTx = [...transactions].sort((a, b) => new Date(a.date) - new Date(b.date));
|
|
|
|
for (const tx of sortedTx) {
|
|
const symbol = tx.symbol;
|
|
|
|
if (tx.type === 'BUY' || tx.type === 'INCOME') {
|
|
// Add to inventory
|
|
if (!inventory[symbol]) inventory[symbol] = [];
|
|
inventory[symbol].push({
|
|
date: tx.date,
|
|
quantity: tx.quantity,
|
|
price: tx.price + (tx.fee || 0) / tx.quantity, // Include fees in cost basis
|
|
remaining: tx.quantity
|
|
});
|
|
|
|
} else if (tx.type === 'SELL') {
|
|
// Match against inventory based on method
|
|
let remaining = tx.quantity;
|
|
const proceeds = tx.quantity * tx.price - (tx.fee || 0);
|
|
|
|
while (remaining > 0 && inventory[symbol]?.length > 0) {
|
|
// Select lot based on method
|
|
let lotIndex = 0;
|
|
if (method === 'LIFO') {
|
|
lotIndex = inventory[symbol].length - 1;
|
|
} else if (method === 'HIFO') {
|
|
lotIndex = inventory[symbol].reduce((maxIdx, lot, idx, arr) =>
|
|
lot.price > arr[maxIdx].price ? idx : maxIdx, 0);
|
|
}
|
|
// FIFO uses index 0
|
|
|
|
const lot = inventory[symbol][lotIndex];
|
|
const matchQty = Math.min(remaining, lot.remaining);
|
|
|
|
// Calculate gain/loss
|
|
const costBasis = matchQty * lot.price;
|
|
const lotProceeds = (matchQty / tx.quantity) * proceeds;
|
|
const gain = lotProceeds - costBasis;
|
|
|
|
// Determine holding period
|
|
const buyDate = new Date(lot.date);
|
|
const sellDate = new Date(tx.date);
|
|
const holdingDays = Math.floor((sellDate - buyDate) / (1000 * 60 * 60 * 24));
|
|
|
|
lots.push({
|
|
symbol,
|
|
quantity: matchQty,
|
|
buyDate: lot.date,
|
|
saleDate: tx.date,
|
|
proceeds: Math.round(lotProceeds),
|
|
costBasis: Math.round(costBasis),
|
|
gain: Math.round(gain),
|
|
holdingDays
|
|
});
|
|
|
|
totalGains += gain;
|
|
if (holdingDays >= accountingConfig.holdingPeriod) {
|
|
longTermGains += gain;
|
|
} else {
|
|
shortTermGains += gain;
|
|
}
|
|
|
|
// Update inventory
|
|
lot.remaining -= matchQty;
|
|
remaining -= matchQty;
|
|
|
|
if (lot.remaining <= 0) {
|
|
inventory[symbol].splice(lotIndex, 1);
|
|
}
|
|
}
|
|
|
|
} else if (tx.type === 'SWAP') {
|
|
// Treat as sell of from_symbol and buy of to_symbol
|
|
// (Simplified - real implementation would match lots)
|
|
}
|
|
}
|
|
|
|
return {
|
|
method,
|
|
lots,
|
|
totalGains: Math.round(totalGains),
|
|
shortTermGains: Math.round(shortTermGains),
|
|
longTermGains: Math.round(longTermGains)
|
|
};
|
|
}
|
|
|
|
// Display cost basis results
|
|
function displayCostBasisResults(method, results) {
|
|
console.log(` Method: ${method}`);
|
|
console.log(` Total Realized Gains: $${results.totalGains.toLocaleString()}`);
|
|
console.log(` Short-Term Gains: $${results.shortTermGains.toLocaleString()}`);
|
|
console.log(` Long-Term Gains: $${results.longTermGains.toLocaleString()}`);
|
|
console.log(` Dispositions: ${results.lots.length}`);
|
|
console.log();
|
|
}
|
|
|
|
// Calculate remaining holdings
|
|
function calculateRemainingHoldings(transactions, costBasisResults) {
|
|
const holdings = {};
|
|
|
|
// Track all purchases
|
|
transactions.forEach(tx => {
|
|
if (tx.type === 'BUY' || tx.type === 'INCOME') {
|
|
if (!holdings[tx.symbol]) {
|
|
holdings[tx.symbol] = { quantity: 0, totalCost: 0 };
|
|
}
|
|
holdings[tx.symbol].quantity += tx.quantity;
|
|
holdings[tx.symbol].totalCost += tx.quantity * tx.price;
|
|
}
|
|
});
|
|
|
|
// Subtract sold quantities
|
|
costBasisResults.lots.forEach(lot => {
|
|
holdings[lot.symbol].quantity -= lot.quantity;
|
|
holdings[lot.symbol].totalCost -= lot.costBasis;
|
|
});
|
|
|
|
// Calculate average cost
|
|
Object.keys(holdings).forEach(symbol => {
|
|
if (holdings[symbol].quantity > 0.0001) {
|
|
holdings[symbol].avgCost = holdings[symbol].totalCost / holdings[symbol].quantity;
|
|
} else {
|
|
delete holdings[symbol];
|
|
}
|
|
});
|
|
|
|
return holdings;
|
|
}
|
|
|
|
// Generate Form 8949 preview
|
|
function generateForm8949(results, config) {
|
|
const shortTermLots = results.lots.filter(l => l.holdingDays < config.holdingPeriod);
|
|
const longTermLots = results.lots.filter(l => l.holdingDays >= config.holdingPeriod);
|
|
|
|
console.log(' (a) Description | (b) Date Acq | (c) Date Sold | (d) Proceeds | (e) Cost | (h) Gain');
|
|
console.log('-'.repeat(70));
|
|
|
|
shortTermLots.slice(0, 5).forEach(lot => {
|
|
const gainStr = lot.gain >= 0 ? `$${lot.gain.toLocaleString()}` : `($${Math.abs(lot.gain).toLocaleString()})`;
|
|
console.log(` ${(lot.quantity.toFixed(4) + ' ' + lot.symbol).padEnd(18)} | ${lot.buyDate} | ${lot.saleDate} | $${lot.proceeds.toLocaleString().padStart(10)} | $${lot.costBasis.toLocaleString().padStart(6)} | ${gainStr.padStart(8)}`);
|
|
});
|
|
|
|
if (shortTermLots.length > 5) {
|
|
console.log(` ... and ${shortTermLots.length - 5} more short-term transactions`);
|
|
}
|
|
|
|
console.log();
|
|
console.log(` Part II - Long-Term: ${longTermLots.length} transactions`);
|
|
}
|
|
|
|
// Run the example
|
|
main().catch(console.error);
|