mgbam commited on
Commit
510ee6f
Β·
verified Β·
1 Parent(s): a7b69e3

Update app/app.py

Browse files
Files changed (1) hide show
  1. app/app.py +58 -74
app/app.py CHANGED
@@ -1,13 +1,14 @@
1
  """
2
- Sentinel TradeFlow Protocol β€” v3.0 FINAL
3
 
4
- This is the definitive, robust version of the application, designed for
5
- guaranteed functionality and a superior user experience.
6
  """
7
  import asyncio
8
  import os
9
  from contextlib import asynccontextmanager
10
  from datetime import datetime, timezone
 
 
11
 
12
  import httpx
13
  from fastapi import FastAPI, Request
@@ -15,113 +16,96 @@ from fastapi.responses import HTMLResponse, StreamingResponse
15
  from fastapi.templating import Jinja2Templates
16
 
17
  from .price_fetcher import PriceFetcher
18
- from .gemini_analyzer import GeminiAnalyzer
19
- from newsapi import NewsApiClient
 
20
 
21
  @asynccontextmanager
22
  async def lifespan(app: FastAPI):
23
  async with httpx.AsyncClient() as client:
24
- app.state.price_fetcher = PriceFetcher(client=client, coins=["bitcoin", "ethereum", "dogecoin"])
25
- app.state.gemini_analyzer = GeminiAnalyzer(client=client)
26
- app.state.news_api = NewsApiClient(api_key=os.getenv("NEWS_API_KEY"))
27
  app.state.signal_queue: asyncio.Queue = asyncio.Queue()
28
 
29
- price_task = asyncio.create_task(run_periodic_updates(app.state.price_fetcher, 30))
30
- news_task = asyncio.create_task(run_periodic_news_analysis(app, 300))
31
 
32
- print("πŸš€ Sentinel TradeFlow Protocol v3.0 FINAL started successfully.")
33
  yield
34
 
35
- print("⏳ Shutting down background tasks...")
36
- price_task.cancel()
37
- news_task.cancel()
38
  try:
39
- await asyncio.gather(price_task, news_task)
40
  except asyncio.CancelledError:
41
- print("Background tasks cancelled.")
42
-
43
- async def run_periodic_updates(fetcher: PriceFetcher, interval_seconds: int):
44
- while True:
45
- await fetcher.update_prices_async()
46
- await asyncio.sleep(interval_seconds)
47
 
48
- async def run_periodic_news_analysis(app: FastAPI, interval_seconds: int):
49
- processed_urls = set()
50
  while True:
51
- print("πŸ“° Fetching news...")
52
- try:
53
- top_headlines = app.state.news_api.get_everything(
54
- q='(crypto OR bitcoin OR ethereum) AND (regulation OR partnership OR hack OR update OR adoption)',
55
- language='en', sort_by='publishedAt', page_size=10
56
- )
57
- articles = top_headlines.get('articles', [])
58
- print(f"πŸ“° Found {len(articles)} articles.")
59
 
60
- analyzer: GeminiAnalyzer = app.state.gemini_analyzer
61
- for article in reversed(articles):
62
- title = article.get('title')
63
- url = article.get('url')
64
- if title and "[Removed]" not in title and url not in processed_urls:
65
- print(f"✨ Analyzing: '{title}'")
66
- analysis = await analyzer.analyze_text(title)
67
- if not analysis.get("error"):
68
- analysis['url'] = url
69
- analysis['timestamp'] = datetime.now(timezone.utc).isoformat()
70
- await app.state.signal_queue.put(analysis)
71
- processed_urls.add(url)
72
- print(f"βœ… Queued: '{title}'")
73
- except Exception as e:
74
- print(f"❌ CRITICAL ERROR in news loop: {e}")
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  await asyncio.sleep(interval_seconds)
77
 
78
- app = FastAPI(title="Sentinel TradeFlow Protocol", lifespan=lifespan)
79
  templates = Jinja2Templates(directory="templates")
80
 
81
  def render_signal_card(payload: dict) -> str:
82
  s = payload
83
- url = s.get('url', '#')
84
- summary = s.get('summary', 'N/A')
85
  time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
86
-
87
- # This entire block will be sent as a single data payload
88
  return f"""
89
- <div id="last-signal-time" hx-swap-oob="true">{time_str}</div>
90
- <div class="card impact-{s.get('impact', 'low').lower()}" hx-swap-oob="afterbegin:#signal-container">
91
- <header class="card-header">
92
- <span>{s.get('topic', 'General News')}</span>
93
- <span>{', '.join(s.get('entities', []))}</span>
94
- </header>
95
- <blockquote><a href="{url}" target="_blank" rel="noopener noreferrer">{summary}</a></blockquote>
96
- <footer><strong>Sentiment:</strong> <span class="sentiment-{s.get('sentiment', 'neutral').lower()}">{s.get('sentiment')} ({s.get('sentiment_score', 0):.2f})</span> β€’ <strong>Impact:</strong> {s.get('impact')}</footer>
97
- </div>
 
 
98
  """
99
 
100
  @app.get("/", response_class=HTMLResponse)
101
  async def serve_dashboard(request: Request):
102
  return templates.TemplateResponse("index.html", {"request": request})
103
 
104
- @app.get("/api/prices", response_class=HTMLResponse)
105
- async def get_prices_fragment(request: Request):
106
- prices = request.app.state.price_fetcher.get_current_prices()
107
- return HTMLResponse("".join(
108
- f"<span>{c.capitalize()}: <strong>${p:,.2f}</strong></span>" if isinstance(p, (int, float))
109
- else f"<span>{c.capitalize()}: <strong>{p}</strong></span>"
110
- for c, p in prices.items()
111
- ))
112
-
113
  @app.get("/api/signals/stream")
114
  async def signal_stream(request: Request):
115
  queue: asyncio.Queue = request.app.state.signal_queue
116
  async def event_generator():
117
- # --- THE GUARANTEED "HELLO" MESSAGE ---
118
- welcome_html = "<div id='signal-container' hx-swap-oob='innerHTML'></div>"
119
- yield f"event: message\ndata: {welcome_html}\n\n"
120
-
121
  while True:
122
  payload = await queue.get()
123
  html_card = render_signal_card(payload)
124
  data_payload = html_card.replace('\n', ' ').strip()
125
  yield f"event: message\ndata: {data_payload}\n\n"
126
-
127
  return StreamingResponse(event_generator(), media_type="text/event-stream")
 
1
  """
2
+ Sentinel Arbitrage Engine - v4.0 FINAL
3
 
4
+ High-frequency arbitrage detection and AI-powered risk analysis.
 
5
  """
6
  import asyncio
7
  import os
8
  from contextlib import asynccontextmanager
9
  from datetime import datetime, timezone
10
+ from typing import Dict, Optional
11
+ import json
12
 
13
  import httpx
14
  from fastapi import FastAPI, Request
 
16
  from fastapi.templating import Jinja2Templates
17
 
18
  from .price_fetcher import PriceFetcher
19
+ from .arbitrage_analyzer import ArbitrageAnalyzer
20
+
21
+ OPPORTUNITY_THRESHOLD = 0.003 # 0.3% price difference to trigger a signal
22
 
23
  @asynccontextmanager
24
  async def lifespan(app: FastAPI):
25
  async with httpx.AsyncClient() as client:
26
+ app.state.price_fetcher = PriceFetcher(client=client)
27
+ app.state.arbitrage_analyzer = ArbitrageAnalyzer(client=client)
 
28
  app.state.signal_queue: asyncio.Queue = asyncio.Queue()
29
 
30
+ arbitrage_task = asyncio.create_task(run_arbitrage_detector(app, 5)) # Run every 5 seconds
 
31
 
32
+ print("πŸš€ Sentinel Arbitrage Engine started successfully.")
33
  yield
34
 
35
+ print("⏳ Shutting down arbitrage engine...")
36
+ arbitrage_task.cancel()
 
37
  try:
38
+ await arbitrage_task
39
  except asyncio.CancelledError:
40
+ print("Engine shut down.")
 
 
 
 
 
41
 
42
+ async def run_arbitrage_detector(app: FastAPI, interval_seconds: int):
43
+ last_opportunity = {}
44
  while True:
45
+ await app.state.price_fetcher.update_prices_async()
46
+ prices = app.state.price_fetcher.get_current_prices()
47
+
48
+ valid_prices = {k: v for k, v in prices.items() if v is not None}
49
+ if len(valid_prices) < 2:
50
+ await asyncio.sleep(interval_seconds)
51
+ continue
 
52
 
53
+ min_exchange = min(valid_prices, key=valid_prices.get)
54
+ max_exchange = max(valid_prices, key=valid_prices.get)
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
+ buy_price = valid_prices[min_exchange]
57
+ sell_price = valid_prices[max_exchange]
58
+
59
+ if buy_price > 0 and (sell_price - buy_price) / buy_price > OPPORTUNITY_THRESHOLD:
60
+ opportunity = {
61
+ "id": f"{min_exchange}-{max_exchange}",
62
+ "buy_exchange": min_exchange.capitalize(), "buy_price": buy_price,
63
+ "sell_exchange": max_exchange.capitalize(), "sell_price": sell_price,
64
+ "spread_pct": ((sell_price - buy_price) / buy_price) * 100
65
+ }
66
+ # Only analyze if it's a new opportunity
67
+ if last_opportunity.get("id") != opportunity["id"]:
68
+ print(f"⚑️ Arbitrage Opportunity Detected: {opportunity['spread_pct']:.2f}% spread")
69
+ briefing = await app.state.arbitrage_analyzer.get_alpha_briefing(opportunity)
70
+ if briefing:
71
+ signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
72
+ await app.state.signal_queue.put(signal)
73
+ last_opportunity = opportunity
74
+
75
  await asyncio.sleep(interval_seconds)
76
 
77
+ app = FastAPI(title="Sentinel Arbitrage Engine", lifespan=lifespan)
78
  templates = Jinja2Templates(directory="templates")
79
 
80
  def render_signal_card(payload: dict) -> str:
81
  s = payload
 
 
82
  time_str = datetime.fromisoformat(s['timestamp']).strftime('%H:%M:%S UTC')
83
+
 
84
  return f"""
85
+ <tr id="trade-row-{s['id']}" hx-swap-oob="true">
86
+ <td><strong>BTC</strong></td>
87
+ <td><span class="buy">{s['buy_exchange']}</span><br>${s['buy_price']:,.2f}</td>
88
+ <td><span class="sell">{s['sell_exchange']}</span><br>${s['sell_price']:,.2f}</td>
89
+ <td><strong>{s['spread_pct']:.3f}%</strong></td>
90
+ <td><span class="risk-{s.get('risk', 'low').lower()}">{s.get('risk', 'N/A')}</span></td>
91
+ <td>{s.get('strategy', 'N/A')}</td>
92
+ <td><strong>${s.get('profit_usd', 0):,.2f}</strong></td>
93
+ <td><button class="trade-btn">Simulate Trade</button></td>
94
+ </tr>
95
+ <div id="last-update-time" hx-swap-oob="true">{time_str}</div>
96
  """
97
 
98
  @app.get("/", response_class=HTMLResponse)
99
  async def serve_dashboard(request: Request):
100
  return templates.TemplateResponse("index.html", {"request": request})
101
 
 
 
 
 
 
 
 
 
 
102
  @app.get("/api/signals/stream")
103
  async def signal_stream(request: Request):
104
  queue: asyncio.Queue = request.app.state.signal_queue
105
  async def event_generator():
 
 
 
 
106
  while True:
107
  payload = await queue.get()
108
  html_card = render_signal_card(payload)
109
  data_payload = html_card.replace('\n', ' ').strip()
110
  yield f"event: message\ndata: {data_payload}\n\n"
 
111
  return StreamingResponse(event_generator(), media_type="text/event-stream")