mgbam commited on
Commit
9b0536f
Β·
verified Β·
1 Parent(s): 0323e8e

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +51 -71
app/main.py CHANGED
@@ -1,9 +1,9 @@
1
  """
2
- Sentinel Arbitrage Engine - v20.1 FINAL (Syntax Fix)
3
 
4
- This version uses a file-based log and a raw, unstoppable streaming
5
- endpoint to guarantee signal delivery. This version corrects the async
6
- generator syntax.
7
  """
8
  import asyncio
9
  import os
@@ -12,95 +12,75 @@ import time
12
  from contextlib import asynccontextmanager
13
  from datetime import datetime, timezone
14
  import httpx
15
- from fastapi import FastAPI, Request
16
- from fastapi.responses import StreamingResponse
17
  from fastapi.staticfiles import StaticFiles
18
 
19
- # --- RELATIVE IMPORTS ---
20
- # This assumes your project is structured with an 'app' package.
21
  from .price_fetcher import PriceFetcher
22
  from .arbitrage_analyzer import ArbitrageAnalyzer
23
 
24
  OPPORTUNITY_THRESHOLD = 0.0015
25
- SIGNALS_FILE = "signals.log"
26
 
27
- @asynccontextmanager
28
- async def lifespan(app: FastAPI):
29
- if os.path.exists(SIGNALS_FILE):
30
- os.remove(SIGNALS_FILE)
31
-
32
- async with httpx.AsyncClient() as client:
33
- price_fetcher = PriceFetcher(client)
34
- arbitrage_analyzer = ArbitrageAnalyzer(client)
35
-
36
- app.state.engine_task = asyncio.create_task(
37
- run_arbitrage_detector(price_fetcher, arbitrage_analyzer)
38
- )
39
- print("πŸš€ Sentinel Engine v20.1 (Syntax Fix) is ONLINE.")
40
- yield
41
- app.state.engine_task.cancel()
42
- print("βœ… Engine shut down.")
43
 
44
  async def run_arbitrage_detector(price_fetcher, analyzer):
45
- """The core engine loop; writes signals to a log file."""
 
46
  while True:
47
  try:
 
48
  await price_fetcher.update_prices_async()
49
  all_prices = price_fetcher.get_all_prices()
 
 
 
 
 
 
50
  for asset, prices in all_prices.items():
51
  pyth_price = prices.get("pyth")
52
  chainlink_price = prices.get("chainlink_agg")
 
53
  if pyth_price and chainlink_price and pyth_price > 0:
54
  spread = abs(pyth_price - chainlink_price) / chainlink_price
55
  if spread > OPPORTUNITY_THRESHOLD:
56
- current_time = time.time()
57
- if not hasattr(analyzer, 'last_call') or current_time - analyzer.last_call.get(asset, 0) > 60:
58
- analyzer.last_call = getattr(analyzer, 'last_call', {})
59
- analyzer.last_call[asset] = current_time
60
- opportunity = {"asset": asset, "pyth_price": pyth_price, "chainlink_price": chainlink_price, "spread_pct": spread * 100}
61
- briefing = await analyzer.get_alpha_briefing(asset, opportunity)
62
- if briefing:
63
- signal = {**opportunity, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
64
- with open(SIGNALS_FILE, "a") as f:
65
- f.write(json.dumps(signal) + "\n")
66
- print(f"βœ… Signal LOGGED for {asset}")
67
  except Exception as e:
68
- print(f"❌ ERROR in engine loop: {e}")
 
69
  await asyncio.sleep(15)
70
 
71
- # --- FastAPI App Setup ---
72
- app = FastAPI(lifespan=lifespan)
 
 
 
 
 
 
 
73
 
74
- # --- API Endpoints ---
75
- @app.get("/api/signals/stream")
76
- async def signal_stream():
77
- """This endpoint streams the content of the signals.log file line by line."""
78
- async def event_generator():
79
- try:
80
- with open(SIGNALS_FILE, 'r') as f:
81
- # Go to the end of the file
82
- f.seek(0, 2)
83
- while True:
84
- line = f.readline()
85
- if not line:
86
- await asyncio.sleep(0.5) # Wait for new lines
87
- continue
88
- # Stream the line to the client
89
- yield line
90
- except FileNotFoundError:
91
- print("signals.log not created yet, client connected early.")
92
- # Keep the connection open and wait for the file to be created
93
- while not os.path.exists(SIGNALS_FILE):
94
- await asyncio.sleep(1)
95
- # ====================================================================
96
- # THE CRITICAL FIX IS HERE
97
- # ====================================================================
98
- # Use 'async for' to correctly delegate to the async generator
99
- async for line in event_generator():
100
- yield line
101
- # ====================================================================
102
 
103
- return StreamingResponse(event_generator(), media_type="text/plain")
 
 
 
104
 
105
- # Mount static files to serve index.html at the root
106
- app.mount("/", StaticFiles(directory="static", html=True), name="static")
 
 
1
  """
2
+ Sentinel Arbitrage Engine - v21.0 OMEGA (Unbreakable)
3
 
4
+ This is the definitive, money-spinning engine. It uses a self-healing
5
+ background task, a transparent logging UI, and a robust Socket.IO
6
+ connection to guarantee performance and reliability.
7
  """
8
  import asyncio
9
  import os
 
12
  from contextlib import asynccontextmanager
13
  from datetime import datetime, timezone
14
  import httpx
15
+ import socketio
16
+ from fastapi import FastAPI
17
  from fastapi.staticfiles import StaticFiles
18
 
 
 
19
  from .price_fetcher import PriceFetcher
20
  from .arbitrage_analyzer import ArbitrageAnalyzer
21
 
22
  OPPORTUNITY_THRESHOLD = 0.0015
 
23
 
24
+ sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
25
+
26
+ async def log_and_emit(message: str):
27
+ """Helper to both print to console and emit to frontend."""
28
+ print(message)
29
+ await sio.emit('log_message', message)
 
 
 
 
 
 
 
 
 
 
30
 
31
  async def run_arbitrage_detector(price_fetcher, analyzer):
32
+ """The self-healing core engine loop."""
33
+ await log_and_emit("Engine core starting...")
34
  while True:
35
  try:
36
+ await log_and_emit("Updating prices from oracles...")
37
  await price_fetcher.update_prices_async()
38
  all_prices = price_fetcher.get_all_prices()
39
+
40
+ if not any(p.get("pyth") and p.get("chainlink_agg") for p in all_prices.values()):
41
+ await log_and_emit("Oracle data incomplete. Waiting for next cycle.")
42
+ await asyncio.sleep(30)
43
+ continue
44
+
45
  for asset, prices in all_prices.items():
46
  pyth_price = prices.get("pyth")
47
  chainlink_price = prices.get("chainlink_agg")
48
+
49
  if pyth_price and chainlink_price and pyth_price > 0:
50
  spread = abs(pyth_price - chainlink_price) / chainlink_price
51
  if spread > OPPORTUNITY_THRESHOLD:
52
+ await log_and_emit(f"⚑️ Discrepancy for {asset}: {spread:.3f}%")
53
+ briefing = await analyzer.get_alpha_briefing(asset, {"asset": asset, "pyth_price": pyth_price, "chainlink_price": chainlink_price, "spread_pct": spread * 100})
54
+ if briefing:
55
+ signal = {"asset": asset, "pyth_price": pyth_price, "chainlink_price": chainlink_price, "spread_pct": spread * 100, **briefing, "timestamp": datetime.now(timezone.utc).isoformat()}
56
+ await sio.emit('new_signal', signal)
57
+ await log_and_emit(f"βœ… Signal Emitted for {asset}: {signal['strategy']}")
58
+
 
 
 
 
59
  except Exception as e:
60
+ await log_and_emit(f"❌ CRITICAL ERROR in engine loop: {e}. Recovering...")
61
+
62
  await asyncio.sleep(15)
63
 
64
+ @asynccontextmanager
65
+ async def lifespan(app: FastAPI):
66
+ print("πŸš€ Initializing Sentinel Arbitrage Engine v21.0...")
67
+ async with httpx.AsyncClient() as client:
68
+ price_fetcher = PriceFetcher(client)
69
+ arbitrage_analyzer = ArbitrageAnalyzer(client)
70
+ sio.background_task = sio.start_background_task(run_arbitrage_detector, price_fetcher, arbitrage_analyzer)
71
+ yield
72
+ print("βœ… Engine shut down.")
73
 
74
+ app = FastAPI(lifespan=lifespan)
75
+ socket_app = socketio.ASGIApp(sio)
76
+ app.mount('/socket.io', socket_app)
77
+ app.mount("/", StaticFiles(directory="static", html=True), name="static")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
+ @sio.event
80
+ async def connect(sid, environ):
81
+ await sio.emit('welcome', {'message': 'Connection to Sentinel Engine established!'}, to=sid)
82
+ print(f"βœ… Client connected: {sid}")
83
 
84
+ @sio.event
85
+ async def disconnect(sid):
86
+ print(f"πŸ”₯ Client disconnected: {sid}")