Phase 1.1: Add exchange configuration

- Add exchange constants to shared/constants.json:
  - eurTradeMin: 100, eurTradeMax: 3000, eurTradeIncrement: 20
  - premiumPercentage: 5
  - priceRefreshSeconds: 60, priceStalenessSeconds: 300
- Add exchangeStatuses and tradeDirections to shared constants
- Add ExchangeStatus and TradeDirection enums to models.py
- Update shared_constants.py to export new exchange constants
- Update validate_constants.py to validate new enums and fields
This commit is contained in:
counterweight 2025-12-22 18:16:35 +01:00
parent c89e0312fa
commit 30e5d0828e
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
4 changed files with 84 additions and 1 deletions

View file

@ -68,6 +68,23 @@ class AppointmentStatus(str, PyEnum):
CANCELLED_BY_ADMIN = "cancelled_by_admin"
class ExchangeStatus(str, PyEnum):
"""Status of an exchange trade."""
BOOKED = "booked"
COMPLETED = "completed"
CANCELLED_BY_USER = "cancelled_by_user"
CANCELLED_BY_ADMIN = "cancelled_by_admin"
NO_SHOW = "no_show"
class TradeDirection(str, PyEnum):
"""Direction of a trade from the user's perspective."""
BUY = "buy" # User buys BTC, gives EUR
SELL = "sell" # User sells BTC, gets EUR
# Role name constants
ROLE_ADMIN = "admin"
ROLE_REGULAR = "regular"

View file

@ -11,3 +11,11 @@ SLOT_DURATION_MINUTES: int = _constants["booking"]["slotDurationMinutes"]
MIN_ADVANCE_DAYS: int = _constants["booking"]["minAdvanceDays"]
MAX_ADVANCE_DAYS: int = _constants["booking"]["maxAdvanceDays"]
NOTE_MAX_LENGTH: int = _constants["booking"]["noteMaxLength"]
# Exchange constants
EUR_TRADE_MIN: int = _constants["exchange"]["eurTradeMin"]
EUR_TRADE_MAX: int = _constants["exchange"]["eurTradeMax"]
EUR_TRADE_INCREMENT: int = _constants["exchange"]["eurTradeIncrement"]
PREMIUM_PERCENTAGE: int = _constants["exchange"]["premiumPercentage"]
PRICE_REFRESH_SECONDS: int = _constants["exchange"]["priceRefreshSeconds"]
PRICE_STALENESS_SECONDS: int = _constants["exchange"]["priceStalenessSeconds"]

View file

@ -3,7 +3,14 @@
import json
from pathlib import Path
from models import ROLE_ADMIN, ROLE_REGULAR, AppointmentStatus, InviteStatus
from models import (
ROLE_ADMIN,
ROLE_REGULAR,
AppointmentStatus,
ExchangeStatus,
InviteStatus,
TradeDirection,
)
def validate_shared_constants() -> None:
@ -44,6 +51,24 @@ def validate_shared_constants() -> None:
f"Expected: {expected_appointment_statuses}, Got: {got}"
)
# Validate exchange statuses
expected_exchange_statuses = {s.name: s.value for s in ExchangeStatus}
if constants.get("exchangeStatuses") != expected_exchange_statuses:
got = constants.get("exchangeStatuses")
raise ValueError(
f"Exchange status mismatch. "
f"Expected: {expected_exchange_statuses}, Got: {got}"
)
# Validate trade directions
expected_trade_directions = {d.name: d.value for d in TradeDirection}
if constants.get("tradeDirections") != expected_trade_directions:
got = constants.get("tradeDirections")
raise ValueError(
f"Trade direction mismatch. "
f"Expected: {expected_trade_directions}, Got: {got}"
)
# Validate booking constants exist with required fields
booking = constants.get("booking", {})
required_booking_fields = [
@ -56,6 +81,20 @@ def validate_shared_constants() -> None:
if field not in booking:
raise ValueError(f"Missing booking constant '{field}' in constants.json")
# Validate exchange constants exist with required fields
exchange = constants.get("exchange", {})
required_exchange_fields = [
"eurTradeMin",
"eurTradeMax",
"eurTradeIncrement",
"premiumPercentage",
"priceRefreshSeconds",
"priceStalenessSeconds",
]
for field in required_exchange_fields:
if field not in exchange:
raise ValueError(f"Missing exchange constant '{field}' in constants.json")
# Validate validation rules exist (structure check only)
validation = constants.get("validation", {})
required_fields = ["telegram", "signal", "nostrNpub"]

View file

@ -13,12 +13,31 @@
"CANCELLED_BY_USER": "cancelled_by_user",
"CANCELLED_BY_ADMIN": "cancelled_by_admin"
},
"exchangeStatuses": {
"BOOKED": "booked",
"COMPLETED": "completed",
"CANCELLED_BY_USER": "cancelled_by_user",
"CANCELLED_BY_ADMIN": "cancelled_by_admin",
"NO_SHOW": "no_show"
},
"tradeDirections": {
"BUY": "buy",
"SELL": "sell"
},
"booking": {
"slotDurationMinutes": 15,
"maxAdvanceDays": 30,
"minAdvanceDays": 1,
"noteMaxLength": 144
},
"exchange": {
"eurTradeMin": 100,
"eurTradeMax": 3000,
"eurTradeIncrement": 20,
"premiumPercentage": 5,
"priceRefreshSeconds": 60,
"priceStalenessSeconds": 300
},
"validation": {
"telegram": {
"maxLengthAfterAt": 32,