diff --git a/backend/testhelpers/tradecycles.sh b/backend/testhelpers/tradecycles.sh new file mode 100755 index 0000000..f2f5831 --- /dev/null +++ b/backend/testhelpers/tradecycles.sh @@ -0,0 +1,56 @@ +curl --location '127.0.0.1:18881/api/v1/trades' \ +--header 'Content-Type: application/json' \ +--header 'Cookie: session_token=uYsEZZdH9ecQ432HQUdfab292I14suk4GuI12-cAyuw' \ +--data '{ + "friendly_name": "20250908-CA-PUT", + "symbol": "CA", + "exchange_id": 1, + "underlying_currency": "EUR", + "trade_type": "SELL_PUT", + "trade_strategy": "WHEEL", + "trade_date": "2025-09-08", + "quantity": 1, + "quantity_multiplier": 100, + "price_cents": 17, + "expiry_date": "2025-09-09", + "strike_price_cents": 1220, + "commission_cents": 114 +}' + +curl --location '127.0.0.1:18881/api/v1/trades' \ +--header 'Content-Type: application/json' \ +--header 'Cookie: session_token=uYsEZZdH9ecQ432HQUdfab292I14suk4GuI12-cAyuw' \ +--data '{ + "friendly_name": "20250920-CA-ASSIGN", + "symbol": "CA", + "exchange_id": 1, + "cycle_id": 1, + "underlying_currency": "EUR", + "trade_type": "ASSIGNMENT", + "trade_strategy": "WHEEL", + "trade_date": "2025-09-20", + "quantity": 100, + "quantity_multiplier": 1, + "price_cents": 1220, + "commission_cents": 0 +}' + +curl --location '127.0.0.1:18881/api/v1/trades' \ +--header 'Content-Type: application/json' \ +--header 'Cookie: session_token=uYsEZZdH9ecQ432HQUdfab292I14suk4GuI12-cAyuw' \ +--data '{ + "friendly_name": "20250923-CA-CALL", + "symbol": "CA", + "exchange_id": 1, + "cycle_id": 1, + "underlying_currency": "EUR", + "trade_type": "SELL_CALL", + "trade_strategy": "WHEEL", + "trade_date": "2025-09-23", + "quantity": 1, + "quantity_multiplier": 100, + "price_cents": 31, + "expiry_date": "2025-10-10", + "strike_price_cents": 1200, + "commission_cents": 114 +}' \ No newline at end of file diff --git a/backend/trading_journal/dto.py b/backend/trading_journal/dto.py index 1851b0d..0dd227f 100644 --- a/backend/trading_journal/dto.py +++ b/backend/trading_journal/dto.py @@ -69,7 +69,7 @@ class CycleBase(SQLModel): funding_source: str | None = None capital_exposure_cents: int | None = None loan_amount_cents: int | None = None - loan_interest_rate_bps: int | None = None + loan_interest_rate_tenth_bps: int | None = None trades: list[TradeRead] | None = None exchange: ExchangesRead | None = None diff --git a/backend/trading_journal/service.py b/backend/trading_journal/service.py index 07a3b19..4855c3e 100644 --- a/backend/trading_journal/service.py +++ b/backend/trading_journal/service.py @@ -250,11 +250,23 @@ def get_cycles_by_user_service(db_session: Session, user_id: int) -> list[CycleR return [CycleRead.model_validate(cycle) for cycle in cycles] -def _validate_cycle_update_data(cycle_data: CycleUpdate) -> tuple[bool, str]: +def _validate_cycle_update_data(cycle_data: CycleUpdate) -> tuple[bool, str]: # noqa: PLR0911 if cycle_data.status == "CLOSED" and cycle_data.end_date is None: return False, "end_date is required when status is CLOSED" if cycle_data.status == "OPEN" and cycle_data.end_date is not None: return False, "end_date must be empty when status is OPEN" + if cycle_data.capital_exposure_cents is not None and cycle_data.capital_exposure_cents < 0: + return False, "capital_exposure_cents must be non-negative" + if ( + cycle_data.funding_source is not None + and cycle_data.funding_source != "CASH" + and (cycle_data.loan_amount_cents is None or cycle_data.loan_interest_rate_tenth_bps is None) + ): + return False, "loan_amount_cents and loan_interest_rate_tenth_bps are required when funding_source is not CASH" + if cycle_data.loan_amount_cents is not None and cycle_data.loan_amount_cents < 0: + return False, "loan_amount_cents must be non-negative" + if cycle_data.loan_interest_rate_tenth_bps is not None and cycle_data.loan_interest_rate_tenth_bps < 0: + return False, "loan_interest_rate_tenth_bps must be non-negative" return True, ""