From 1943da1fc3bd9f13b1ba1c2a1f4d4b74d328b2ff Mon Sep 17 00:00:00 2001 From: padreug Date: Sat, 5 Jul 2025 15:06:32 +0200 Subject: [PATCH] Enhance DCA distribution logic: Improved the proportional distribution algorithm to include precise calculations with banker's rounding and handle remainder allocation fairly among clients. Added verification to ensure total distributed amount matches the base allocation, enhancing accuracy and reliability in transaction processing. --- transaction_processor.py | 54 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/transaction_processor.py b/transaction_processor.py index 6fc5647..7e161f7 100644 --- a/transaction_processor.py +++ b/transaction_processor.py @@ -704,15 +704,56 @@ class LamassuTransactionProcessor: logger.info("No clients with remaining DCA balance - skipping distribution") return {} - # Calculate proportional distribution + # Calculate proportional distribution with remainder allocation distributions = {} + distributed_sats = 0 + client_calculations = [] + # First pass: calculate base amounts and track remainders for client_id, client_balance in client_balances.items(): # Calculate this client's proportion of the total DCA pool proportion = client_balance / total_confirmed_deposits - # Calculate client's share of the base crypto (after commission) - client_sats_amount = int(base_crypto_atoms * proportion) + # Calculate exact share (with decimals) + exact_share = base_crypto_atoms * proportion + + # Use banker's rounding for base allocation + client_sats_amount = round(exact_share) + + client_calculations.append({ + 'client_id': client_id, + 'proportion': proportion, + 'exact_share': exact_share, + 'allocated_sats': client_sats_amount, + 'client_balance': client_balance + }) + + distributed_sats += client_sats_amount + + # Handle any remainder due to rounding (should be small) + remainder = base_crypto_atoms - distributed_sats + + if remainder != 0: + logger.info(f"Distributing remainder: {remainder} sats among {len(client_calculations)} clients") + + # Sort clients by largest fractional remainder to distribute fairly + client_calculations.sort( + key=lambda x: x['exact_share'] - x['allocated_sats'], + reverse=True + ) + + # Distribute remainder one sat at a time to clients with largest fractional parts + for i in range(abs(remainder)): + if remainder > 0: + client_calculations[i % len(client_calculations)]['allocated_sats'] += 1 + else: + client_calculations[i % len(client_calculations)]['allocated_sats'] -= 1 + + # Second pass: create distributions with final amounts + for calc in client_calculations: + client_id = calc['client_id'] + client_sats_amount = calc['allocated_sats'] + proportion = calc['proportion'] # Calculate equivalent fiat value in centavos for tracking purposes (industry standard) # Store as centavos to maintain precision and avoid floating-point errors @@ -726,6 +767,13 @@ class LamassuTransactionProcessor: logger.info(f"Client {client_id[:8]}... gets {client_sats_amount} sats (≈{client_fiat_amount/100:.2f} GTQ, {proportion:.2%} share)") + # Verification: ensure total distribution equals base amount + total_distributed = sum(dist["sats_amount"] for dist in distributions.values()) + if total_distributed != base_crypto_atoms: + logger.error(f"Distribution mismatch! Expected: {base_crypto_atoms} sats, Distributed: {total_distributed} sats") + raise ValueError(f"Satoshi distribution calculation error: {base_crypto_atoms} != {total_distributed}") + + logger.info(f"Distribution verified: {total_distributed} sats distributed across {len(distributions)} clients") return distributions except Exception as e: