summaryrefslogtreecommitdiff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/alembic/versions/001_initial_schema.py10
-rw-r--r--shared/alembic/versions/002_news_sentiment_tables.py10
-rw-r--r--shared/alembic/versions/003_add_missing_indexes.py8
-rw-r--r--shared/src/shared/db.py29
-rw-r--r--shared/src/shared/events.py6
-rw-r--r--shared/src/shared/healthcheck.py5
-rw-r--r--shared/src/shared/metrics.py2
-rw-r--r--shared/src/shared/models.py29
-rw-r--r--shared/src/shared/notifier.py22
-rw-r--r--shared/src/shared/resilience.py4
-rw-r--r--shared/src/shared/sentiment.py5
-rw-r--r--shared/src/shared/sentiment_models.py5
-rw-r--r--shared/tests/test_alpaca.py4
-rw-r--r--shared/tests/test_broker.py5
-rw-r--r--shared/tests/test_db.py21
-rw-r--r--shared/tests/test_db_news.py13
-rw-r--r--shared/tests/test_events.py10
-rw-r--r--shared/tests/test_models.py13
-rw-r--r--shared/tests/test_news_events.py6
-rw-r--r--shared/tests/test_notifier.py2
-rw-r--r--shared/tests/test_resilience.py1
-rw-r--r--shared/tests/test_sa_news_models.py2
-rw-r--r--shared/tests/test_sentiment_aggregator.py16
-rw-r--r--shared/tests/test_sentiment_models.py14
24 files changed, 125 insertions, 117 deletions
diff --git a/shared/alembic/versions/001_initial_schema.py b/shared/alembic/versions/001_initial_schema.py
index 2bdaafc..7b744ee 100644
--- a/shared/alembic/versions/001_initial_schema.py
+++ b/shared/alembic/versions/001_initial_schema.py
@@ -5,16 +5,16 @@ Revises:
Create Date: 2026-04-01
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
+from alembic import op
# revision identifiers, used by Alembic.
revision: str = "001"
-down_revision: Union[str, None] = None
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | None = None
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/shared/alembic/versions/002_news_sentiment_tables.py b/shared/alembic/versions/002_news_sentiment_tables.py
index 402ff87..d85a634 100644
--- a/shared/alembic/versions/002_news_sentiment_tables.py
+++ b/shared/alembic/versions/002_news_sentiment_tables.py
@@ -5,15 +5,15 @@ Revises: 001
Create Date: 2026-04-02
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
-from alembic import op
import sqlalchemy as sa
+from alembic import op
revision: str = "002"
-down_revision: Union[str, None] = "001"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | None = "001"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/shared/alembic/versions/003_add_missing_indexes.py b/shared/alembic/versions/003_add_missing_indexes.py
index ff08789..7a252d4 100644
--- a/shared/alembic/versions/003_add_missing_indexes.py
+++ b/shared/alembic/versions/003_add_missing_indexes.py
@@ -5,14 +5,14 @@ Revises: 002
Create Date: 2026-04-02
"""
-from typing import Sequence, Union
+from collections.abc import Sequence
from alembic import op
revision: str = "003"
-down_revision: Union[str, None] = "002"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
+down_revision: str | None = "002"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
def upgrade() -> None:
diff --git a/shared/src/shared/db.py b/shared/src/shared/db.py
index e7cad92..a718951 100644
--- a/shared/src/shared/db.py
+++ b/shared/src/shared/db.py
@@ -3,26 +3,25 @@
import json
import uuid
from contextlib import asynccontextmanager
-from datetime import datetime, date, timedelta, timezone
+from datetime import UTC, date, datetime, timedelta
from decimal import Decimal
-from typing import Optional
from sqlalchemy import select, update
-from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
+from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
-from shared.models import Candle, Signal, Order, OrderStatus, NewsItem
-from shared.sentiment_models import SymbolScore, MarketSentiment
+from shared.models import Candle, NewsItem, Order, OrderStatus, Signal
from shared.sa_models import (
Base,
CandleRow,
- SignalRow,
+ MarketSentimentRow,
+ NewsItemRow,
OrderRow,
PortfolioSnapshotRow,
- NewsItemRow,
- SymbolScoreRow,
- MarketSentimentRow,
+ SignalRow,
StockSelectionRow,
+ SymbolScoreRow,
)
+from shared.sentiment_models import MarketSentiment, SymbolScore
class Database:
@@ -149,7 +148,7 @@ class Database:
self,
order_id: str,
status: OrderStatus,
- filled_at: Optional[datetime] = None,
+ filled_at: datetime | None = None,
) -> None:
"""Update the status (and optionally filled_at) of an order."""
stmt = (
@@ -195,7 +194,7 @@ class Database:
total_value=total_value,
realized_pnl=realized_pnl,
unrealized_pnl=unrealized_pnl,
- snapshot_at=datetime.now(timezone.utc),
+ snapshot_at=datetime.now(UTC),
)
session.add(row)
await session.commit()
@@ -206,7 +205,7 @@ class Database:
async def get_portfolio_snapshots(self, days: int = 30) -> list[dict]:
"""Retrieve recent portfolio snapshots."""
async with self.get_session() as session:
- since = datetime.now(timezone.utc) - timedelta(days=days)
+ since = datetime.now(UTC) - timedelta(days=days)
stmt = (
select(PortfolioSnapshotRow)
.where(PortfolioSnapshotRow.snapshot_at >= since)
@@ -249,7 +248,7 @@ class Database:
async def get_recent_news(self, hours: int = 24) -> list[dict]:
"""Retrieve news items published in the last N hours."""
- since = datetime.now(timezone.utc) - timedelta(hours=hours)
+ since = datetime.now(UTC) - timedelta(hours=hours)
stmt = (
select(NewsItemRow)
.where(NewsItemRow.published_at >= since)
@@ -367,7 +366,7 @@ class Database:
await session.rollback()
raise
- async def get_latest_market_sentiment(self) -> Optional[dict]:
+ async def get_latest_market_sentiment(self) -> dict | None:
"""Retrieve the 'latest' market sentiment row, or None if not found."""
stmt = select(MarketSentimentRow).where(MarketSentimentRow.id == "latest")
async with self._session_factory() as session:
@@ -409,7 +408,7 @@ class Database:
reason=reason,
key_news=json.dumps(key_news),
sentiment_snapshot=json.dumps(sentiment_snapshot),
- created_at=datetime.now(timezone.utc),
+ created_at=datetime.now(UTC),
)
async with self._session_factory() as session:
try:
diff --git a/shared/src/shared/events.py b/shared/src/shared/events.py
index 63f93a2..6f8def1 100644
--- a/shared/src/shared/events.py
+++ b/shared/src/shared/events.py
@@ -1,14 +1,14 @@
"""Event types and serialization for the trading platform."""
-from enum import Enum
+from enum import StrEnum
from typing import Any
from pydantic import BaseModel
-from shared.models import Candle, Signal, Order, NewsItem
+from shared.models import Candle, NewsItem, Order, Signal
-class EventType(str, Enum):
+class EventType(StrEnum):
CANDLE = "CANDLE"
SIGNAL = "SIGNAL"
ORDER = "ORDER"
diff --git a/shared/src/shared/healthcheck.py b/shared/src/shared/healthcheck.py
index 7411e8a..a19705b 100644
--- a/shared/src/shared/healthcheck.py
+++ b/shared/src/shared/healthcheck.py
@@ -3,10 +3,11 @@
from __future__ import annotations
import time
-from typing import Any, Callable, Awaitable
+from collections.abc import Awaitable, Callable
+from typing import Any
from aiohttp import web
-from prometheus_client import CollectorRegistry, REGISTRY, generate_latest, CONTENT_TYPE_LATEST
+from prometheus_client import CONTENT_TYPE_LATEST, REGISTRY, CollectorRegistry, generate_latest
class HealthCheckServer:
diff --git a/shared/src/shared/metrics.py b/shared/src/shared/metrics.py
index cd239f3..6189143 100644
--- a/shared/src/shared/metrics.py
+++ b/shared/src/shared/metrics.py
@@ -2,7 +2,7 @@
from __future__ import annotations
-from prometheus_client import Counter, Gauge, Histogram, CollectorRegistry, REGISTRY
+from prometheus_client import REGISTRY, CollectorRegistry, Counter, Gauge, Histogram
class ServiceMetrics:
diff --git a/shared/src/shared/models.py b/shared/src/shared/models.py
index a436c03..f357f9f 100644
--- a/shared/src/shared/models.py
+++ b/shared/src/shared/models.py
@@ -1,25 +1,24 @@
"""Shared Pydantic models for the trading platform."""
import uuid
+from datetime import UTC, datetime
from decimal import Decimal
-from datetime import datetime, timezone
-from enum import Enum
-from typing import Optional
+from enum import StrEnum
from pydantic import BaseModel, Field, computed_field
-class OrderSide(str, Enum):
+class OrderSide(StrEnum):
BUY = "BUY"
SELL = "SELL"
-class OrderType(str, Enum):
+class OrderType(StrEnum):
MARKET = "MARKET"
LIMIT = "LIMIT"
-class OrderStatus(str, Enum):
+class OrderStatus(StrEnum):
PENDING = "PENDING"
FILLED = "FILLED"
CANCELLED = "CANCELLED"
@@ -46,9 +45,9 @@ class Signal(BaseModel):
quantity: Decimal
reason: str
conviction: float = 1.0 # 0.0 to 1.0, signal strength/confidence
- stop_loss: Optional[Decimal] = None # Price to exit at loss
- take_profit: Optional[Decimal] = None # Price to exit at profit
- created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
+ stop_loss: Decimal | None = None # Price to exit at loss
+ take_profit: Decimal | None = None # Price to exit at profit
+ created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
class Order(BaseModel):
@@ -60,8 +59,8 @@ class Order(BaseModel):
price: Decimal
quantity: Decimal
status: OrderStatus = OrderStatus.PENDING
- created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
- filled_at: Optional[datetime] = None
+ created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
+ filled_at: datetime | None = None
class Position(BaseModel):
@@ -76,7 +75,7 @@ class Position(BaseModel):
return self.quantity * (self.current_price - self.avg_entry_price)
-class NewsCategory(str, Enum):
+class NewsCategory(StrEnum):
POLICY = "policy"
EARNINGS = "earnings"
MACRO = "macro"
@@ -89,11 +88,11 @@ class NewsItem(BaseModel):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
source: str
headline: str
- summary: Optional[str] = None
- url: Optional[str] = None
+ summary: str | None = None
+ url: str | None = None
published_at: datetime
symbols: list[str] = []
sentiment: float
category: NewsCategory
raw_data: dict = {}
- created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
+ created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
diff --git a/shared/src/shared/notifier.py b/shared/src/shared/notifier.py
index 3d7b6cf..cfc86cd 100644
--- a/shared/src/shared/notifier.py
+++ b/shared/src/shared/notifier.py
@@ -2,13 +2,13 @@
import asyncio
import logging
+from collections.abc import Sequence
from decimal import Decimal
-from typing import Optional, Sequence
import aiohttp
-from shared.models import Signal, Order, Position
-from shared.sentiment_models import SelectedStock, MarketSentiment
+from shared.models import Order, Position, Signal
+from shared.sentiment_models import MarketSentiment, SelectedStock
logger = logging.getLogger(__name__)
@@ -23,7 +23,7 @@ class TelegramNotifier:
self._bot_token = bot_token
self._chat_id = chat_id
self._semaphore = asyncio.Semaphore(1)
- self._session: Optional[aiohttp.ClientSession] = None
+ self._session: aiohttp.ClientSession | None = None
@property
def enabled(self) -> bool:
@@ -113,13 +113,13 @@ class TelegramNotifier:
"",
"<b>Positions:</b>",
]
- for pos in positions:
- lines.append(
- f" {pos.symbol}: qty={pos.quantity} "
- f"entry={pos.avg_entry_price} "
- f"current={pos.current_price} "
- f"pnl={pos.unrealized_pnl}"
- )
+ lines.extend(
+ f" {pos.symbol}: qty={pos.quantity} "
+ f"entry={pos.avg_entry_price} "
+ f"current={pos.current_price} "
+ f"pnl={pos.unrealized_pnl}"
+ for pos in positions
+ )
if not positions:
lines.append(" No open positions")
await self.send("\n".join(lines))
diff --git a/shared/src/shared/resilience.py b/shared/src/shared/resilience.py
index ef2a1f6..66225d7 100644
--- a/shared/src/shared/resilience.py
+++ b/shared/src/shared/resilience.py
@@ -11,9 +11,10 @@ import functools
import logging
import random
import time
+from collections.abc import Callable
from contextlib import asynccontextmanager
from enum import StrEnum
-from typing import Any, Callable
+from typing import Any
class _State(StrEnum):
@@ -21,6 +22,7 @@ class _State(StrEnum):
OPEN = "open"
HALF_OPEN = "half_open"
+
logger = logging.getLogger(__name__)
diff --git a/shared/src/shared/sentiment.py b/shared/src/shared/sentiment.py
index 5b4b0da..c56da3e 100644
--- a/shared/src/shared/sentiment.py
+++ b/shared/src/shared/sentiment.py
@@ -1,6 +1,7 @@
"""Market sentiment aggregation."""
from datetime import datetime
+from typing import ClassVar
from shared.sentiment_models import SymbolScore
@@ -14,9 +15,9 @@ def _safe_avg(values: list[float]) -> float:
class SentimentAggregator:
"""Aggregates per-news sentiment into per-symbol scores."""
- WEIGHTS = {"news": 0.3, "social": 0.2, "policy": 0.3, "filing": 0.2}
+ WEIGHTS: ClassVar[dict[str, float]] = {"news": 0.3, "social": 0.2, "policy": 0.3, "filing": 0.2}
- CATEGORY_MAP = {
+ CATEGORY_MAP: ClassVar[dict[str, str]] = {
"earnings": "news",
"macro": "news",
"social": "social",
diff --git a/shared/src/shared/sentiment_models.py b/shared/src/shared/sentiment_models.py
index a009601..ac06c20 100644
--- a/shared/src/shared/sentiment_models.py
+++ b/shared/src/shared/sentiment_models.py
@@ -1,7 +1,6 @@
"""Sentiment scoring and stock selection models."""
from datetime import datetime
-from typing import Optional
from pydantic import BaseModel
@@ -22,7 +21,7 @@ class SymbolScore(BaseModel):
class MarketSentiment(BaseModel):
fear_greed: int
fear_greed_label: str
- vix: Optional[float] = None
+ vix: float | None = None
fed_stance: str
market_regime: str
updated_at: datetime
@@ -39,6 +38,6 @@ class SelectedStock(BaseModel):
class Candidate(BaseModel):
symbol: str
source: str
- direction: Optional[OrderSide] = None
+ direction: OrderSide | None = None
score: float
reason: str
diff --git a/shared/tests/test_alpaca.py b/shared/tests/test_alpaca.py
index 080b7c4..55a2b24 100644
--- a/shared/tests/test_alpaca.py
+++ b/shared/tests/test_alpaca.py
@@ -1,7 +1,9 @@
"""Tests for Alpaca API client."""
-import pytest
from unittest.mock import AsyncMock, MagicMock
+
+import pytest
+
from shared.alpaca import AlpacaClient
diff --git a/shared/tests/test_broker.py b/shared/tests/test_broker.py
index eb1582d..5636611 100644
--- a/shared/tests/test_broker.py
+++ b/shared/tests/test_broker.py
@@ -1,10 +1,11 @@
"""Tests for the Redis broker."""
-import pytest
import json
-import redis
from unittest.mock import AsyncMock, patch
+import pytest
+import redis
+
@pytest.mark.asyncio
async def test_broker_publish():
diff --git a/shared/tests/test_db.py b/shared/tests/test_db.py
index 439a66e..b44a713 100644
--- a/shared/tests/test_db.py
+++ b/shared/tests/test_db.py
@@ -1,10 +1,11 @@
"""Tests for the SQLAlchemy async database layer."""
-import pytest
+from datetime import UTC, datetime
from decimal import Decimal
-from datetime import datetime, timezone
from unittest.mock import AsyncMock, MagicMock, patch
+import pytest
+
def make_candle():
from shared.models import Candle
@@ -12,7 +13,7 @@ def make_candle():
return Candle(
symbol="AAPL",
timeframe="1m",
- open_time=datetime(2024, 1, 1, tzinfo=timezone.utc),
+ open_time=datetime(2024, 1, 1, tzinfo=UTC),
open=Decimal("50000"),
high=Decimal("51000"),
low=Decimal("49500"),
@@ -22,7 +23,7 @@ def make_candle():
def make_signal():
- from shared.models import Signal, OrderSide
+ from shared.models import OrderSide, Signal
return Signal(
id="sig-1",
@@ -32,12 +33,12 @@ def make_signal():
price=Decimal("50000"),
quantity=Decimal("0.1"),
reason="Golden cross",
- created_at=datetime(2024, 1, 1, tzinfo=timezone.utc),
+ created_at=datetime(2024, 1, 1, tzinfo=UTC),
)
def make_order():
- from shared.models import Order, OrderSide, OrderType, OrderStatus
+ from shared.models import Order, OrderSide, OrderStatus, OrderType
return Order(
id="ord-1",
@@ -48,7 +49,7 @@ def make_order():
price=Decimal("50000"),
quantity=Decimal("0.1"),
status=OrderStatus.PENDING,
- created_at=datetime(2024, 1, 1, tzinfo=timezone.utc),
+ created_at=datetime(2024, 1, 1, tzinfo=UTC),
)
@@ -259,7 +260,7 @@ class TestUpdateOrderStatus:
db._session_factory = MagicMock(return_value=mock_session)
- filled = datetime(2024, 1, 2, tzinfo=timezone.utc)
+ filled = datetime(2024, 1, 2, tzinfo=UTC)
await db.update_order_status("ord-1", OrderStatus.FILLED, filled)
mock_session.execute.assert_awaited_once()
@@ -278,7 +279,7 @@ class TestGetCandles:
mock_row._mapping = {
"symbol": "AAPL",
"timeframe": "1m",
- "open_time": datetime(2024, 1, 1, tzinfo=timezone.utc),
+ "open_time": datetime(2024, 1, 1, tzinfo=UTC),
"open": Decimal("50000"),
"high": Decimal("51000"),
"low": Decimal("49500"),
@@ -444,7 +445,7 @@ class TestGetPortfolioSnapshots:
mock_row.total_value = Decimal("10000")
mock_row.realized_pnl = Decimal("0")
mock_row.unrealized_pnl = Decimal("500")
- mock_row.snapshot_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
+ mock_row.snapshot_at = datetime(2024, 1, 1, tzinfo=UTC)
mock_result = MagicMock()
mock_result.scalars.return_value.all.return_value = [mock_row]
diff --git a/shared/tests/test_db_news.py b/shared/tests/test_db_news.py
index a2c9140..c184bed 100644
--- a/shared/tests/test_db_news.py
+++ b/shared/tests/test_db_news.py
@@ -1,11 +1,12 @@
"""Tests for database news/sentiment methods. Uses in-memory SQLite."""
+from datetime import UTC, date, datetime
+
import pytest
-from datetime import datetime, date, timezone
from shared.db import Database
-from shared.models import NewsItem, NewsCategory
-from shared.sentiment_models import SymbolScore, MarketSentiment
+from shared.models import NewsCategory, NewsItem
+from shared.sentiment_models import MarketSentiment, SymbolScore
@pytest.fixture
@@ -20,7 +21,7 @@ async def test_insert_and_get_news_items(db):
item = NewsItem(
source="finnhub",
headline="AAPL earnings beat",
- published_at=datetime(2026, 4, 2, 12, 0, tzinfo=timezone.utc),
+ published_at=datetime(2026, 4, 2, 12, 0, tzinfo=UTC),
sentiment=0.8,
category=NewsCategory.EARNINGS,
symbols=["AAPL"],
@@ -40,7 +41,7 @@ async def test_upsert_symbol_score(db):
policy_score=0.0,
filing_score=0.2,
composite=0.3,
- updated_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ updated_at=datetime(2026, 4, 2, tzinfo=UTC),
)
await db.upsert_symbol_score(score)
scores = await db.get_top_symbol_scores(limit=5)
@@ -55,7 +56,7 @@ async def test_upsert_market_sentiment(db):
vix=18.2,
fed_stance="neutral",
market_regime="neutral",
- updated_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ updated_at=datetime(2026, 4, 2, tzinfo=UTC),
)
await db.upsert_market_sentiment(ms)
result = await db.get_latest_market_sentiment()
diff --git a/shared/tests/test_events.py b/shared/tests/test_events.py
index 6077d93..1ccd904 100644
--- a/shared/tests/test_events.py
+++ b/shared/tests/test_events.py
@@ -1,7 +1,7 @@
"""Tests for shared event types."""
+from datetime import UTC, datetime
from decimal import Decimal
-from datetime import datetime, timezone
def make_candle():
@@ -10,7 +10,7 @@ def make_candle():
return Candle(
symbol="AAPL",
timeframe="1m",
- open_time=datetime(2024, 1, 1, tzinfo=timezone.utc),
+ open_time=datetime(2024, 1, 1, tzinfo=UTC),
open=Decimal("50000"),
high=Decimal("51000"),
low=Decimal("49500"),
@@ -20,7 +20,7 @@ def make_candle():
def make_signal():
- from shared.models import Signal, OrderSide
+ from shared.models import OrderSide, Signal
return Signal(
strategy="test",
@@ -59,7 +59,7 @@ def test_candle_event_deserialize():
def test_signal_event_serialize():
"""Test SignalEvent serializes to dict correctly."""
- from shared.events import SignalEvent, EventType
+ from shared.events import EventType, SignalEvent
signal = make_signal()
event = SignalEvent(data=signal)
@@ -71,7 +71,7 @@ def test_signal_event_serialize():
def test_event_from_dict_dispatch():
"""Test Event.from_dict dispatches to correct class."""
- from shared.events import Event, CandleEvent, SignalEvent
+ from shared.events import CandleEvent, Event, SignalEvent
candle = make_candle()
event = CandleEvent(data=candle)
diff --git a/shared/tests/test_models.py b/shared/tests/test_models.py
index 21f9831..40bb791 100644
--- a/shared/tests/test_models.py
+++ b/shared/tests/test_models.py
@@ -1,8 +1,8 @@
"""Tests for shared models and settings."""
import os
+from datetime import UTC, datetime
from decimal import Decimal
-from datetime import datetime, timezone
from unittest.mock import patch
@@ -28,7 +28,7 @@ def test_candle_creation():
"""Test Candle model creation."""
from shared.models import Candle
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
candle = Candle(
symbol="AAPL",
timeframe="1m",
@@ -50,7 +50,7 @@ def test_candle_creation():
def test_signal_creation():
"""Test Signal model creation."""
- from shared.models import Signal, OrderSide
+ from shared.models import OrderSide, Signal
signal = Signal(
strategy="rsi_strategy",
@@ -72,9 +72,10 @@ def test_signal_creation():
def test_order_creation():
"""Test Order model creation with defaults."""
- from shared.models import Order, OrderSide, OrderType, OrderStatus
import uuid
+ from shared.models import Order, OrderSide, OrderStatus, OrderType
+
signal_id = str(uuid.uuid4())
order = Order(
signal_id=signal_id,
@@ -93,7 +94,7 @@ def test_order_creation():
def test_signal_conviction_default():
"""Test Signal defaults for conviction, stop_loss, take_profit."""
- from shared.models import Signal, OrderSide
+ from shared.models import OrderSide, Signal
signal = Signal(
strategy="rsi",
@@ -110,7 +111,7 @@ def test_signal_conviction_default():
def test_signal_with_stops():
"""Test Signal with explicit conviction, stop_loss, take_profit."""
- from shared.models import Signal, OrderSide
+ from shared.models import OrderSide, Signal
signal = Signal(
strategy="rsi",
diff --git a/shared/tests/test_news_events.py b/shared/tests/test_news_events.py
index 384796a..f748d8a 100644
--- a/shared/tests/test_news_events.py
+++ b/shared/tests/test_news_events.py
@@ -1,16 +1,16 @@
"""Tests for NewsEvent."""
-from datetime import datetime, timezone
+from datetime import UTC, datetime
+from shared.events import Event, EventType, NewsEvent
from shared.models import NewsCategory, NewsItem
-from shared.events import NewsEvent, EventType, Event
def test_news_event_to_dict():
item = NewsItem(
source="finnhub",
headline="Test",
- published_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ published_at=datetime(2026, 4, 2, tzinfo=UTC),
sentiment=0.5,
category=NewsCategory.MACRO,
)
diff --git a/shared/tests/test_notifier.py b/shared/tests/test_notifier.py
index 6c81369..cc98a56 100644
--- a/shared/tests/test_notifier.py
+++ b/shared/tests/test_notifier.py
@@ -6,7 +6,7 @@ from unittest.mock import AsyncMock, MagicMock, patch
import pytest
-from shared.models import Signal, Order, OrderSide, OrderType, OrderStatus, Position
+from shared.models import Order, OrderSide, OrderStatus, OrderType, Position, Signal
from shared.notifier import TelegramNotifier
diff --git a/shared/tests/test_resilience.py b/shared/tests/test_resilience.py
index 5ed4ac3..e0781af 100644
--- a/shared/tests/test_resilience.py
+++ b/shared/tests/test_resilience.py
@@ -6,7 +6,6 @@ import pytest
from shared.resilience import CircuitBreaker, async_timeout, retry_async
-
# --- retry_async tests ---
diff --git a/shared/tests/test_sa_news_models.py b/shared/tests/test_sa_news_models.py
index 91e6d4a..dc2d026 100644
--- a/shared/tests/test_sa_news_models.py
+++ b/shared/tests/test_sa_news_models.py
@@ -1,6 +1,6 @@
"""Tests for news-related SQLAlchemy models."""
-from shared.sa_models import NewsItemRow, SymbolScoreRow, MarketSentimentRow, StockSelectionRow
+from shared.sa_models import MarketSentimentRow, NewsItemRow, StockSelectionRow, SymbolScoreRow
def test_news_item_row_tablename():
diff --git a/shared/tests/test_sentiment_aggregator.py b/shared/tests/test_sentiment_aggregator.py
index a99c711..9193785 100644
--- a/shared/tests/test_sentiment_aggregator.py
+++ b/shared/tests/test_sentiment_aggregator.py
@@ -1,7 +1,9 @@
"""Tests for sentiment aggregator."""
+from datetime import UTC, datetime, timedelta
+
import pytest
-from datetime import datetime, timezone, timedelta
+
from shared.sentiment import SentimentAggregator
@@ -12,25 +14,25 @@ def aggregator():
def test_freshness_decay_recent():
a = SentimentAggregator()
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
assert a._freshness_decay(now, now) == 1.0
def test_freshness_decay_3_hours():
a = SentimentAggregator()
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
assert a._freshness_decay(now - timedelta(hours=3), now) == 0.7
def test_freshness_decay_12_hours():
a = SentimentAggregator()
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
assert a._freshness_decay(now - timedelta(hours=12), now) == 0.3
def test_freshness_decay_old():
a = SentimentAggregator()
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
assert a._freshness_decay(now - timedelta(days=2), now) == 0.0
@@ -44,7 +46,7 @@ def test_compute_composite():
def test_aggregate_news_by_symbol(aggregator):
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
news_items = [
{"symbols": ["AAPL"], "sentiment": 0.8, "category": "earnings", "published_at": now},
{
@@ -64,7 +66,7 @@ def test_aggregate_news_by_symbol(aggregator):
def test_aggregate_empty(aggregator):
- now = datetime.now(timezone.utc)
+ now = datetime.now(UTC)
assert aggregator.aggregate([], now) == {}
diff --git a/shared/tests/test_sentiment_models.py b/shared/tests/test_sentiment_models.py
index 25fc371..e00ffa6 100644
--- a/shared/tests/test_sentiment_models.py
+++ b/shared/tests/test_sentiment_models.py
@@ -1,16 +1,16 @@
"""Tests for news and sentiment models."""
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from shared.models import NewsCategory, NewsItem, OrderSide
-from shared.sentiment_models import SymbolScore, MarketSentiment, SelectedStock, Candidate
+from shared.sentiment_models import Candidate, MarketSentiment, SelectedStock, SymbolScore
def test_news_item_defaults():
item = NewsItem(
source="finnhub",
headline="Test headline",
- published_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ published_at=datetime(2026, 4, 2, tzinfo=UTC),
sentiment=0.5,
category=NewsCategory.MACRO,
)
@@ -25,7 +25,7 @@ def test_news_item_with_symbols():
item = NewsItem(
source="rss",
headline="AAPL earnings beat",
- published_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ published_at=datetime(2026, 4, 2, tzinfo=UTC),
sentiment=0.8,
category=NewsCategory.EARNINGS,
symbols=["AAPL"],
@@ -52,7 +52,7 @@ def test_symbol_score():
policy_score=0.0,
filing_score=0.2,
composite=0.3,
- updated_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ updated_at=datetime(2026, 4, 2, tzinfo=UTC),
)
assert score.symbol == "AAPL"
assert score.composite == 0.3
@@ -65,7 +65,7 @@ def test_market_sentiment():
vix=32.5,
fed_stance="hawkish",
market_regime="risk_off",
- updated_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ updated_at=datetime(2026, 4, 2, tzinfo=UTC),
)
assert ms.market_regime == "risk_off"
assert ms.vix == 32.5
@@ -77,7 +77,7 @@ def test_market_sentiment_no_vix():
fear_greed_label="Neutral",
fed_stance="neutral",
market_regime="neutral",
- updated_at=datetime(2026, 4, 2, tzinfo=timezone.utc),
+ updated_at=datetime(2026, 4, 2, tzinfo=UTC),
)
assert ms.vix is None