feature/api_endpoint #5
@@ -50,6 +50,7 @@ def make_cycle(session: Session, user_id: int, friendly_name: str = "Test Cycle"
|
|||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
friendly_name=friendly_name,
|
friendly_name=friendly_name,
|
||||||
symbol="AAPL",
|
symbol="AAPL",
|
||||||
|
exchange="NASDAQ",
|
||||||
underlying_currency=models.UnderlyingCurrency.USD,
|
underlying_currency=models.UnderlyingCurrency.USD,
|
||||||
status=models.CycleStatus.OPEN,
|
status=models.CycleStatus.OPEN,
|
||||||
start_date=datetime.now(timezone.utc).date(),
|
start_date=datetime.now(timezone.utc).date(),
|
||||||
@@ -65,6 +66,7 @@ def make_trade(session: Session, user_id: int, cycle_id: int, friendly_name: str
|
|||||||
user_id=user_id,
|
user_id=user_id,
|
||||||
friendly_name=friendly_name,
|
friendly_name=friendly_name,
|
||||||
symbol="AAPL",
|
symbol="AAPL",
|
||||||
|
exchange="NASDAQ",
|
||||||
underlying_currency=models.UnderlyingCurrency.USD,
|
underlying_currency=models.UnderlyingCurrency.USD,
|
||||||
trade_type=models.TradeType.LONG_SPOT,
|
trade_type=models.TradeType.LONG_SPOT,
|
||||||
trade_strategy=models.TradeStrategy.SPOT,
|
trade_strategy=models.TradeStrategy.SPOT,
|
||||||
@@ -129,6 +131,7 @@ def test_create_trade_success_with_cycle(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Test Trade",
|
"friendly_name": "Test Trade",
|
||||||
"symbol": "AAPL",
|
"symbol": "AAPL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -169,6 +172,7 @@ def test_create_trade_with_auto_created_cycle(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Test Trade with Auto Cycle",
|
"friendly_name": "Test Trade with Auto Cycle",
|
||||||
"symbol": "AAPL",
|
"symbol": "AAPL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -211,6 +215,7 @@ def test_create_trade_missing_required_fields(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Incomplete Trade",
|
"friendly_name": "Incomplete Trade",
|
||||||
"symbol": "AAPL",
|
"symbol": "AAPL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -226,6 +231,13 @@ def test_create_trade_missing_required_fields(session: Session) -> None:
|
|||||||
crud.create_trade(session, trade_data)
|
crud.create_trade(session, trade_data)
|
||||||
assert "symbol is required" in str(excinfo.value)
|
assert "symbol is required" in str(excinfo.value)
|
||||||
|
|
||||||
|
# Missing exchange
|
||||||
|
trade_data = base_trade_data.copy()
|
||||||
|
trade_data.pop("exchange", None)
|
||||||
|
with pytest.raises(ValueError) as excinfo:
|
||||||
|
crud.create_trade(session, trade_data)
|
||||||
|
assert "exchange is required" in str(excinfo.value)
|
||||||
|
|
||||||
# Missing underlying_currency
|
# Missing underlying_currency
|
||||||
trade_data = base_trade_data.copy()
|
trade_data = base_trade_data.copy()
|
||||||
trade_data.pop("underlying_currency", None)
|
trade_data.pop("underlying_currency", None)
|
||||||
@@ -269,6 +281,7 @@ def test_get_trade_by_id(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Test Trade for Get",
|
"friendly_name": "Test Trade for Get",
|
||||||
"symbol": "AAPL",
|
"symbol": "AAPL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -307,6 +320,7 @@ def test_get_trade_by_user_id_and_friendly_name(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": friendly_name,
|
"friendly_name": friendly_name,
|
||||||
"symbol": "AAPL",
|
"symbol": "AAPL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -333,6 +347,7 @@ def test_get_trades_by_user_id(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Trade One",
|
"friendly_name": "Trade One",
|
||||||
"symbol": "AAPL",
|
"symbol": "AAPL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -349,6 +364,7 @@ def test_get_trades_by_user_id(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Trade Two",
|
"friendly_name": "Trade Two",
|
||||||
"symbol": "GOOGL",
|
"symbol": "GOOGL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.SHORT_SPOT,
|
"trade_type": models.TradeType.SHORT_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -412,6 +428,7 @@ def test_replace_trade(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "Replaced Trade",
|
"friendly_name": "Replaced Trade",
|
||||||
"symbol": "MSFT",
|
"symbol": "MSFT",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"trade_type": models.TradeType.LONG_SPOT,
|
"trade_type": models.TradeType.LONG_SPOT,
|
||||||
"trade_strategy": models.TradeStrategy.SPOT,
|
"trade_strategy": models.TradeStrategy.SPOT,
|
||||||
@@ -452,6 +469,7 @@ def test_create_cycle(session: Session) -> None:
|
|||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"friendly_name": "My First Cycle",
|
"friendly_name": "My First Cycle",
|
||||||
"symbol": "GOOGL",
|
"symbol": "GOOGL",
|
||||||
|
"exchange": "NASDAQ",
|
||||||
"underlying_currency": models.UnderlyingCurrency.USD,
|
"underlying_currency": models.UnderlyingCurrency.USD,
|
||||||
"status": models.CycleStatus.OPEN,
|
"status": models.CycleStatus.OPEN,
|
||||||
"start_date": datetime.now(timezone.utc).date(),
|
"start_date": datetime.now(timezone.utc).date(),
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ def create_trade(session: Session, trade_data: Mapping) -> models.Trades:
|
|||||||
payload = {k: v for k, v in data.items() if k in allowed}
|
payload = {k: v for k, v in data.items() if k in allowed}
|
||||||
if "symbol" not in payload:
|
if "symbol" not in payload:
|
||||||
raise ValueError("symbol is required")
|
raise ValueError("symbol is required")
|
||||||
|
if "exchange" not in payload:
|
||||||
|
raise ValueError("exchange is required")
|
||||||
if "underlying_currency" not in payload:
|
if "underlying_currency" not in payload:
|
||||||
raise ValueError("underlying_currency is required")
|
raise ValueError("underlying_currency is required")
|
||||||
payload["underlying_currency"] = _check_enum(models.UnderlyingCurrency, payload["underlying_currency"], "underlying_currency")
|
payload["underlying_currency"] = _check_enum(models.UnderlyingCurrency, payload["underlying_currency"], "underlying_currency")
|
||||||
@@ -74,6 +76,7 @@ def create_trade(session: Session, trade_data: Mapping) -> models.Trades:
|
|||||||
c_payload = {
|
c_payload = {
|
||||||
"user_id": user_id,
|
"user_id": user_id,
|
||||||
"symbol": payload["symbol"],
|
"symbol": payload["symbol"],
|
||||||
|
"exchange": payload["exchange"],
|
||||||
"underlying_currency": payload["underlying_currency"],
|
"underlying_currency": payload["underlying_currency"],
|
||||||
"friendly_name": "Auto-created Cycle by trade " + payload.get("friendly_name", ""),
|
"friendly_name": "Auto-created Cycle by trade " + payload.get("friendly_name", ""),
|
||||||
"status": models.CycleStatus.OPEN,
|
"status": models.CycleStatus.OPEN,
|
||||||
@@ -184,6 +187,8 @@ def create_cycle(session: Session, cycle_data: Mapping) -> models.Cycles:
|
|||||||
raise ValueError("user_id is required")
|
raise ValueError("user_id is required")
|
||||||
if "symbol" not in payload:
|
if "symbol" not in payload:
|
||||||
raise ValueError("symbol is required")
|
raise ValueError("symbol is required")
|
||||||
|
if "exchange" not in payload:
|
||||||
|
raise ValueError("exchange is required")
|
||||||
if "underlying_currency" not in payload:
|
if "underlying_currency" not in payload:
|
||||||
raise ValueError("underlying_currency is required")
|
raise ValueError("underlying_currency is required")
|
||||||
payload["underlying_currency"] = _check_enum(models.UnderlyingCurrency, payload["underlying_currency"], "underlying_currency")
|
payload["underlying_currency"] = _check_enum(models.UnderlyingCurrency, payload["underlying_currency"], "underlying_currency")
|
||||||
|
|||||||
21
backend/trading_journal/dto.py
Normal file
21
backend/trading_journal/dto.py
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from sqlmodel import SQLModel
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
from trading_journal.models import TradeStrategy, TradeType, UnderlyingCurrency
|
||||||
|
|
||||||
|
|
||||||
|
class TradeBase(SQLModel):
|
||||||
|
user_id: int
|
||||||
|
friendly_name: str | None
|
||||||
|
symbol: str
|
||||||
|
underlying_currency: UnderlyingCurrency
|
||||||
|
trade_type: TradeType
|
||||||
|
trade_strategy: TradeStrategy
|
||||||
|
trade_date: date
|
||||||
|
trade_time_utc: datetime
|
||||||
@@ -72,6 +72,7 @@ class Trades(SQLModel, table=True):
|
|||||||
# allow null while user may omit friendly_name; uniqueness enforced per-user by constraint
|
# allow null while user may omit friendly_name; uniqueness enforced per-user by constraint
|
||||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
|
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||||
trade_type: TradeType = Field(sa_column=Column(Text, nullable=False))
|
trade_type: TradeType = Field(sa_column=Column(Text, nullable=False))
|
||||||
trade_strategy: TradeStrategy = Field(sa_column=Column(Text, nullable=False))
|
trade_strategy: TradeStrategy = Field(sa_column=Column(Text, nullable=False))
|
||||||
@@ -100,6 +101,7 @@ class Cycles(SQLModel, table=True):
|
|||||||
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
||||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
|
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||||
status: CycleStatus = Field(sa_column=Column(Text, nullable=False))
|
status: CycleStatus = Field(sa_column=Column(Text, nullable=False))
|
||||||
funding_source: FundingSource = Field(sa_column=Column(Text, nullable=True))
|
funding_source: FundingSource = Field(sa_column=Column(Text, nullable=True))
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ class Trades(SQLModel, table=True):
|
|||||||
# allow null while user may omit friendly_name; uniqueness enforced per-user by constraint
|
# allow null while user may omit friendly_name; uniqueness enforced per-user by constraint
|
||||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
|
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||||
trade_type: TradeType = Field(sa_column=Column(Text, nullable=False))
|
trade_type: TradeType = Field(sa_column=Column(Text, nullable=False))
|
||||||
trade_strategy: TradeStrategy = Field(sa_column=Column(Text, nullable=False))
|
trade_strategy: TradeStrategy = Field(sa_column=Column(Text, nullable=False))
|
||||||
@@ -100,6 +101,7 @@ class Cycles(SQLModel, table=True):
|
|||||||
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
user_id: int = Field(foreign_key="users.id", nullable=False, index=True)
|
||||||
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
friendly_name: str | None = Field(default=None, sa_column=Column(Text, nullable=True))
|
||||||
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
symbol: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
|
exchange: str = Field(sa_column=Column(Text, nullable=False))
|
||||||
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
underlying_currency: UnderlyingCurrency = Field(sa_column=Column(Text, nullable=False))
|
||||||
status: CycleStatus = Field(sa_column=Column(Text, nullable=False))
|
status: CycleStatus = Field(sa_column=Column(Text, nullable=False))
|
||||||
funding_source: FundingSource = Field(sa_column=Column(Text, nullable=True))
|
funding_source: FundingSource = Field(sa_column=Column(Text, nullable=True))
|
||||||
|
|||||||
Reference in New Issue
Block a user