EA/EA
2025-09-02 12:36:43 +00:00

213 lines
No EOL
6.2 KiB
Text

backend/
├─ main.py
├─ engine.py
├─ mt5_adapter.py
├─ strategies.py
├─ backtest.py
├─ requirements.txt
└─ .env.example
from fastapi import FastAPI, BackgroundTasks
from pydantic import BaseModel
from dotenv import load_dotenv
import os
load_dotenv()
from engine import Engine
app = FastAPI(title="FX Bot - Demo (MT5 Adapter)")
engine = Engine()
class StartRequest(BaseModel):
adapter: str = "mt5"
demo: bool = True
@app.post("/start")
async def start(req: StartRequest, bg: BackgroundTasks):
if engine.running:
return {"ok": False, "msg": "Engine already running"}
engine.select_adapter(req.adapter, demo=req.demo)
bg.add_task(engine.run)
return {"ok": True, "adapter": req.adapter}
@app.post("/stop")
def stop():
engine.stop()
return {"ok": True}
@app.get("/status")
def status():
return engine.get_status()
@app.post("/backtest")
def backtest(payload: dict):
return engine.backtest(payload)
import asyncio
from datetime import datetime
from mt5_adapter import MT5Adapter
from strategies import ema_atr_strategy
class Engine:
def _init_(self):
self.adapter = None
self.running = False
self.equity = 10000.0
self.open_positions = []
self.logs = []
self.adapter_name = None
self._stop_requested = False
self.max_concurrent = 2
self.risk_per_trade = 0.005
def select_adapter(self, name: str, demo: bool=True):
name = name.lower()
self.adapter_name = name
if name == "mt5":
self.adapter = MT5Adapter(demo=demo, engine=self)
else:
raise ValueError("Unknown adapter")
async def _run_loop(self):
self.running = True
self._stop_requested = False
self.logs.append(f"{datetime.utcnow().isoformat()}Z starting engine with {self.adapter_name}")
await self.adapter.connect()
try:
while not self._stop_requested:
df = await self.adapter.get_latest_candles("EURUSD", "M15")
signal = ema_atr_strategy(df)
if signal != 0:
if len(self.open_positions) < self.max_concurrent:
volume = 0.01
order = await self.adapter.create_market_order("EURUSD", signal, volume=volume)
self.open_positions.append(order)
self.logs.append(f"{datetime.utcnow().isoformat()}Z order placed: {order}")
await asyncio.sleep(5)
finally:
await self.adapter.disconnect()
self.running = False
self.logs.append(f"{datetime.utcnow().isoformat()}Z engine stopped")
def run(self):
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(self._run_loop())
loop.close()
def stop(self):
self._stop_requested = True
def get_status(self):
return {
"running": self.running,
"mode": self.adapter_name,
"equity": self.equity,
"open_positions": len(self.open_positions),
"logs": self.logs[-200:]
}
def backtest(self, payload):
from backtest import run_backtest
return run_backtest(payload)
import asyncio
from datetime import datetime
from mt5_adapter import MT5Adapter
from strategies import ema_atr_strategy
class Engine:
def _init_(self):
self.adapter = None
self.running = False
self.equity = 10000.0
self.open_positions = []
self.logs = []
self.adapter_name = None
self._stop_requested = False
self.max_concurrent = 2
self.risk_per_trade = 0.005
def select_adapter(self, name: str, demo: bool=True):
name = name.lower()
self.adapter_name = name
if name == "mt5":
self.adapter = MT5Adapter(demo=demo, engine=self)
else:
raise ValueError("Unknown adapter")
async def _run_loop(self):
self.running = True
import os, time, pandas as pd
import MetaTrader5 as mt5
from datetime import datetime
class MT5Adapter:
def _init_(self, demo=True, engine=None):
self.demo = demo
self.engine = engine
self.login = int(os.getenv("MT5_LOGIN") or 0)
self.password = os.getenv("MT5_PASSWORD")
self.server = os.getenv("MT5_SERVER")
async def connect(self):
if not mt5.initialize():
raise RuntimeError("MT5 initialize failed")
if self.login:
logged = mt5.login(self.login, password=self.password, server=self.server)
if not logged:
last = mt5.last_error()
raise RuntimeError(f"MT5 login failed: {last}")
time.sleep(1)
return True
async …
import numpy as np
def ema(series, n):
return series.ewm(span=n, adjust=False).mean()
def atr(df, n=14):
h, l, c = df["high"], df["low"], df["close"]
prev = c.shift(1)
tr = np.maximum(h - l, np.maximum((h - prev).abs(), (l - prev).abs()))
return tr.rolling(n).mean()
def ema_atr_strategy(df):
df = df.copy().dropna()
if len(df) < 60:
return 0
df["ema_fast"] = ema(df["close"], 20)
df["ema_slow"] = ema(df["close"], 50)
df["atr"] = atr(df, 14)
last = df.iloc[-1]
prev = df.iloc[-2]
if last["ema_fast"] > last["ema_slow"] and prev["ema_fast"] <= prev["ema_slow"]:
return 1
if last["ema_fast"] < last["ema_slow"] and prev["ema_fast"] >= prev["ema_slow"]:
return -1
return 0
import pandas as pd
import numpy as np
def run_backtest(payload):
start = payload.get("start", "2024-01-01")
end = payload.get("end", "2024-02-01")
freq = "15T"
rng = pd.date_range(start=start, end=end, freq=freq)
px = 1.0 + np.cumsum(np.random.normal(0, 0.0005, len(rng)))
df = pd.DataFrame({"time": rng, "open": px, "high": px + 0.0005, "low": px - 0.0005, "close": px}).set_index("time")
trades = []
for i in range(5):
trades.append({"time": str(rng[i]), "pnl": float(np.random.normal(5,20))})
stats = {"trades": len(trades), "net_pnl": sum(t["pnl"] for t in trades), "win_rate": 0.5}
return {"stats": stats, "trades": trades}
fastapi
uvicorn[standard]
pydantic
numpy
pandas
python-dotenv
MetaTrader5
requests
websockets