-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.py
More file actions
121 lines (101 loc) · 3.98 KB
/
Copy pathexecutor.py
File metadata and controls
121 lines (101 loc) · 3.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""
executor.py — Execution Layer
Handles all Binance API calls: data fetching, market orders, OCO orders.
Automatically routes to Testnet or Live based on config.
"""
import pandas as pd
import logging
from binance.client import Client
from binance.exceptions import BinanceAPIException
from config import (
API_KEY, API_SECRET, USE_TESTNET,
SYMBOL, INTERVAL, BACKTEST_DAYS
)
logger = logging.getLogger(__name__)
def get_client() -> Client:
"""Create and return a Binance client (testnet or live)."""
client = Client(API_KEY, API_SECRET, testnet=USE_TESTNET)
if USE_TESTNET:
logger.info("Connected to Binance TESTNET")
else:
logger.warning("Connected to Binance LIVE — real funds at risk!")
return client
def fetch_historical_data(client: Client, days: int = BACKTEST_DAYS) -> pd.DataFrame:
"""
Download OHLCV candlestick data and return a clean DataFrame.
Columns: open, high, low, close, volume (all float), index = datetime
"""
raw = client.get_historical_klines(
SYMBOL, INTERVAL, f"{days} days ago UTC"
)
df = pd.DataFrame(raw, columns=[
"open_time", "open", "high", "low", "close", "volume",
"close_time", "quote_volume", "trades",
"taker_buy_base", "taker_buy_quote", "ignore"
])
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
df.set_index("open_time", inplace=True)
for col in ["open", "high", "low", "close", "volume"]:
df[col] = df[col].astype(float)
df = df[["open", "high", "low", "close", "volume"]]
logger.info(f"Fetched {len(df)} candles for {SYMBOL} ({INTERVAL})")
return df
def fetch_latest_candles(client: Client, limit: int = 100) -> pd.DataFrame:
"""Fetch the most recent N candles for live signal generation."""
raw = client.get_klines(symbol=SYMBOL, interval=INTERVAL, limit=limit)
df = pd.DataFrame(raw, columns=[
"open_time", "open", "high", "low", "close", "volume",
"close_time", "quote_volume", "trades",
"taker_buy_base", "taker_buy_quote", "ignore"
])
df["open_time"] = pd.to_datetime(df["open_time"], unit="ms")
df.set_index("open_time", inplace=True)
for col in ["open", "high", "low", "close", "volume"]:
df[col] = df[col].astype(float)
return df[["open", "high", "low", "close", "volume"]]
def place_market_buy(client: Client, quantity: float) -> dict | None:
"""Place a market BUY order. Returns the order dict or None on failure."""
try:
order = client.order_market_buy(symbol=SYMBOL, quantity=quantity)
logger.info(f"MARKET BUY placed: {order}")
return order
except BinanceAPIException as e:
logger.error(f"Market buy failed: {e}")
return None
def place_oco_sell(
client: Client,
quantity: float,
take_profit: float,
stop_loss: float,
stop_limit_offset: float = 100.0
) -> dict | None:
"""
Place an OCO (One-Cancels-the-Other) SELL order.
One leg hits take_profit (limit).
Other leg triggers at stop_loss (stop-limit).
Whichever fills first cancels the other automatically.
"""
stop_limit_price = round(stop_loss - stop_limit_offset, 2)
try:
order = client.create_oco_order(
symbol=SYMBOL,
side="SELL",
quantity=quantity,
price=str(round(take_profit, 2)),
stopPrice=str(round(stop_loss, 2)),
stopLimitPrice=str(stop_limit_price),
stopLimitTimeInForce="GTC"
)
logger.info(f"OCO SELL placed: TP={take_profit}, SL={stop_loss}")
return order
except BinanceAPIException as e:
logger.error(f"OCO sell failed: {e}")
return None
def get_account_balance(client: Client, asset: str = "USDT") -> float:
"""Return the free balance for the given asset."""
try:
balance = client.get_asset_balance(asset=asset)
return float(balance["free"])
except BinanceAPIException as e:
logger.error(f"Balance check failed: {e}")
return 0.0