Spaces:
Running
Running
Update app/app.py
Browse files- app/app.py +33 -26
app/app.py
CHANGED
@@ -18,14 +18,10 @@ from fastapi import FastAPI, Request
|
|
18 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
19 |
from fastapi.templating import Jinja2Templates
|
20 |
|
21 |
-
# ====================================================================
|
22 |
-
# FIX APPLIED HERE
|
23 |
-
# ====================================================================
|
24 |
# Use relative imports because these modules are in the same 'app' package.
|
25 |
from .price_fetcher import PriceFetcher
|
26 |
from .gemini_analyzer import GeminiAnalyzer
|
27 |
from newsapi import NewsApiClient
|
28 |
-
# ====================================================================
|
29 |
|
30 |
|
31 |
# --- Application Lifespan for Resource Management ---
|
@@ -37,20 +33,17 @@ async def lifespan(app: FastAPI):
|
|
37 |
lifespan context manager.
|
38 |
"""
|
39 |
async with httpx.AsyncClient() as client:
|
40 |
-
# Instantiate and store all services in the application state.
|
41 |
app.state.price_fetcher = PriceFetcher(client=client, coins=["bitcoin", "ethereum", "dogecoin"])
|
42 |
app.state.gemini_analyzer = GeminiAnalyzer(client=client)
|
43 |
app.state.news_api = NewsApiClient(api_key=os.getenv("NEWS_API_KEY"))
|
44 |
-
|
45 |
-
# Create a queue for the real-time signal feed
|
46 |
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
|
47 |
|
48 |
-
# Create cancellable background tasks for
|
49 |
price_task = asyncio.create_task(
|
50 |
run_periodic_updates(app.state.price_fetcher, interval_seconds=60)
|
51 |
)
|
52 |
news_task = asyncio.create_task(
|
53 |
-
run_periodic_news_analysis(app, interval_seconds=
|
54 |
)
|
55 |
|
56 |
print("π Sentinel TradeFlow Protocol started successfully.")
|
@@ -72,42 +65,57 @@ async def run_periodic_updates(fetcher: PriceFetcher, interval_seconds: int):
|
|
72 |
await asyncio.sleep(interval_seconds)
|
73 |
|
74 |
async def run_periodic_news_analysis(app: FastAPI, interval_seconds: int):
|
75 |
-
"""Fetches, analyzes, and queues top crypto news periodically."""
|
76 |
while True:
|
77 |
-
print("π° Fetching latest crypto news
|
78 |
try:
|
79 |
top_headlines = app.state.news_api.get_everything(
|
80 |
-
q='bitcoin OR ethereum OR
|
81 |
language='en',
|
82 |
sort_by='publishedAt',
|
83 |
page_size=5
|
84 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
85 |
analyzer: GeminiAnalyzer = app.state.gemini_analyzer
|
86 |
-
for article in
|
87 |
title = article.get('title')
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
|
98 |
except Exception as e:
|
99 |
-
print(f"
|
100 |
|
|
|
101 |
await asyncio.sleep(interval_seconds)
|
102 |
|
103 |
# --- FastAPI App Initialization ---
|
104 |
|
105 |
app = FastAPI(title="Sentinel TradeFlow Protocol", lifespan=lifespan)
|
106 |
-
# This path is relative to the root where the uvicorn command is run
|
107 |
templates = Jinja2Templates(directory="templates")
|
108 |
|
109 |
# --- HTML Rendering Helper ---
|
110 |
-
|
111 |
def render_signal_card(payload: dict) -> str:
|
112 |
"""Renders a dictionary of analysis into a styled HTML card."""
|
113 |
s = payload
|
@@ -133,7 +141,6 @@ def render_signal_card(payload: dict) -> str:
|
|
133 |
"""
|
134 |
|
135 |
# --- API Endpoints ---
|
136 |
-
|
137 |
@app.get("/", response_class=HTMLResponse)
|
138 |
async def serve_dashboard(request: Request):
|
139 |
"""Serves the main interactive dashboard from `index.html`."""
|
|
|
18 |
from fastapi.responses import HTMLResponse, StreamingResponse
|
19 |
from fastapi.templating import Jinja2Templates
|
20 |
|
|
|
|
|
|
|
21 |
# Use relative imports because these modules are in the same 'app' package.
|
22 |
from .price_fetcher import PriceFetcher
|
23 |
from .gemini_analyzer import GeminiAnalyzer
|
24 |
from newsapi import NewsApiClient
|
|
|
25 |
|
26 |
|
27 |
# --- Application Lifespan for Resource Management ---
|
|
|
33 |
lifespan context manager.
|
34 |
"""
|
35 |
async with httpx.AsyncClient() as client:
|
|
|
36 |
app.state.price_fetcher = PriceFetcher(client=client, coins=["bitcoin", "ethereum", "dogecoin"])
|
37 |
app.state.gemini_analyzer = GeminiAnalyzer(client=client)
|
38 |
app.state.news_api = NewsApiClient(api_key=os.getenv("NEWS_API_KEY"))
|
|
|
|
|
39 |
app.state.signal_queue: asyncio.Queue = asyncio.Queue()
|
40 |
|
41 |
+
# Create cancellable background tasks. Let's use a shorter timer for testing.
|
42 |
price_task = asyncio.create_task(
|
43 |
run_periodic_updates(app.state.price_fetcher, interval_seconds=60)
|
44 |
)
|
45 |
news_task = asyncio.create_task(
|
46 |
+
run_periodic_news_analysis(app, interval_seconds=300) # Check news every 5 minutes for debugging
|
47 |
)
|
48 |
|
49 |
print("π Sentinel TradeFlow Protocol started successfully.")
|
|
|
65 |
await asyncio.sleep(interval_seconds)
|
66 |
|
67 |
async def run_periodic_news_analysis(app: FastAPI, interval_seconds: int):
|
68 |
+
"""Fetches, analyzes, and queues top crypto news periodically with detailed logging."""
|
69 |
while True:
|
70 |
+
print("π° [1/5] Fetching latest crypto news...")
|
71 |
try:
|
72 |
top_headlines = app.state.news_api.get_everything(
|
73 |
+
q='bitcoin OR ethereum OR "binance coin" OR solana OR ripple OR cardano',
|
74 |
language='en',
|
75 |
sort_by='publishedAt',
|
76 |
page_size=5
|
77 |
)
|
78 |
+
|
79 |
+
articles = top_headlines.get('articles', [])
|
80 |
+
print(f"π° [2/5] NewsAPI call successful. Found {len(articles)} articles.")
|
81 |
+
|
82 |
+
if not articles:
|
83 |
+
print("π° [SKIP] No new articles found in this cycle.")
|
84 |
+
await asyncio.sleep(interval_seconds)
|
85 |
+
continue
|
86 |
+
|
87 |
analyzer: GeminiAnalyzer = app.state.gemini_analyzer
|
88 |
+
for article in articles:
|
89 |
title = article.get('title')
|
90 |
+
print(f"π° [3/5] Processing article: '{title}'")
|
91 |
+
|
92 |
+
if not title or "[Removed]" in title:
|
93 |
+
print(f"π° [SKIP] Article has no title or was removed.")
|
94 |
+
continue
|
95 |
+
|
96 |
+
print(f"π° [4/5] Sending to Gemini for analysis...")
|
97 |
+
analysis = await analyzer.analyze_text(title)
|
98 |
+
|
99 |
+
if analysis.get("error"):
|
100 |
+
print(f"β [SKIP] Gemini analysis failed for '{title}'. Reason: {analysis.get('reason')}")
|
101 |
+
continue
|
102 |
+
|
103 |
+
analysis['url'] = article.get('url')
|
104 |
+
await app.state.signal_queue.put(analysis)
|
105 |
+
print(f"β
[5/5] Signal generated and queued for: '{title}'")
|
106 |
|
107 |
except Exception as e:
|
108 |
+
print(f"βββ CRITICAL ERROR in news analysis loop: {e}")
|
109 |
|
110 |
+
print(f"π° Loop finished. Waiting for {interval_seconds} seconds.")
|
111 |
await asyncio.sleep(interval_seconds)
|
112 |
|
113 |
# --- FastAPI App Initialization ---
|
114 |
|
115 |
app = FastAPI(title="Sentinel TradeFlow Protocol", lifespan=lifespan)
|
|
|
116 |
templates = Jinja2Templates(directory="templates")
|
117 |
|
118 |
# --- HTML Rendering Helper ---
|
|
|
119 |
def render_signal_card(payload: dict) -> str:
|
120 |
"""Renders a dictionary of analysis into a styled HTML card."""
|
121 |
s = payload
|
|
|
141 |
"""
|
142 |
|
143 |
# --- API Endpoints ---
|
|
|
144 |
@app.get("/", response_class=HTMLResponse)
|
145 |
async def serve_dashboard(request: Request):
|
146 |
"""Serves the main interactive dashboard from `index.html`."""
|