feat: Implementa Fase 5 - DOM, VWAP, Audio Alerts e Historical Browser

This commit is contained in:
Thiago Moura
2026-06-07 23:17:08 -03:00
parent 54f269b0ce
commit 764822da46
8 changed files with 622 additions and 26 deletions
+52 -3
View File
@@ -14,6 +14,7 @@ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")
from config import settings
from backend.aggregator import Aggregator
from backend.mt5_collector import MT5Collector
from backend.trading import send_buy_signal, send_sell_signal
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger("server")
@@ -23,7 +24,7 @@ aggregator = Aggregator(tick_size=1.0)
active_connections: Set[WebSocket] = set()
collector_task: Optional[asyncio.Task] = None
def broadcast_update(active_json: dict, closed_json: Optional[dict]):
def broadcast_update(active_json: dict, closed_json: Optional[dict], dom_data: Optional[list] = None):
"""
Callback executed by MT5Collector when a new tick is processed.
Only broadcasts when a cluster closes (not every tick) to avoid flooding during replay.
@@ -34,7 +35,23 @@ def broadcast_update(active_json: dict, closed_json: Optional[dict]):
message = {
"type": "tick",
"active": active_json,
"closed": closed_json
"closed": closed_json,
"dom": dom_data
}
async def safe_send(ws: WebSocket, msg: dict):
try:
await ws.send_json(msg)
except Exception:
active_connections.discard(ws)
for connection in list(active_connections):
asyncio.create_task(safe_send(connection, message))
def broadcast_big_trade(trade_data: dict):
if not active_connections:
return
message = {
"type": "big_trade",
"data": trade_data
}
async def safe_send(ws: WebSocket, msg: dict):
try:
@@ -45,7 +62,7 @@ def broadcast_update(active_json: dict, closed_json: Optional[dict]):
asyncio.create_task(safe_send(connection, message))
# Initialize MT5 Collector
collector = MT5Collector(aggregator, on_update_callback=broadcast_update)
collector = MT5Collector(aggregator, on_update_callback=broadcast_update, on_big_trade_callback=broadcast_big_trade)
async def _start_collector_delayed():
"""Wait a moment for the server to fully start, then begin polling MT5."""
@@ -96,6 +113,7 @@ async def get_config():
"volume_max": settings.CLUSTER_VOLUME_MAX,
"range_points": settings.CLUSTER_RANGE_POINTS,
"time_seconds": settings.CLUSTER_TIME_SECONDS,
"symbol": settings.MT5_SYMBOL,
}
@app.post("/config")
@@ -113,6 +131,37 @@ async def update_config(update: ConfigUpdate):
logger.info(f"Config updated: mode={settings.CLUSTER_CLOSE_MODE}, delta_max={settings.CLUSTER_DELTA_MAX}")
return {"ok": True}
@app.post("/trade/buy")
async def trade_buy():
success = send_buy_signal()
return {"success": success}
@app.post("/trade/sell")
async def trade_sell():
success = send_sell_signal()
return {"success": success}
class HistoryLoadRequest(BaseModel):
start_time: str
end_time: str
@app.post("/history/load")
async def load_history(req: HistoryLoadRequest):
from datetime import datetime
try:
start_dt = datetime.fromisoformat(req.start_time)
end_dt = datetime.fromisoformat(req.end_time)
await collector.load_historical_range(start_dt, end_dt)
return {"success": True}
except Exception as e:
logger.error(f"Error loading history: {e}")
return {"success": False, "error": str(e)}
@app.post("/history/live")
async def return_to_live():
collector.return_to_live()
return {"success": True}
@app.get("/history")
async def get_history():
"""Returns the buffer of historical closed clusters."""