ifieryarrows commited on
Commit
7f9b9b1
·
verified ·
1 Parent(s): 269f729

Sync from GitHub

Browse files
Files changed (3) hide show
  1. app/commentary.py +68 -2
  2. app/main.py +2 -0
  3. app/models.py +4 -0
app/commentary.py CHANGED
@@ -13,6 +13,64 @@ from .settings import get_settings
13
  logger = logging.getLogger(__name__)
14
 
15
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  async def generate_commentary(
17
  current_price: float,
18
  predicted_price: float,
@@ -141,6 +199,7 @@ def save_commentary_to_db(
141
  predicted_price: float,
142
  predicted_return: float,
143
  sentiment_label: str,
 
144
  ) -> None:
145
  """
146
  Save generated commentary to database (upsert).
@@ -160,9 +219,10 @@ def save_commentary_to_db(
160
  existing.predicted_price = predicted_price
161
  existing.predicted_return = predicted_return
162
  existing.sentiment_label = sentiment_label
 
163
  existing.generated_at = datetime.utcnow()
164
  existing.model_name = settings.openrouter_model
165
- logger.info(f"Updated AI commentary for {symbol}")
166
  else:
167
  # Create new
168
  new_commentary = AICommentary(
@@ -172,10 +232,11 @@ def save_commentary_to_db(
172
  predicted_price=predicted_price,
173
  predicted_return=predicted_return,
174
  sentiment_label=sentiment_label,
 
175
  model_name=settings.openrouter_model,
176
  )
177
  session.add(new_commentary)
178
- logger.info(f"Created new AI commentary for {symbol}")
179
 
180
  session.commit()
181
 
@@ -197,6 +258,7 @@ def get_commentary_from_db(session, symbol: str) -> Optional[dict]:
197
  "predicted_price": record.predicted_price,
198
  "predicted_return": record.predicted_return,
199
  "sentiment_label": record.sentiment_label,
 
200
  "model_name": record.model_name,
201
  }
202
 
@@ -229,6 +291,9 @@ async def generate_and_save_commentary(
229
  )
230
 
231
  if commentary:
 
 
 
232
  save_commentary_to_db(
233
  session=session,
234
  symbol=symbol,
@@ -237,6 +302,7 @@ async def generate_and_save_commentary(
237
  predicted_price=predicted_price,
238
  predicted_return=predicted_return,
239
  sentiment_label=sentiment_label,
 
240
  )
241
 
242
  return commentary
 
13
  logger = logging.getLogger(__name__)
14
 
15
 
16
+ async def determine_ai_stance(commentary: str) -> str:
17
+ """
18
+ Have the AI analyze its own commentary to determine market stance.
19
+
20
+ Args:
21
+ commentary: The generated commentary text
22
+
23
+ Returns:
24
+ BULLISH, NEUTRAL, or BEARISH
25
+ """
26
+ settings = get_settings()
27
+
28
+ if not settings.openrouter_api_key or not commentary:
29
+ return "NEUTRAL"
30
+
31
+ prompt = f"""Analyze the following market commentary and determine the overall market stance.
32
+ Respond with ONLY one word: BULLISH, NEUTRAL, or BEARISH.
33
+
34
+ Commentary:
35
+ {commentary}
36
+
37
+ Your response (one word only):"""
38
+
39
+ try:
40
+ async with httpx.AsyncClient(timeout=30.0) as client:
41
+ response = await client.post(
42
+ "https://openrouter.ai/api/v1/chat/completions",
43
+ headers={
44
+ "Authorization": f"Bearer {settings.openrouter_api_key}",
45
+ "Content-Type": "application/json",
46
+ },
47
+ json={
48
+ "model": settings.openrouter_model,
49
+ "messages": [{"role": "user", "content": prompt}],
50
+ "max_tokens": 10,
51
+ "temperature": 0.1,
52
+ }
53
+ )
54
+
55
+ if response.status_code == 200:
56
+ data = response.json()
57
+ stance = data.get("choices", [{}])[0].get("message", {}).get("content", "").strip().upper()
58
+
59
+ # Validate response
60
+ if stance in ["BULLISH", "NEUTRAL", "BEARISH"]:
61
+ logger.info(f"AI stance determined: {stance}")
62
+ return stance
63
+ else:
64
+ logger.warning(f"Invalid AI stance response: {stance}, defaulting to NEUTRAL")
65
+ return "NEUTRAL"
66
+ else:
67
+ logger.error(f"AI stance API error: {response.status_code}")
68
+ return "NEUTRAL"
69
+
70
+ except Exception as e:
71
+ logger.error(f"AI stance detection failed: {e}")
72
+ return "NEUTRAL"
73
+
74
  async def generate_commentary(
75
  current_price: float,
76
  predicted_price: float,
 
199
  predicted_price: float,
200
  predicted_return: float,
201
  sentiment_label: str,
202
+ ai_stance: str = "NEUTRAL",
203
  ) -> None:
204
  """
205
  Save generated commentary to database (upsert).
 
219
  existing.predicted_price = predicted_price
220
  existing.predicted_return = predicted_return
221
  existing.sentiment_label = sentiment_label
222
+ existing.ai_stance = ai_stance
223
  existing.generated_at = datetime.utcnow()
224
  existing.model_name = settings.openrouter_model
225
+ logger.info(f"Updated AI commentary for {symbol} (stance: {ai_stance})")
226
  else:
227
  # Create new
228
  new_commentary = AICommentary(
 
232
  predicted_price=predicted_price,
233
  predicted_return=predicted_return,
234
  sentiment_label=sentiment_label,
235
+ ai_stance=ai_stance,
236
  model_name=settings.openrouter_model,
237
  )
238
  session.add(new_commentary)
239
+ logger.info(f"Created new AI commentary for {symbol} (stance: {ai_stance})")
240
 
241
  session.commit()
242
 
 
258
  "predicted_price": record.predicted_price,
259
  "predicted_return": record.predicted_return,
260
  "sentiment_label": record.sentiment_label,
261
+ "ai_stance": record.ai_stance or "NEUTRAL",
262
  "model_name": record.model_name,
263
  }
264
 
 
291
  )
292
 
293
  if commentary:
294
+ # Determine AI stance from the commentary
295
+ ai_stance = await determine_ai_stance(commentary)
296
+
297
  save_commentary_to_db(
298
  session=session,
299
  symbol=symbol,
 
302
  predicted_price=predicted_price,
303
  predicted_return=predicted_return,
304
  sentiment_label=sentiment_label,
305
+ ai_stance=ai_stance,
306
  )
307
 
308
  return commentary
app/main.py CHANGED
@@ -622,6 +622,7 @@ async def get_commentary(
622
  "commentary": result["commentary"],
623
  "error": None,
624
  "generated_at": result["generated_at"],
 
625
  }
626
  else:
627
  return {
@@ -629,6 +630,7 @@ async def get_commentary(
629
  "commentary": None,
630
  "error": "No commentary available. Commentary is generated after pipeline runs.",
631
  "generated_at": None,
 
632
  }
633
 
634
 
 
622
  "commentary": result["commentary"],
623
  "error": None,
624
  "generated_at": result["generated_at"],
625
+ "ai_stance": result.get("ai_stance", "NEUTRAL"),
626
  }
627
  else:
628
  return {
 
630
  "commentary": None,
631
  "error": "No commentary available. Commentary is generated after pipeline runs.",
632
  "generated_at": None,
633
+ "ai_stance": "NEUTRAL",
634
  }
635
 
636
 
app/models.py CHANGED
@@ -218,6 +218,10 @@ class AICommentary(Base):
218
  predicted_return = Column(Float, nullable=True)
219
  sentiment_label = Column(String(20), nullable=True)
220
 
 
 
 
 
221
  # When generated
222
  generated_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow, index=True)
223
 
 
218
  predicted_return = Column(Float, nullable=True)
219
  sentiment_label = Column(String(20), nullable=True)
220
 
221
+ # AI-determined market stance (BULLISH/NEUTRAL/BEARISH)
222
+ # Generated by having LLM analyze its own commentary
223
+ ai_stance = Column(String(20), nullable=True, default="NEUTRAL")
224
+
225
  # When generated
226
  generated_at = Column(DateTime(timezone=True), nullable=False, default=datetime.utcnow, index=True)
227