Jitendra12421's picture
Upload app.py
b5948d3 verified
Raw
History Blame Contribute Delete
6.19 kB
import os, time
from datetime import datetime, timezone
from pathlib import Path
import requests, pandas as pd, numpy as np
import joblib
from sklearn.ensemble import HistGradientBoostingClassifier
ROOT = Path(__file__).parent
DATA = ROOT / "BTCUSD_1m.parquet"
LIVE_DATA = ROOT / "live_candles.parquet"
DELTA = os.getenv("DELTA_URL", "https://api.india.delta.exchange")
SYMBOL = os.getenv("SYMBOL", "BTCUSD")
RECEIVER_URL = os.getenv("RECEIVER_URL", "").rstrip("/")
MOVE, OFFSET, HORIZONS = .0008, .0004, (5, 15, 30)
MODELS = None
def epoch_seconds(value):
value = float(value)
if value > 1e14: return int(value / 1e6)
if value > 1e11: return int(value / 1e3)
# Some previously cached Delta responses were stored as epoch milliseconds
# with three digits dropped (e.g. 1786405). Restore them to epoch seconds.
if 1e6 < value < 1e9: return int(value * 1000)
return int(value)
def get_candles():
"""Incrementally backfill Delta candles from the newest stored timestamp."""
now = int(time.time())
old = pd.read_parquet(LIVE_DATA) if LIVE_DATA.exists() else pd.DataFrame()
if len(old) and epoch_seconds(old.epoch.max()) < 1577836800:
old = pd.DataFrame()
if len(old):
start = epoch_seconds(old.epoch.max()) + 60
else:
base = pd.read_parquet(DATA, columns=["timestamp"])
start = int(pd.to_datetime(base.timestamp, utc=True).astype("int64").max() // 10**9) + 60
rows=[]
while start < now:
end=min(now, start + 2000*60)
r=requests.get(DELTA + "/v2/history/candles", params={"resolution":"1m",
"symbol":SYMBOL,"start":start,"end":end}, timeout=20); r.raise_for_status()
batch=r.json().get("result", [])
if not batch: break
rows.extend(batch)
latest=max(epoch_seconds(q["time"]) for q in batch)
if latest < start: break
start=max(end, latest+60)
fresh=pd.DataFrame(rows).rename(columns={"time":"epoch"})
if len(fresh): fresh["epoch"] = fresh["epoch"].map(epoch_seconds)
live=pd.concat([old,fresh],ignore_index=True) if len(old) or len(fresh) else pd.DataFrame()
if len(live):
live=live.drop_duplicates("epoch",keep="last").sort_values("epoch").reset_index(drop=True)
live.to_parquet(LIVE_DATA,index=False,compression="zstd")
return live
def make_features(d):
o,h,l,c,v=[d[k].to_numpy(float) for k in ("open","high","low","close","volume")]
r=np.maximum(h-l,1e-12); vm=pd.Series(v).rolling(30).median().to_numpy()
vwap=(pd.Series(c*v).rolling(30).sum()/pd.Series(v).rolling(30).sum()).to_numpy()
rm=lambda a,w:pd.Series(a).rolling(w).mean().to_numpy()
ret=c/np.roll(c,1)-1
ts = d.epoch if "epoch" in d.columns else pd.to_datetime(d.timestamp,utc=True).astype("int64") // 10**9
x=np.column_stack([ret,c/np.roll(c,3)-1,c/np.roll(c,10)-1,c/np.roll(c,30)-1,
rm(r,5)/c,rm(r,20)/c,(c-vwap)/c,v/(vm+1e-12),(c-o)/r,
(h-np.maximum(o,c))/r,(np.minimum(o,c)-l)/r,r/c,rm(ret,5),rm(ret,20),
pd.to_datetime(ts,unit="s",utc=True).dt.hour.to_numpy()/24])
return x
def train():
d=pd.read_parquet(DATA); d["epoch"]=(pd.to_datetime(d.timestamp,utc=True).astype("int64")//10**9); x=make_features(d); c=d.close.to_numpy(float); h=d.high.to_numpy(float); l=d.low.to_numpy(float)
n=len(c); labels={}; fills={}
for si,side in enumerate((1,-1)):
entry=c*(1-OFFSET if side==1 else 1+OFFSET)
fills[si]=np.r_[((l[1:]<=entry[:-1]) if side==1 else (h[1:]>=entry[:-1])),False]
for horizon in HORIZONS:
y=np.zeros(n,bool)
for i in range(n-horizon-1):
if fills[si][i]: y[i]=(np.max(h[i+1:i+horizon+1])>=entry[i]*(1+MOVE) if side==1 else np.min(l[i+1:i+horizon+1])<=entry[i]*(1-MOVE))
labels[si,horizon]=y
good=np.isfinite(x).all(1); train_ix=np.arange(120,n,5); train_ix=train_ix[good[train_ix]]; models={}
for si in range(2):
for horizon in HORIZONS:
m=HistGradientBoostingClassifier(max_iter=120,learning_rate=.07,max_leaf_nodes=15,min_samples_leaf=80,l2_regularization=2,random_state=41+horizon+si)
m.fit(x[train_ix],labels[si,horizon][train_ix].astype(int)); models[si,horizon]=m
return models
def tick():
global MODELS
if MODELS is None:
if (ROOT / "models.joblib").exists():
try:
MODELS=joblib.load(ROOT / "models.joblib")
except Exception:
MODELS=train()
else:
MODELS=train()
live=get_candles(); hist=pd.read_parquet(DATA).tail(5000)
hist["epoch"]=(pd.to_datetime(hist.timestamp,utc=True).astype("int64")//10**9)
d=pd.concat([hist,live],ignore_index=True).drop_duplicates("epoch",keep="last").sort_values("epoch").reset_index(drop=True)
i=len(d)-2; x=make_features(d); probs=np.zeros((2,3))
for si in range(2):
for hi,horizon in enumerate(HORIZONS): probs[si,hi]=MODELS[si,horizon].predict_proba(x[i:i+1])[0,1]
score=3/(1/(probs+1e-6)).sum(axis=1); si=int(np.argmax(score)); side="LONG" if si==0 else "SHORT"
row=d.iloc[i]; candle_epoch=epoch_seconds(row.epoch); event={"event":"tick","symbol":SYMBOL,"candle":{"time":datetime.fromtimestamp(candle_epoch,timezone.utc).isoformat(),"epoch":candle_epoch,"open":float(row.open),"high":float(row.high),"low":float(row.low),"close":float(row.close)},"signal":None,"probs":probs.tolist(),"score":float(score[si]),"source":"exact-hybrid-multihorizon"}
if np.isfinite(x[i]).all(): event["signal"]={"side":side,"price":float(row.close),"score":float(score[si]),"offset":OFFSET,"target":MOVE}
event["data_last_updated"] = event["candle"]["time"]
event["live_cache_rows"] = int(len(live))
if RECEIVER_URL:
r=requests.post(RECEIVER_URL,json=event,timeout=15); r.raise_for_status()
return {"ok":True,"sent":bool(RECEIVER_URL),"event":event}
if __name__=="__main__":
import gradio as gr
with gr.Blocks() as demo:
gr.Markdown("# Exact Hybrid BTC Paper Trader")
out=gr.JSON(); gr.Button("Run exact tick").click(tick,outputs=out,api_name="tick")
demo.queue().launch(server_name="0.0.0.0",server_port=7860,ssr_mode=False)