Jitendra12421 commited on
Commit
aa63688
·
verified ·
1 Parent(s): 0139139

Upload 6 files

Browse files
Files changed (5) hide show
  1. BTCUSD_1m.parquet +3 -0
  2. README.md +2 -1
  3. app.py +90 -49
  4. models.joblib +3 -0
  5. requirements.txt +3 -0
BTCUSD_1m.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e3348c6d825a3478a2396b036f8140ec92c99565a0b7397ca6e1dc64ef425f2c
3
+ size 12516035
README.md CHANGED
@@ -9,4 +9,5 @@ Set these Space variables:
9
  - `RECEIVER_URL=https://YOUR-NETLIFY-SITE.netlify.app/api/signal`
10
  The Space exposes the Gradio API function `tick`; Netlify's scheduled
11
  function calls it once per minute. It uses Delta public candles and sends only
12
- paper signal events.
 
 
9
  - `RECEIVER_URL=https://YOUR-NETLIFY-SITE.netlify.app/api/signal`
10
  The Space exposes the Gradio API function `tick`; Netlify's scheduled
11
  function calls it once per minute. It uses Delta public candles and sends only
12
+ paper signal events. `BTCUSD_1m.parquet` is the historical training dataset
13
+ used by the exact multi-horizon model.
app.py CHANGED
@@ -1,58 +1,99 @@
1
- import os, time, json
2
  from datetime import datetime, timezone
3
- import requests, pandas as pd
4
- import gradio as gr
 
 
 
 
 
 
5
  DELTA = os.getenv("DELTA_URL", "https://api.india.delta.exchange")
6
  SYMBOL = os.getenv("SYMBOL", "BTCUSD")
7
- RECEIVER_URL = os.environ.get("RECEIVER_URL", "").rstrip("/")
8
- last_signal = None
 
9
 
10
- def candles(limit=240):
 
11
  now = int(time.time())
12
- r = requests.get(DELTA + "/v2/history/candles", params={
13
- "resolution": "1m", "symbol": SYMBOL, "start": now-limit*60, "end": now
14
- }, timeout=20)
15
- r.raise_for_status()
16
- rows = r.json().get("result", [])
17
- return pd.DataFrame(rows).sort_values("time").reset_index(drop=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
 
19
- def signal():
20
- d = candles()
21
- if len(d) < 80: return None
22
- i = len(d) - 2
23
- c = d.close.astype(float); h = d.high.astype(float); l = d.low.astype(float)
24
- ret = c.pct_change()
25
- fast = ret.rolling(5).mean().iloc[i]
26
- slow = ret.rolling(20).mean().iloc[i]
27
- range_pct = ((h-l)/c).rolling(20).mean().iloc[i]
28
- if pd.isna(fast) or pd.isna(slow) or pd.isna(range_pct): return None
29
- edge = abs(float(fast-slow)) / max(float(range_pct), 1e-9)
30
- if edge < 0.25: return None
31
- side = "LONG" if fast > slow else "SHORT"
32
- price = float(c.iloc[i])
33
- return {"event":"signal", "symbol":SYMBOL, "side":side,
34
- "signal_time":datetime.fromtimestamp(int(d.time.iloc[i]),timezone.utc).isoformat(),
35
- "price":price, "score":edge, "source":"huggingface-paper-space"}
36
 
37
- def tick():
38
- global last_signal
39
- event = signal()
40
- if event and event["signal_time"] != (last_signal or {}).get("signal_time"):
41
- last_signal = event
42
- if RECEIVER_URL:
43
- try:
44
- response = requests.post(RECEIVER_URL, json=event, timeout=15)
45
- response.raise_for_status()
46
- except requests.RequestException as exc:
47
- return {"ok":False, "event":event, "sent":False,
48
- "receiver_url":RECEIVER_URL, "error":str(exc)}
49
- return {"ok":True,"event":event,"sent":bool(event and RECEIVER_URL)}
 
 
 
 
50
 
51
- with gr.Blocks() as demo:
52
- gr.Markdown("# BTC Paper Signal Worker")
53
- output = gr.JSON(label="Last tick")
54
- button = gr.Button("Run tick")
55
- button.click(tick, inputs=None, outputs=output, api_name="tick")
 
 
 
 
 
 
 
 
 
 
 
 
 
56
 
57
- if __name__ == "__main__":
58
- demo.queue().launch(server_name="0.0.0.0", server_port=7860, ssr_mode=False)
 
 
 
 
 
1
+ import os, time
2
  from datetime import datetime, timezone
3
+ from pathlib import Path
4
+ import requests, pandas as pd, numpy as np
5
+ import joblib
6
+ from sklearn.ensemble import HistGradientBoostingClassifier
7
+
8
+ ROOT = Path(__file__).parent
9
+ DATA = ROOT / "BTCUSD_1m.parquet"
10
+ LIVE_DATA = ROOT / "live_candles.parquet"
11
  DELTA = os.getenv("DELTA_URL", "https://api.india.delta.exchange")
12
  SYMBOL = os.getenv("SYMBOL", "BTCUSD")
13
+ RECEIVER_URL = os.getenv("RECEIVER_URL", "").rstrip("/")
14
+ MOVE, OFFSET, HORIZONS = .0008, .0004, (5, 15, 30)
15
+ MODELS = None
16
 
17
+ def get_candles():
18
+ """Incrementally backfill Delta candles from the newest stored timestamp."""
19
  now = int(time.time())
20
+ old = pd.read_parquet(LIVE_DATA) if LIVE_DATA.exists() else pd.DataFrame()
21
+ if len(old):
22
+ start = int(old.epoch.max()) + 60
23
+ else:
24
+ base = pd.read_parquet(DATA, columns=["timestamp"])
25
+ start = int(pd.to_datetime(base.timestamp, utc=True).astype("int64").max() // 10**9) + 60
26
+ rows=[]
27
+ while start < now:
28
+ end=min(now, start + 2000*60)
29
+ r=requests.get(DELTA + "/v2/history/candles", params={"resolution":"1m",
30
+ "symbol":SYMBOL,"start":start,"end":end}, timeout=20); r.raise_for_status()
31
+ batch=r.json().get("result", [])
32
+ if not batch: break
33
+ rows.extend(batch)
34
+ latest=max(int(q["time"]) for q in batch)
35
+ if latest < start: break
36
+ start=max(end, latest+60)
37
+ fresh=pd.DataFrame(rows).rename(columns={"time":"epoch"})
38
+ live=pd.concat([old,fresh],ignore_index=True) if len(old) or len(fresh) else pd.DataFrame()
39
+ if len(live):
40
+ live=live.drop_duplicates("epoch",keep="last").sort_values("epoch").reset_index(drop=True)
41
+ live.to_parquet(LIVE_DATA,index=False,compression="zstd")
42
+ return live
43
 
44
+ def make_features(d):
45
+ o,h,l,c,v=[d[k].to_numpy(float) for k in ("open","high","low","close","volume")]
46
+ r=np.maximum(h-l,1e-12); vm=pd.Series(v).rolling(30).median().to_numpy()
47
+ vwap=(pd.Series(c*v).rolling(30).sum()/pd.Series(v).rolling(30).sum()).to_numpy()
48
+ rm=lambda a,w:pd.Series(a).rolling(w).mean().to_numpy()
49
+ ret=c/np.roll(c,1)-1
50
+ ts = d.epoch if "epoch" in d.columns else pd.to_datetime(d.timestamp,utc=True).astype("int64") // 10**9
51
+ x=np.column_stack([ret,c/np.roll(c,3)-1,c/np.roll(c,10)-1,c/np.roll(c,30)-1,
52
+ rm(r,5)/c,rm(r,20)/c,(c-vwap)/c,v/(vm+1e-12),(c-o)/r,
53
+ (h-np.maximum(o,c))/r,(np.minimum(o,c)-l)/r,r/c,rm(ret,5),rm(ret,20),
54
+ pd.to_datetime(ts,unit="s",utc=True).dt.hour.to_numpy()/24])
55
+ return x
 
 
 
 
 
56
 
57
+ def train():
58
+ 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)
59
+ n=len(c); labels={}; fills={}
60
+ for si,side in enumerate((1,-1)):
61
+ entry=c*(1-OFFSET if side==1 else 1+OFFSET)
62
+ fills[si]=np.r_[((l[1:]<=entry[:-1]) if side==1 else (h[1:]>=entry[:-1])),False]
63
+ for horizon in HORIZONS:
64
+ y=np.zeros(n,bool)
65
+ for i in range(n-horizon-1):
66
+ 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))
67
+ labels[si,horizon]=y
68
+ good=np.isfinite(x).all(1); train_ix=np.arange(120,n,5); train_ix=train_ix[good[train_ix]]; models={}
69
+ for si in range(2):
70
+ for horizon in HORIZONS:
71
+ m=HistGradientBoostingClassifier(max_iter=120,learning_rate=.07,max_leaf_nodes=15,min_samples_leaf=80,l2_regularization=2,random_state=41+horizon+si)
72
+ m.fit(x[train_ix],labels[si,horizon][train_ix].astype(int)); models[si,horizon]=m
73
+ return models
74
 
75
+ def tick():
76
+ global MODELS
77
+ if MODELS is None:
78
+ MODELS=joblib.load(ROOT / "models.joblib") if (ROOT / "models.joblib").exists() else train()
79
+ live=get_candles(); hist=pd.read_parquet(DATA).tail(5000)
80
+ hist["epoch"]=(pd.to_datetime(hist.timestamp,utc=True).astype("int64")//10**9)
81
+ d=pd.concat([hist,live],ignore_index=True).drop_duplicates("epoch",keep="last").sort_values("epoch").reset_index(drop=True)
82
+ i=len(d)-2; x=make_features(d); probs=np.zeros((2,3))
83
+ for si in range(2):
84
+ for hi,horizon in enumerate(HORIZONS): probs[si,hi]=MODELS[si,horizon].predict_proba(x[i:i+1])[0,1]
85
+ score=3/(1/(probs+1e-6)).sum(axis=1); si=int(np.argmax(score)); side="LONG" if si==0 else "SHORT"
86
+ row=d.iloc[i]; event={"event":"tick","symbol":SYMBOL,"candle":{"time":datetime.fromtimestamp(int(row.epoch),timezone.utc).isoformat(),"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"}
87
+ if np.isfinite(x[i]).all(): event["signal"]={"side":side,"price":float(row.close),"score":float(score[si]),"offset":OFFSET,"target":MOVE}
88
+ event["data_last_updated"] = event["candle"]["time"]
89
+ event["live_cache_rows"] = int(len(live))
90
+ if RECEIVER_URL:
91
+ r=requests.post(RECEIVER_URL,json=event,timeout=15); r.raise_for_status()
92
+ return {"ok":True,"sent":bool(RECEIVER_URL),"event":event}
93
 
94
+ if __name__=="__main__":
95
+ import gradio as gr
96
+ with gr.Blocks() as demo:
97
+ gr.Markdown("# Exact Hybrid BTC Paper Trader")
98
+ out=gr.JSON(); gr.Button("Run exact tick").click(tick,outputs=out,api_name="tick")
99
+ demo.queue().launch(server_name="0.0.0.0",server_port=7860,ssr_mode=False)
models.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8dfc89e4a117846d9b7a6202d0ca59b980541979522123a7c8529eca9d7cf7ee
3
+ size 590138
requirements.txt CHANGED
@@ -1,3 +1,6 @@
1
  requests
2
  pandas
 
 
 
3
  gradio==6.23.1
 
1
  requests
2
  pandas
3
+ pyarrow
4
+ scikit-learn
5
+ joblib
6
  gradio==6.23.1