feat: productionize local service — fix signal pipeline, lower thresholds, add company-name ticker extraction

- Point Ollama to local instance via host.docker.internal, use gemma3 model
- Remove Docker Ollama service (using host's Ollama instead)
- Add company-name-to-ticker mapping (Apple→AAPL, Tesla→TSLA, etc.) for RSS articles
- Lower signal thresholds for faster feedback with paper trading:
  - FinBERT confidence: 0.6→0.4, signal strength: 0.3→0.15
  - News strategy: article_count 2→1, confidence 0.5→0.3, score ±0.3→±0.15
- Fix market data BarSet access bug (BarSet.__contains__ returns False incorrectly)
- Fix market data SIP feed error by switching to IEX feed for free Alpaca accounts
- Fix nginx proxy routing for /api/auth/* to api-gateway /auth/*
- Add seed_sample_data script
- Update tests for new thresholds and alpaca mock modules
This commit is contained in:
Viktor Barzin 2026-02-22 22:17:26 +00:00
parent 67e64fab18
commit d36ae40df1
No known key found for this signature in database
GPG key ID: 0EB088298288D958
18 changed files with 749 additions and 185 deletions

View file

@ -188,9 +188,10 @@ class TestTradesListEndpoint:
trade.signal_id = None
trade.created_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
# session.execute will be called twice: count + data
# session.execute is called twice: count + data (now returns tuples)
count_result = _make_execute_result([], scalar=1)
data_result = _make_execute_result([trade])
data_result = MagicMock()
data_result.all.return_value = [(trade, None)] # (Trade, strategy_name)
session.execute = AsyncMock(side_effect=[count_result, data_result])
resp = client.get("/api/trades")
@ -242,8 +243,11 @@ class TestStrategiesEndpoint:
strategy.active = True
strategy.created_at = datetime(2024, 1, 1, tzinfo=timezone.utc)
# First call: list strategies; subsequent calls: trades per strategy
strategies_result = _make_execute_result([strategy])
trades_result = _make_execute_result([]) # no trades
session.execute = AsyncMock(
return_value=_make_execute_result([strategy])
side_effect=[strategies_result, trades_result]
)
resp = client.get("/api/strategies")

View file

@ -97,6 +97,9 @@ def _install_alpaca_mocks():
historical_mod = ModuleType("alpaca.data.historical")
historical_mod.StockHistoricalDataClient = MagicMock
enums_mod = ModuleType("alpaca.data.enums")
enums_mod.DataFeed = MagicMock()
# Build the package hierarchy
alpaca_mod = sys.modules.get("alpaca") or ModuleType("alpaca")
data_mod = sys.modules.get("alpaca.data") or ModuleType("alpaca.data")
@ -106,6 +109,7 @@ def _install_alpaca_mocks():
sys.modules["alpaca.data.timeframe"] = timeframe_mod
sys.modules["alpaca.data.requests"] = requests_mod
sys.modules["alpaca.data.historical"] = historical_mod
sys.modules["alpaca.data.enums"] = enums_mod
# Install mocks before importing from market_data.main

View file

@ -271,17 +271,17 @@ class TestNewsDrivenStrategy:
@pytest.mark.asyncio
async def test_news_driven_no_signal_low_confidence(self, strategy: NewsDrivenStrategy) -> None:
"""No signal when avg_confidence is too low (<=0.5)."""
"""No signal when avg_confidence is too low (<=0.3)."""
market = _market()
sentiment = _sentiment(avg_score=0.8, avg_confidence=0.4, article_count=5)
sentiment = _sentiment(avg_score=0.8, avg_confidence=0.2, article_count=5)
signal = await strategy.evaluate("AAPL", market, sentiment)
assert signal is None
@pytest.mark.asyncio
async def test_news_driven_no_signal_few_articles(self, strategy: NewsDrivenStrategy) -> None:
"""No signal when article_count < 2."""
"""No signal when article_count < 1."""
market = _market()
sentiment = _sentiment(avg_score=0.8, avg_confidence=0.7, article_count=1)
sentiment = _sentiment(avg_score=0.8, avg_confidence=0.7, article_count=0)
signal = await strategy.evaluate("AAPL", market, sentiment)
assert signal is None
@ -311,7 +311,7 @@ class TestNewsDrivenStrategy:
@pytest.mark.asyncio
async def test_news_driven_neutral_score(self, strategy: NewsDrivenStrategy) -> None:
"""No signal when avg_score is between -0.3 and 0.3 (neutral)."""
"""No signal when avg_score is between -0.15 and 0.15 (neutral)."""
market = _market()
sentiment = _sentiment(avg_score=0.1, avg_confidence=0.9, article_count=10)
signal = await strategy.evaluate("AAPL", market, sentiment)
@ -319,9 +319,9 @@ class TestNewsDrivenStrategy:
@pytest.mark.asyncio
async def test_news_driven_boundary_confidence(self, strategy: NewsDrivenStrategy) -> None:
"""No signal when avg_confidence is exactly 0.5 (threshold is >0.5)."""
"""No signal when avg_confidence is exactly 0.3 (threshold is >0.3)."""
market = _market()
sentiment = _sentiment(avg_score=0.8, avg_confidence=0.5, article_count=5)
sentiment = _sentiment(avg_score=0.8, avg_confidence=0.3, article_count=5)
signal = await strategy.evaluate("AAPL", market, sentiment)
assert signal is None