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.

This commit is contained in:
padreug 2025-07-05 15:06:32 +02:00
parent e222922a6a
commit 1943da1fc3

View file

@ -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: