aboutsummaryrefslogtreecommitdiff
path: root/server/api/bids.py
blob: c9d2ebddc7882742ede1b3796309fe9eab6e7881 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from sqlalchemy.exc import OperationalError
import time
from db import SessionLocal
from models.bid import Bid as BidModel
from models.auth import User
from pydantic import BaseModel
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
from typing import List, Optional

router = APIRouter()

MARKET_TIMEZONES = {
    "ISONE": ZoneInfo("America/New_York"),
    "NYISO": ZoneInfo("America/New_York"),
    "MISO": ZoneInfo("America/Chicago"),
}


def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()


class BidBase(BaseModel):
    timestamp: datetime
    quantity: float
    price: float
    user_id: (
        int  # In production the user_id should be obtained from the authenticated user
    )
    market: str


class BidCreate(BidBase):
    pass


class BidResponse(BidBase):
    id: int
    status: str
    pnl: Optional[float]

    class Config:
        from_attributes = True


@router.get("/", response_model=List[BidResponse])
def get_bids(db: Session = Depends(get_db)):
    return db.query(BidModel).all()


@router.post("/", response_model=BidResponse)
def submit_bid(bid: BidCreate, db: Session = Depends(get_db)):
    if bid.market not in MARKET_TIMEZONES:
        raise HTTPException(
            status_code=400,
            detail=f"Invalid market. Supported markets: {list(MARKET_TIMEZONES.keys())}",
        )

    market_tz = MARKET_TIMEZONES[bid.market]

    now = datetime.now(market_tz)
    bid_timestamp_local = bid.timestamp.astimezone(market_tz)
    bid_day = bid_timestamp_local.date()
    today = now.date()

    print(f"Received bid for {bid_day} at {bid.timestamp} ({bid.market})")

    if bid.timestamp < now:
        raise HTTPException(status_code=400, detail="Cannot submit bids in the past.")

    cutoff_time = now.replace(hour=11, minute=0, second=0, microsecond=0)

    if bid_day == today:
        if now > cutoff_time:
            raise HTTPException(
                status_code=400,
                detail="Cannot submit bids for today after 11AM local time.",
            )

    start_of_hour = bid.timestamp.replace(minute=0, second=0, microsecond=0)
    end_of_hour = start_of_hour.replace(minute=59, second=59, microsecond=999999)

    bid_count = (
        db.query(BidModel)
        .filter(
            BidModel.timestamp >= start_of_hour,
            BidModel.timestamp <= end_of_hour,
            BidModel.market == bid.market,
        )
        .count()
    )

    if bid_count >= 10:
        raise HTTPException(
            status_code=400,
            detail="Cannot submit more than 10 bids for this hour in this market.",
        )

    db_bid = BidModel(
        timestamp=bid.timestamp,
        quantity=bid.quantity,
        price=bid.price,
        user_id=bid.user_id,
        market=bid.market,
        status="Submitted",
        pnl=None,
    )
    db.add(db_bid)
    max_retries = 3

    for attempt in range(max_retries):
        try:
            db.commit()
            break
        except OperationalError as e:
            if "database is locked" in str(e).lower():
                if attempt < max_retries - 1:
                    time.sleep(2)
                    continue
                else:
                    raise HTTPException(
                        status_code=500,
                        detail="Database is busy. Please try again later.",
                    )
            else:
                raise
    db.refresh(db_bid)
    return db_bid