Files

131 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Data sources for Kronos advisor.
Two flavours:
- TradernetHlocSource: production, uses Tradernet API (needs PRIVATE_KEY)
- BinancePublicSource: public, no auth, for offline tests & fallback
Both expose the same interface:
fetch_ohlcv(lookback: int) -> pd.DataFrame
with columns ['open','high','low','close','volume'] and DatetimeIndex
"""
from __future__ import annotations
import logging
import time
from typing import Optional
import pandas as pd
import requests
logger = logging.getLogger(__name__)
# Map our SYMBOL (Tradernet format) → Binance symbol
SYMBOL_MAP_TRADERNET_TO_BINANCE = {
"BTC-USDT.IMEX": "BTCUSDT",
"ETH-USDT.IMEX": "ETHUSDT",
"SOL-USDT.IMEX": "SOLUSDT",
"TON-USDT.IMEX": "TONUSDT",
}
class TradernetHlocSource:
"""
Тянет OHLCV с Tradernet (продакшн).
Требует приватный ключ → не используй в офлайн-тестах.
"""
def __init__(self, api, symbol: str, tf_min: int = 60):
self.api = api # экземпляр TradernetAPI
self.symbol = symbol
self.tf_min = tf_min
def fetch_ohlcv(self, lookback: int = 500, timeout: float = 20.0) -> pd.DataFrame:
# Берём запас побольше — на случай пропусков
# ВАЖНО: Tradernet по (date_from="", date_to="", count=N) возвращает
# САМЫЕ СТАРЫЕ N свечей, а не свежие. Подставляем явный date_to=NOW.
from datetime import datetime, timedelta
now = datetime.now()
# lookback свечей × tf_min минут = сколько часов назад начинать
hours_back = max(1, int(lookback * self.tf_min / 60) + 1)
date_from = (now - timedelta(hours=hours_back)).strftime("%d.%m.%Y %H:%M")
date_to = now.strftime("%d.%m.%Y %H:%M")
resp = self.api.get_hloc_sync(
ticker=self.symbol,
timeframe_min=self.tf_min,
date_from=date_from,
date_to=date_to,
count=0, # count=0 — берём только между датами
timeout=timeout,
)
if not resp or "hloc" not in resp:
raise RuntimeError(f"Tradernet getHloc returned empty for {self.symbol}")
hloc_map = resp["hloc"]
x_map = resp.get("xSeries", {})
vl_map = resp.get("vl", {})
rows = hloc_map.get(self.symbol) or next(iter(hloc_map.values()))
ts_list = x_map.get(self.symbol) or next(iter(x_map.values()), [])
vols = vl_map.get(self.symbol) or next(iter(vl_map.values()), []) if vl_map else []
if not rows or not ts_list:
raise RuntimeError(f"Tradernet getHloc: empty series for {self.symbol}")
df = pd.DataFrame(rows, columns=["open", "high", "low", "close"])
df["volume"] = vols if len(vols) == len(df) else 0.0
# xSeries — unix-секунды
df.index = pd.to_datetime(ts_list, unit="s", utc=True).tz_convert(None)
df = df.sort_index()
return df.tail(lookback)
class BinancePublicSource:
"""
Публичный API Binance (https://api.binance.com). Без ключей.
Используется для offline-тестов Kronos и как fallback, если Tradernet недоступен.
"""
BASE_URL = "https://api.binance.com"
def __init__(self, symbol: str, tf_min: int = 60):
# symbol в формате Binance: BTCUSDT
self.symbol = symbol
self.tf_min = tf_min
@classmethod
def from_tradernet(cls, tradernet_symbol: str, tf_min: int = 60) -> "BinancePublicSource":
"""Конвертирует Tradernet-символ в Binance-символ."""
bsym = SYMBOL_MAP_TRADERNET_TO_BINANCE.get(tradernet_symbol)
if not bsym:
raise ValueError(f"No Binance mapping for {tradernet_symbol}")
return cls(bsym, tf_min)
def fetch_ohlcv(self, lookback: int = 500, timeout: float = 20.0) -> pd.DataFrame:
# Binance: 1m/3m/5m/15m/30m/1h/2h/4h/... (не "60m", а "1h")
interval_map = {1: "1m", 3: "3m", 5: "5m", 15: "15m", 30: "30m",
60: "1h", 120: "2h", 240: "4h", 360: "6h",
720: "12h", 1440: "1d"}
interval = interval_map.get(self.tf_min, f"{self.tf_min}m")
limit = min(1000, lookback)
url = f"{self.BASE_URL}/api/v3/klines"
params = {"symbol": self.symbol, "interval": interval, "limit": limit}
r = requests.get(url, params=params, timeout=timeout)
r.raise_for_status()
data = r.json()
if not data:
raise RuntimeError(f"Binance returned empty for {self.symbol}")
cols = ["open_time", "open", "high", "low", "close", "volume",
"close_time", "quote_vol", "trades", "taker_buy_base", "taker_buy_quote", "_"]
df = pd.DataFrame(data, columns=cols)
for c in ("open", "high", "low", "close", "volume"):
df[c] = df[c].astype(float)
df.index = pd.to_datetime(df["open_time"], unit="ms", utc=True)
df.index = df.index.tz_convert(None)
df = df[["open", "high", "low", "close", "volume"]].sort_index()
return df.tail(lookback)