Step 6: Update exchange creation logic to use new pricing config

- Update ExchangeService to load pricing config from database
- Update validate_eur_amount to use direction-specific limits
- Update apply_premium_for_direction to calculate base + extra premium
- Update create_exchange to use new premium calculation
- Add tests for premium calculation (small trade extra, large trade base only, direction-specific)
- Update existing tests to account for new premium calculation
This commit is contained in:
counterweight 2025-12-26 20:24:13 +01:00
parent d317939ad0
commit 41e158376c
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
2 changed files with 210 additions and 26 deletions

View file

@ -252,8 +252,10 @@ class TestCreateExchange:
assert data["eur_amount"] == 10000
assert data["status"] == "booked"
assert data["sats_amount"] > 0
# For buy, agreed price is market * 1.05
assert data["agreed_price_eur"] == pytest.approx(21000.0, rel=0.001)
# For buy with €100 (small trade): base 5% + extra 2% = 7%
# Agreed price is market * 1.07
assert data["agreed_price_eur"] == pytest.approx(21400.0, rel=0.001)
assert data["premium_percentage"] == 7
@pytest.mark.asyncio
async def test_create_exchange_sell_success(
@ -279,8 +281,10 @@ class TestCreateExchange:
assert data["direction"] == "sell"
assert data["bitcoin_transfer_method"] == "lightning"
assert data["eur_amount"] == 20000
# For sell, agreed price is market * 0.95
assert data["agreed_price_eur"] == pytest.approx(19000.0, rel=0.001)
# For sell with €200 (at threshold): base 5% + extra 2% = 7%
# Agreed price is market * 0.93
assert data["agreed_price_eur"] == pytest.approx(18600.0, rel=0.001)
assert data["premium_percentage"] == 7
@pytest.mark.asyncio
async def test_cannot_book_same_slot_twice(
@ -536,6 +540,118 @@ class TestCreateExchange:
assert response.status_code == 400
assert "at most" in response.json()["detail"]
@pytest.mark.asyncio
async def test_premium_calculation_small_trade_extra(
self, client_factory, regular_user, admin_user
):
"""Premium includes extra for small trades (amount <= threshold)."""
target_date = await setup_availability_and_price(client_factory, admin_user)
with mock_price_fetcher(20000.0):
async with client_factory.create(cookies=regular_user["cookies"]) as client:
# Trade below threshold: should get base + extra premium
response = await client.post(
"/api/exchange",
json={
"slot_start": f"{target_date}T09:00:00Z",
"direction": "buy",
"bitcoin_transfer_method": "onchain",
"eur_amount": 14000, # €140, below €200 threshold, multiple of €20
},
)
assert response.status_code == 200
data = response.json()
# Base 5% + extra 2% = 7% total
assert data["premium_percentage"] == 7
# Market 20000 * 1.07 = 21400
assert data["agreed_price_eur"] == pytest.approx(21400.0, rel=0.001)
@pytest.mark.asyncio
async def test_premium_calculation_large_trade_base_only(
self, client_factory, regular_user, admin_user
):
"""Premium is base only for large trades (amount > threshold)."""
target_date = await setup_availability_and_price(client_factory, admin_user)
with mock_price_fetcher(20000.0):
async with client_factory.create(cookies=regular_user["cookies"]) as client:
# Trade above threshold: should get base premium only
response = await client.post(
"/api/exchange",
json={
"slot_start": f"{target_date}T10:00:00Z",
"direction": "buy",
"bitcoin_transfer_method": "onchain",
"eur_amount": 24000, # €240, above €200 threshold, multiple of €20
},
)
assert response.status_code == 200
data = response.json()
# Base 5% only (no extra)
assert data["premium_percentage"] == 5
# Market 20000 * 1.05 = 21000
assert data["agreed_price_eur"] == pytest.approx(21000.0, rel=0.001)
@pytest.mark.asyncio
async def test_premium_calculation_direction_specific(
self, client_factory, regular_user, admin_user
):
"""Premium uses direction-specific base values."""
target_date = await setup_availability_and_price(client_factory, admin_user)
target_date_2 = await setup_availability_and_price(
client_factory, admin_user, target_date=in_days(2)
)
# Update pricing config with different premiums for buy/sell
async with client_factory.get_db_session() as db:
from repositories.pricing import PricingRepository
repo = PricingRepository(db)
await repo.create_or_update(
premium_buy=10,
premium_sell=8,
small_trade_threshold_eur=20000,
small_trade_extra_premium=2,
eur_min_buy=10000,
eur_max_buy=300000,
eur_min_sell=10000,
eur_max_sell=300000,
)
with mock_price_fetcher(20000.0):
async with client_factory.create(cookies=regular_user["cookies"]) as client:
# Buy trade
buy_response = await client.post(
"/api/exchange",
json={
"slot_start": f"{target_date}T09:00:00Z",
"direction": "buy",
"bitcoin_transfer_method": "onchain",
"eur_amount": 14000, # €140, multiple of €20
},
)
assert buy_response.status_code == 200
buy_data = buy_response.json()
# Buy: base 10% + extra 2% = 12%
assert buy_data["premium_percentage"] == 12
# Sell trade (different day to avoid one-trade-per-day limit)
sell_response = await client.post(
"/api/exchange",
json={
"slot_start": f"{target_date_2}T10:00:00Z",
"direction": "sell",
"bitcoin_transfer_method": "onchain",
"eur_amount": 14000, # €140, multiple of €20
},
)
assert sell_response.status_code == 200
sell_data = sell_response.json()
# Sell: base 8% + extra 2% = 10%
assert sell_data["premium_percentage"] == 10
@pytest.mark.asyncio
async def test_amount_not_multiple_of_increment_rejected(
self, client_factory, regular_user, admin_user
@ -595,9 +711,22 @@ class TestCreateExchange:
},
)
# Create stale price (create_exchange doesn't fetch, just reads from DB)
# Create stale price and pricing config
async with client_factory.get_db_session() as db:
await create_price_in_db(db, price=20000.0, minutes_ago=10)
from repositories.pricing import PricingRepository
repo = PricingRepository(db)
await repo.create_or_update(
premium_buy=5,
premium_sell=5,
small_trade_threshold_eur=20000,
small_trade_extra_premium=2,
eur_min_buy=10000,
eur_max_buy=300000,
eur_min_sell=10000,
eur_max_sell=300000,
)
async with client_factory.create(cookies=regular_user["cookies"]) as client:
response = await client.post(