Skip to content

Commit 3eb4088

Browse files
committed
feat(config): replace max_to_lend with max_offer_size for smoothed lending
Remove redundant \max_to_lend\, \max_percent_to_lend\, and \max_to_lend_rate\ configurations that conflicted with the FRR strategy. Introduce \max_offer_size\ to cap the maximum amount of a single loan offer, allowing unused balances to carry over for smoothed lending rates over time. Simplify \MaxToLend.py\ to exclusively handle \max_active_amount\ global caps. Update documentation and configuration templates to reflect these changes. Add comprehensive test coverage for \max_offer_size\ order sizing and remainder precision logic.
1 parent a35765d commit 3eb4088

11 files changed

Lines changed: 119 additions & 230 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,4 +117,5 @@ desktop.ini
117117
*.pem
118118
secrets/
119119
credentials.json
120-
.secrets/
120+
.secrets/
121+
BITFINEX

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,5 @@
1616
- [x] Clean unused options in config
1717
- [x] Make rate:dur mapping configurable in Settings
1818
- [x] Make my added features configurable in web settings (like frrdelta) and add proper docs for them
19-
- [ ] max_to_lend/max_percent_to_lend/max_to_lend_rate: These 3 config options are not useful and may conflict with FFR strategy? Consider removing them
19+
- [x] max_to_lend/max_percent_to_lend/max_to_lend_rate: These 3 config options are not useful and may conflict with FFR strategy? Consider removing them
2020

config_sample.toml

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,11 @@ min_loan_size = 0.01
9292
# >0 = limit (cap total lending to this amount, e.g., 1000 for USD means max 1000 USD total lent)
9393
max_active_amount = -1
9494

95-
# How much to lend out
96-
# Raw maximum amount to lend if under max_to_lend_rate. 0 or commented = check max_percent_to_lend
97-
max_to_lend = 0
98-
# Maximum percent to lend if under max_to_lend_rate. 0 or commented = 100%
99-
max_percent_to_lend = 0
100-
# Max to lend conditional rate. If > 0, the limits above apply when rate <= max_to_lend_rate.
101-
max_to_lend_rate = 0
95+
# Limits the maximum amount of a single loan offer to smooth out lending rates over time.
96+
# Unused balance remains in the account and will be offered in the next bot cycle.
97+
# -1 = unlimited (no limit on single offer size, standard spreading applies)
98+
# >0 = limit (cap single offer to this amount, e.g., 100 for USD means each offer is max 100 USD)
99+
max_offer_size = -1
102100

103101
# Lending Strategy Selection. "Spread" or "FRR" (Bitfinex only).
104102
# Spread: Standard gap/spread based lending.

docs/configuration.rst

Lines changed: 18 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -271,48 +271,6 @@ Very few situations require you to change these settings.
271271
- Uncomment to enable.
272272
- Format: ``YEAR-MONTH-DAY``
273273

274-
Max to be lent
275-
--------------
276-
277-
This feature group allows you to only lend a certain percentage of your total holding in a coin, until the lending rate surpasses a certain threshold. Then it will lend at max capacity. These settings are found in the ``[coin.default]`` section or specific ``[coin.SYMBOL]`` sections.
278-
279-
- ``max_to_lend`` is a raw number of how much you will lend of each coin whose lending rate is below ``max_to_lend_rate``.
280-
281-
- Default value: 0 (disabled)
282-
- Allowed range: 0 (disabled) or ``min_loan_size`` and up
283-
- If set to 0, it is disabled.
284-
- If disabled, the bot will check if ``max_percent_to_lend`` is enabled and use that instead.
285-
- Setting this overrides ``max_percent_to_lend``.
286-
- This is a setting for the raw value of coin that will be lent if the coin's lending rate is under ``max_to_lend_rate``.
287-
- Has no effect if current rate is higher than ``max_to_lend_rate``.
288-
- If the remainder (after subtracting ``max_to_lend``) in a coin's balance is less than ``min_loan_size``, then the remainder will be lent anyway. Otherwise, the coins would go to waste since you can't lend under ``min_loan_size``.
289-
290-
- ``max_percent_to_lend`` is a percentage of how much you will lend of each coin whose lending rate is below ``max_to_lend_rate``.
291-
292-
- Default value: 0 (disabled)
293-
- Allowed range: 0 (disabled) to 100 percent
294-
- If set to 0, it is disabled.
295-
- If disabled in addition to ``max_to_lend``, the entire feature will be disabled (100% of balance will be lent).
296-
- This percentage is calculated per-coin, and is the percentage of the balance that will be lent if the coin's current rate is less than ``max_to_lend_rate``.
297-
- Has no effect if current rate is higher than ``max_to_lend_rate``.
298-
- If the remainder in a coin's balance is less than ``min_loan_size``, then the remainder will be lent anyway.
299-
300-
301-
- ``max_to_lend_rate`` is the rate threshold (in percent) when all coins are lent.
302-
303-
- Default value: 0 (disabled)
304-
- Allowed range: 0 (disabled) or ``min_daily_rate`` to 5 percent
305-
- Setting this to 0 with a limit in place causes the limit to always be active.
306-
- When an individual coin's lending rate passes this threshold, all of the coin will be lent instead of applying the limits from ``max_to_lend`` or ``max_percent_to_lend``.
307-
308-
.. code-block:: toml
309-
310-
[coin.default]
311-
max_to_lend = 0
312-
max_percent_to_lend = 0
313-
max_to_lend_rate = 0
314-
315-
316274
Config per Coin
317275
---------------
318276

@@ -357,6 +315,24 @@ Max Active Amount (Limit Total Lending)
357315
min_loan_size = 150
358316
max_active_amount = 5000.0 # Only lend up to 5000 USD total
359317
318+
Max Offer Size (Smooth Lending Over Time)
319+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
320+
321+
- ``max_offer_size`` limits the maximum amount of coin placed in a single loan offer. Found in the ``[coin.default]`` or specific ``[coin.SYMBOL]`` section.
322+
323+
- Default value: -1 (unlimited)
324+
- Allowed values:
325+
- ``-1`` = Unlimited (standard spreading applies)
326+
- ``> 0`` = Limit (cap each individual offer to this amount)
327+
- If set to >0, no individual loan offer will exceed this amount. Unused balance remains in your wallet and will be offered in the next bot cycle (e.g. 60 seconds later). This effectively creates a Dollar Cost Averaging (DCA) effect, smoothing out your lending rates over time.
328+
329+
Example configuration:
330+
331+
.. code-block:: toml
332+
333+
[coin.USD]
334+
max_offer_size = 1000.0 # Never place an offer larger than 1000 USD
335+
360336
361337
Advanced logging and Web Display
362338
--------------------------------

docs/dev/config_rearch/config_implementation_plan.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,6 @@ min_loan_size = 0.01
8080
min_daily_rate = 0.005
8181
max_daily_rate = 5.0
8282
max_active_amount = -1 # -1 = unlimited
83-
max_to_lend = 0 # 0 = unlimited
84-
max_percent_to_lend = 0 # 0 = 100%
85-
max_to_lend_rate = 0 # 0 = disabled
8683
strategy = "Spread" # or "FRR"
8784

8885
# Spread Strategy Params

docs/dev/config_rearch/config_validation_rules.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,7 @@
4545
| `max_daily_rate` (old: `maxdailyrate`) | `Decimal` | `5.0` | `0.003 <= x <= 5.0` (in percent) | `Configuration.py:195` |
4646
| `min_loan_size` (old: `minloansize`) | `Decimal` | `0.01` | `>= 0.005` | `Configuration.py:244` |
4747
| `max_active_amount` (old: `maxactiveamount`) | `Decimal` | `-1` | `-1` = unlimited, `0` = disabled, `> 0` = limit (caps total lending) | `Configuration.py:85` |
48-
| `max_to_lend` (old: `maxtolend`) | `Decimal` | `0` | `>= 0`. `0` means unlimited/check percent. | `Configuration.py:179` |
49-
| `max_percent_to_lend` (old: `maxpercenttolend`) | `Decimal` | `0` | `0 <= x <= 100` | `Configuration.py:180` |
50-
| `max_to_lend_rate` (old: `maxtolendrate`) | `Decimal` | `0` | `>= 0` (in percent) | `Configuration.py:181` |
48+
| `max_offer_size` | `Decimal` | `-1` | `-1` = unlimited, `> 0` = cap on single offer size | `Configuration.py:90` |
5149
| `strategy` (old: `lending_strategy`) | `str` | `"Spread"` | Enum: `"Spread"`, `"FRR"` | `Configuration.py:190` |
5250

5351
### Spread Strategy

src/lendingbot/modules/Configuration.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ class CoinConfig(BaseModel):
9090
min_daily_rate: Decimal = Field(Decimal("0.005"), ge=0, le=5)
9191
max_daily_rate: Decimal = Field(Decimal("5.0"), ge=0, le=5)
9292

93-
@field_validator("min_daily_rate", "max_daily_rate", "max_to_lend_rate", mode="after")
93+
@field_validator("min_daily_rate", "max_daily_rate", mode="after")
9494
@classmethod
9595
def convert_percent_to_decimal(cls, v: Decimal) -> Decimal:
9696
return v / 100
@@ -101,9 +101,10 @@ def convert_percent_to_decimal(cls, v: Decimal) -> Decimal:
101101
# 0 = disabled (skip this coin entirely, equivalent to not including in all_currencies)
102102
# >0 = limit (cap total lending to this amount in coin units, e.g., 1000 USD)
103103
max_active_amount: Decimal = Decimal("-1")
104-
max_to_lend: Decimal = Decimal("0")
105-
max_percent_to_lend: Decimal = Field(Decimal("0"), ge=0, le=100)
106-
max_to_lend_rate: Decimal = Decimal("0")
104+
# max_offer_size: Limits the maximum amount of a *single* loan offer.
105+
# -1 = unlimited (no limit on single offer size)
106+
# >0 = limit (cap single offer to this amount in coin units, e.g., 1000 USD)
107+
max_offer_size: Decimal = Decimal("-1")
107108

108109
# Strategy
109110
strategy: LendingStrategy = LendingStrategy.SPREAD

src/lendingbot/modules/Lending.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -576,13 +576,24 @@ def construct_orders(
576576

577577
new_order_rates = sorted(set(order_rates))
578578
new_order_amounts = []
579+
580+
cur_max_offer_size = Decimal("-1")
581+
if cfg := self.coin_cfg.get(cur):
582+
cur_max_offer_size = cfg.max_offer_size
583+
579584
for _ in range(len(new_order_rates)):
580-
new_amount = self.data.truncate(cur_active_bal / len(new_order_rates), 8)
581-
new_order_amounts.append(Decimal(str(new_amount)))
585+
new_amount = Decimal(str(self.data.truncate(cur_active_bal / len(new_order_rates), 8)))
586+
if cur_max_offer_size > 0 and new_amount > cur_max_offer_size:
587+
new_amount = cur_max_offer_size
588+
new_order_amounts.append(new_amount)
582589

583590
remainder = cur_active_bal - sum(new_order_amounts)
584591
if remainder > 0: # If truncating causes remainder, add that to first order.
585-
new_order_amounts[0] += remainder
592+
if cur_max_offer_size <= 0:
593+
new_order_amounts[0] += remainder
594+
elif new_order_amounts[0] < cur_max_offer_size:
595+
allowance = cur_max_offer_size - new_order_amounts[0]
596+
new_order_amounts[0] += min(remainder, allowance)
586597

587598
resp = {"amounts": new_order_amounts, "rates": new_order_rates}
588599
return resp
@@ -677,10 +688,8 @@ def lend_cur(
677688
# Pass the total_lent for this currency to enable max_active_amount limit
678689
cur_total_lent = total_lent.get(active_cur, Decimal(0))
679690
active_bal = MaxToLend.amount_to_lend(
680-
active_cur_total_balance,
681691
active_cur,
682692
Decimal(str(lending_balances[active_cur])),
683-
Decimal(str(order_book["rates"][0])),
684693
total_lent=cur_total_lent,
685694
)
686695

@@ -749,9 +758,7 @@ def lend_all(self) -> None:
749758

750759
for cur in sorted(total_lent):
751760
if not lending_balances or cur not in lending_balances:
752-
MaxToLend.amount_to_lend(
753-
total_lent[cur], cur, Decimal(0), Decimal(0), total_lent=total_lent[cur]
754-
)
761+
MaxToLend.amount_to_lend(cur, Decimal(0), total_lent=total_lent[cur])
755762

756763
usable_currencies = 0
757764
ticker: dict[str, dict[str, str]] | None = None

src/lendingbot/modules/MaxToLend.py

Lines changed: 5 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,10 @@
22

33
from . import Configuration
44
from .Logger import Logger
5-
from .Utils import format_amount_currency, format_rate_pct
5+
from .Utils import format_amount_currency
66

77

88
coin_cfg: dict[str, Configuration.CoinConfig] = {}
9-
max_to_lend_rate: Decimal = Decimal(0)
10-
max_to_lend: Decimal = Decimal(0)
11-
max_percent_to_lend: Decimal = Decimal(0)
129
min_loan_size: Decimal = Decimal("0.001")
1310
log: Logger | None = None
1411

@@ -21,36 +18,29 @@ def init(config: Configuration.RootConfig, log1: Logger) -> None:
2118
config: The configuration object.
2219
log1: The logger instance.
2320
"""
24-
global coin_cfg, max_to_lend_rate, max_to_lend, max_percent_to_lend, min_loan_size, log
21+
global coin_cfg, min_loan_size, log
2522

2623
# Populate coin_cfg with configured coins (merged with defaults by get_coin_config)
2724
coin_cfg = {}
2825
for symbol in config.coin:
2926
coin_cfg[symbol] = config.get_coin_config(symbol)
3027

3128
default_coin = config.get_coin_config("default")
32-
max_to_lend = default_coin.max_to_lend
33-
max_percent_to_lend = default_coin.max_percent_to_lend
34-
max_to_lend_rate = default_coin.max_to_lend_rate
3529
min_loan_size = default_coin.min_loan_size
3630
log = log1
3731

3832

3933
def amount_to_lend(
40-
active_cur_test_balance: Decimal,
4134
active_cur: str,
4235
lending_balance: Decimal,
43-
low_rate: Decimal,
4436
total_lent: Decimal = Decimal(0),
4537
) -> Decimal:
4638
"""
47-
Calculates the actual amount to lend based on limits and market rates.
39+
Calculates the actual amount to lend based on absolute limits.
4840
4941
Args:
50-
active_cur_test_balance: The total balance of the currency (lending + on-order).
5142
active_cur: The currency symbol.
5243
lending_balance: The available balance in the lending account.
53-
low_rate: The lowest rate currently in the order book.
5444
total_lent: The amount currently lent out (active loans).
5545
5646
Returns:
@@ -59,18 +49,9 @@ def amount_to_lend(
5949
if log is None:
6050
return lending_balance
6151

62-
restrict_lend = False
63-
active_bal = Decimal(0)
64-
log_data = ""
65-
cur_max_to_lend_rate = max_to_lend_rate
66-
cur_max_to_lend = max_to_lend
67-
cur_max_percent_to_lend = max_percent_to_lend
68-
cur_max_active_amount = Decimal(-1)
52+
cur_max_active_amount = Decimal("-1")
6953

7054
if cfg := coin_cfg.get(active_cur):
71-
cur_max_to_lend_rate = cfg.max_to_lend_rate
72-
cur_max_to_lend = cfg.max_to_lend
73-
cur_max_percent_to_lend = cfg.max_percent_to_lend
7455
cur_max_active_amount = cfg.max_active_amount
7556

7657
# Check max_active_amount limit first (absolute cap on total lending)
@@ -97,44 +78,4 @@ def amount_to_lend(
9778
)
9879
lending_balance = available_capacity
9980

100-
if (cur_max_to_lend_rate == 0 and low_rate > 0) or cur_max_to_lend_rate >= low_rate > 0:
101-
log_data = (
102-
f"The Lower Rate found on {active_cur} is {format_rate_pct(low_rate)} "
103-
f"vs conditional rate {format_rate_pct(cur_max_to_lend_rate)}. "
104-
)
105-
restrict_lend = True
106-
107-
if cur_max_to_lend != 0 and restrict_lend:
108-
log.updateStatusValue(active_cur, "maxToLend", cur_max_to_lend)
109-
if lending_balance > (active_cur_test_balance - cur_max_to_lend):
110-
active_bal = lending_balance - (active_cur_test_balance - cur_max_to_lend)
111-
112-
if cur_max_to_lend == 0 and cur_max_percent_to_lend != 0 and restrict_lend:
113-
log.updateStatusValue(
114-
active_cur, "maxToLend", (cur_max_percent_to_lend * active_cur_test_balance)
115-
)
116-
if lending_balance > (
117-
active_cur_test_balance - (cur_max_percent_to_lend * active_cur_test_balance)
118-
):
119-
active_bal = lending_balance - (
120-
active_cur_test_balance - (cur_max_percent_to_lend * active_cur_test_balance)
121-
)
122-
123-
if cur_max_to_lend == 0 and cur_max_percent_to_lend == 0:
124-
log.updateStatusValue(active_cur, "maxToLend", active_cur_test_balance)
125-
active_bal = lending_balance
126-
127-
if not restrict_lend:
128-
log.updateStatusValue(active_cur, "maxToLend", active_cur_test_balance)
129-
active_bal = lending_balance
130-
131-
if (lending_balance - active_bal) < min_loan_size:
132-
active_bal = lending_balance
133-
134-
if active_bal < lending_balance:
135-
log.log(
136-
f"{log_data} Lending {format_amount_currency(active_bal, active_cur)} "
137-
f"of {format_amount_currency(lending_balance, active_cur)} Available"
138-
)
139-
140-
return active_bal
81+
return lending_balance

0 commit comments

Comments
 (0)