Better logic on fund splitting
This commit is contained in:
@@ -4,4 +4,6 @@ KRAKEN_API_KEY=your_api_key_here
|
|||||||
KRAKEN_API_SECRET=your_api_secret_here
|
KRAKEN_API_SECRET=your_api_secret_here
|
||||||
|
|
||||||
# Starting balance for paper trading simulation
|
# Starting balance for paper trading simulation
|
||||||
|
# Needs to be at least (max_positions * min_order_usd) to fill all slots.
|
||||||
|
# Default config: 5 positions * $15 min = $75 minimum, $500+ recommended.
|
||||||
PAPER_BALANCE_USD=1000
|
PAPER_BALANCE_USD=1000
|
||||||
|
|||||||
34
bot.py
34
bot.py
@@ -45,9 +45,11 @@ def setup_logging(log_file: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def available_usd(client: KrakenClient, config: Config) -> float:
|
def available_usd(client: KrakenClient, config: Config, portfolio: Portfolio) -> float:
|
||||||
if config.paper_trading:
|
if config.paper_trading:
|
||||||
return float(os.getenv("PAPER_BALANCE_USD", "1000"))
|
total = float(os.getenv("PAPER_BALANCE_USD", "1000"))
|
||||||
|
invested = sum(pos.cost_usd for pos in portfolio.all())
|
||||||
|
return max(0.0, total - invested)
|
||||||
try:
|
try:
|
||||||
return client.get_usd_balance()
|
return client.get_usd_balance()
|
||||||
except KrakenError as exc:
|
except KrakenError as exc:
|
||||||
@@ -129,7 +131,7 @@ def run_cycle(config: Config) -> bool:
|
|||||||
log.info("Portfolio full — no new positions to open.")
|
log.info("Portfolio full — no new positions to open.")
|
||||||
return bool(closed)
|
return bool(closed)
|
||||||
|
|
||||||
balance = available_usd(client, config)
|
balance = available_usd(client, config, portfolio)
|
||||||
log.info("Available balance: $%.2f | Open slots: %d", balance, open_slots)
|
log.info("Available balance: $%.2f | Open slots: %d", balance, open_slots)
|
||||||
|
|
||||||
if balance < config.min_order_usd:
|
if balance < config.min_order_usd:
|
||||||
@@ -146,22 +148,26 @@ def run_cycle(config: Config) -> bool:
|
|||||||
log.info("No opportunities matching criteria this cycle.")
|
log.info("No opportunities matching criteria this cycle.")
|
||||||
return bool(closed)
|
return bool(closed)
|
||||||
|
|
||||||
# Cap to available slots and allocate equally
|
# Sort by 24h change descending — strongest movers get priority when funds are limited
|
||||||
to_buy = opportunities[:open_slots]
|
opportunities.sort(key=lambda o: o.change_pct, reverse=True)
|
||||||
alloc_per_asset = balance / len(to_buy)
|
|
||||||
|
# How many can we actually afford at the minimum order size?
|
||||||
|
affordable = int(balance // config.min_order_usd)
|
||||||
|
num_to_buy = min(open_slots, affordable, len(opportunities))
|
||||||
|
|
||||||
|
if num_to_buy == 0:
|
||||||
|
log.info("Balance $%.2f too low for even one minimum order ($%.2f).", balance, config.min_order_usd)
|
||||||
|
return bool(closed)
|
||||||
|
|
||||||
|
to_buy = opportunities[:num_to_buy]
|
||||||
|
# Split available balance equally across the chosen assets (always >= min_order_usd)
|
||||||
|
alloc_per_asset = balance / num_to_buy
|
||||||
log.info(
|
log.info(
|
||||||
"Buying %d asset(s) @ $%.2f each (total $%.2f)",
|
"Buying %d asset(s) @ $%.2f each (total $%.2f)",
|
||||||
len(to_buy), alloc_per_asset, alloc_per_asset * len(to_buy),
|
num_to_buy, alloc_per_asset, alloc_per_asset * num_to_buy,
|
||||||
)
|
)
|
||||||
|
|
||||||
for opp in to_buy:
|
for opp in to_buy:
|
||||||
if alloc_per_asset < config.min_order_usd:
|
|
||||||
log.warning(
|
|
||||||
"Allocation $%.2f < minimum $%.2f — skipping %s",
|
|
||||||
alloc_per_asset, config.min_order_usd, opp.pair,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
quantity = round(alloc_per_asset / opp.last_price, opp.lot_decimals)
|
quantity = round(alloc_per_asset / opp.last_price, opp.lot_decimals)
|
||||||
|
|
||||||
if opp.order_min > 0 and quantity < opp.order_min:
|
if opp.order_min > 0 and quantity < opp.order_min:
|
||||||
|
|||||||
Reference in New Issue
Block a user