-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtaker_meanrev.py
More file actions
335 lines (275 loc) · 13 KB
/
Copy pathtaker_meanrev.py
File metadata and controls
335 lines (275 loc) · 13 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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
#!/usr/bin/env python3
"""Taker-MeanRev: Patient mean-reversion trader that fades overextended moves and penny-jumps spreads."""
import time
import requests
import signal
import sys
API = "http://localhost:7070"
KEY = "t2_41033909673f4e8f"
HDR = {"X-API-Key": KEY}
# Strategy parameters
FADE_THRESHOLD = 3.00 # Minimum deviation from reference to fade
FADE_BIG = 5.00 # Big deviation threshold
FADE_SIZE_SMALL = 25 # Size for $3-5 deviation
FADE_SIZE_BIG = 45 # Size for $5+ deviation
FADE_OFFSET = 0.50 # Limit order offset from reference
PENNY_SPREAD_MIN = 2.00 # Minimum spread to penny-jump
PENNY_JUMP = 0.10 # How much to improve the best bid/ask
PENNY_SIZE = 12 # Size per penny-jump order
TARGET_COINS = 200 # Neutral position
LEAN_THRESHOLD = 50 # Start leaning when 50+ away from target
MAX_COINS = 350
MIN_COINS = 80
NEWS_COOLDOWN = 12 # Seconds to wait after news
LOOP_INTERVAL = 5 # Main loop interval
# State
my_orders = {} # order_id -> order info
last_news_time = 0
last_news_id = None
running = True
def signal_handler(sig, frame):
global running
print("\n[SHUTDOWN] Cancelling all orders...")
cancel_all()
running = False
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
def get(endpoint):
try:
r = requests.get(f"{API}{endpoint}", headers=HDR, timeout=3)
return r.json()
except Exception as e:
print(f" [ERR] GET {endpoint}: {e}")
return None
def post_order(side, order_type, price, quantity):
body = {"side": side, "type": order_type, "quantity": int(quantity)}
if order_type == "limit":
body["price"] = round(price, 2)
try:
r = requests.post(f"{API}/order", headers=HDR, json=body, timeout=3)
data = r.json()
if "error" in data:
print(f" [ORDER ERR] {side} {quantity}@{price}: {data['error']}")
return None
oid = data.get("order_id") or data.get("id")
filled = data.get("filled_quantity", 0)
print(f" [ORDER] {side.upper()} {quantity} @ ${price:.2f} ({order_type}) -> id={oid}, filled={filled}")
if oid:
my_orders[oid] = {"side": side, "price": price, "qty": quantity, "time": time.time()}
return data
except Exception as e:
print(f" [ERR] POST /order: {e}")
return None
def cancel_order(oid):
try:
r = requests.delete(f"{API}/order/{oid}", headers=HDR, timeout=3)
my_orders.pop(oid, None)
return r.json()
except:
my_orders.pop(oid, None)
return None
def cancel_all():
try:
r = requests.delete(f"{API}/cancel_all", headers=HDR, timeout=3)
my_orders.clear()
return r.json()
except:
my_orders.clear()
return None
def check_news():
"""Check for new news. Returns (is_new, sentiment) where sentiment is 'bullish', 'bearish', or 'neutral'."""
global last_news_time, last_news_id
data = get("/news")
if not data or not isinstance(data, list) or len(data) == 0:
return False, "neutral"
latest = data[-1] if isinstance(data, list) else data
nid = latest.get("id") or latest.get("timestamp") or str(latest)
if nid == last_news_id:
return False, "neutral"
last_news_id = nid
headline = str(latest.get("headline", "") or latest.get("title", "") or latest.get("text", "")).lower()
bullish_words = ["surge", "rally", "bull", "up", "gain", "profit", "growth", "positive", "buy", "record high",
"strong", "boom", "soar", "jump", "increase", "optimis", "upgrade"]
bearish_words = ["crash", "drop", "bear", "down", "loss", "decline", "negative", "sell", "record low",
"weak", "bust", "plunge", "fall", "decrease", "pessimis", "downgrade", "fear", "panic",
"risk", "warn", "crisis", "concern"]
bull_count = sum(1 for w in bullish_words if w in headline)
bear_count = sum(1 for w in bearish_words if w in headline)
if bull_count > bear_count:
sentiment = "bullish"
elif bear_count > bull_count:
sentiment = "bearish"
else:
sentiment = "neutral"
last_news_time = time.time()
print(f" [NEWS] '{headline[:60]}' -> {sentiment}")
return True, sentiment
def get_position_bias(coins):
"""Return a size adjustment based on position. Positive = lean sell, negative = lean buy."""
if coins > TARGET_COINS + LEAN_THRESHOLD:
return min((coins - TARGET_COINS) / 5, 15) # Lean sell, up to 15 extra
elif coins < TARGET_COINS - LEAN_THRESHOLD:
return -min((TARGET_COINS - coins) / 5, 15) # Lean buy, up to 15 extra
return 0
def main_loop():
global my_orders
print("=" * 60)
print(" TAKER-MEANREV: Fade Extremes + Penny Jump")
print(" Waiting for momentum traders to make the first move...")
print("=" * 60)
cycle = 0
last_penny_refresh = 0
while running:
cycle += 1
time.sleep(LOOP_INTERVAL)
# Gather market data
ticker = get("/ticker")
orderbook = get("/orderbook")
account = get("/account")
ref_data = get("/reference")
if not ticker or not orderbook or not account or not ref_data:
print(f"[{cycle}] Data fetch failed, skipping...")
continue
reference = ref_data.get("price", 0) or ref_data.get("reference", 0) or ticker.get("reference", 0)
if isinstance(ref_data, dict) and "history" in ref_data:
reference = ref_data.get("price", reference)
if reference == 0:
reference = ticker.get("reference", 100)
last_price = ticker.get("last")
vwap = ticker.get("vwap_5m")
coins = account.get("coins", 200)
balance = account.get("balance", 50000)
pnl = account.get("pnl", 0)
best_bid = orderbook.get("best_bid")
best_ask = orderbook.get("best_ask")
spread = orderbook.get("spread")
# Use best available price signal
price = last_price or vwap or reference
print(f"\n[{cycle}] ref=${reference:.2f} last={f'${last_price:.2f}' if last_price else 'None'} "
f"bid={f'${best_bid:.2f}' if best_bid else 'None'} ask={f'${best_ask:.2f}' if best_ask else 'None'} "
f"spread={f'${spread:.2f}' if spread else 'None'} | coins={coins:.0f} bal=${balance:.0f} pnl=${pnl:.2f}")
# Check news
is_new_news, sentiment = check_news()
if is_new_news:
print(f" [WAIT] News detected ({sentiment}), cooling down {NEWS_COOLDOWN}s...")
# Cancel penny-jump orders — the book is about to move
cancel_all()
time.sleep(NEWS_COOLDOWN)
# After cooldown, re-fetch and look for fade opportunity
ticker = get("/ticker")
ref_data = get("/reference")
orderbook = get("/orderbook")
if not ticker or not ref_data:
continue
reference = ref_data.get("price", reference)
if isinstance(ref_data, dict) and "history" in ref_data:
reference = ref_data.get("price", reference)
last_price = ticker.get("last")
price = last_price or reference
best_bid = orderbook.get("best_bid") if orderbook else None
best_ask = orderbook.get("best_ask") if orderbook else None
print(f" [POST-NEWS] ref=${reference:.2f} last={f'${price:.2f}' if price else 'None'}")
# Position bias
bias = get_position_bias(coins)
if abs(bias) > 0:
direction = "SELL" if bias > 0 else "BUY"
print(f" [BIAS] Position {coins:.0f} -> leaning {direction} by {abs(bias):.0f}")
# Safety: hard position limits
can_buy = coins < MAX_COINS
can_sell = coins > MIN_COINS
# === STRATEGY 1: Fade overextensions ===
if price and reference:
deviation = price - reference
if deviation > FADE_BIG and can_sell:
# Big overshoot — aggressive sell
size = int(FADE_SIZE_BIG + max(bias, 0))
limit_price = reference + FADE_OFFSET
# If there's a best bid above our limit, use a slightly better price
if best_bid and best_bid > limit_price:
limit_price = best_bid - 0.05
print(f" [FADE] BIG SELL: price ${price:.2f} is ${deviation:.2f} above ref")
post_order("sell", "limit", max(limit_price, reference), min(size, int(coins - MIN_COINS)))
elif deviation > FADE_THRESHOLD and can_sell:
# Standard overshoot — sell
size = int(FADE_SIZE_SMALL + max(bias, 0))
limit_price = reference + FADE_OFFSET
if best_bid and best_bid > limit_price:
limit_price = best_bid - 0.05
print(f" [FADE] SELL: price ${price:.2f} is ${deviation:.2f} above ref")
post_order("sell", "limit", max(limit_price, reference), min(size, int(coins - MIN_COINS)))
elif deviation < -FADE_BIG and can_buy:
# Big dip — aggressive buy
size = int(FADE_SIZE_BIG + max(-bias, 0))
limit_price = reference - FADE_OFFSET
if best_ask and best_ask < limit_price:
limit_price = best_ask + 0.05
print(f" [FADE] BIG BUY: price ${price:.2f} is ${abs(deviation):.2f} below ref")
post_order("buy", "limit", min(limit_price, reference), min(size, int(MAX_COINS - coins)))
elif deviation < -FADE_THRESHOLD and can_buy:
# Standard dip — buy
size = int(FADE_SIZE_SMALL + max(-bias, 0))
limit_price = reference - FADE_OFFSET
if best_ask and best_ask < limit_price:
limit_price = best_ask + 0.05
print(f" [FADE] BUY: price ${price:.2f} is ${abs(deviation):.2f} below ref")
post_order("buy", "limit", min(limit_price, reference), min(size, int(MAX_COINS - coins)))
elif abs(deviation) < 1.0:
# Price is near fair value — no fade edge
pass
# === STRATEGY 2: Penny-jumping ===
if spread and spread > PENNY_SPREAD_MIN and best_bid and best_ask:
now = time.time()
# Refresh penny-jump orders every 15 seconds
if now - last_penny_refresh > 15:
# Cancel stale penny orders
stale_ids = [oid for oid, info in my_orders.items()
if now - info["time"] > 12]
for oid in stale_ids:
cancel_order(oid)
penny_bid = round(best_bid + PENNY_JUMP, 2)
penny_ask = round(best_ask - PENNY_JUMP, 2)
# Only penny-jump if our prices are still inside the spread
if penny_bid < penny_ask:
buy_size = PENNY_SIZE
sell_size = PENNY_SIZE
# Adjust for position bias
if bias > 5:
buy_size = max(5, PENNY_SIZE - 5)
sell_size = PENNY_SIZE + 5
elif bias < -5:
buy_size = PENNY_SIZE + 5
sell_size = max(5, PENNY_SIZE - 5)
if can_buy:
print(f" [PENNY] BUY {buy_size} @ ${penny_bid:.2f} (jumped bid)")
post_order("buy", "limit", penny_bid, min(buy_size, int(MAX_COINS - coins)))
if can_sell:
print(f" [PENNY] SELL {sell_size} @ ${penny_ask:.2f} (jumped ask)")
post_order("sell", "limit", penny_ask, min(sell_size, int(coins - MIN_COINS)))
last_penny_refresh = now
# === STRATEGY 3: Position rebalance (if way off target) ===
if coins > MAX_COINS - 20:
# Dangerously long — market sell some
dump = int(coins - TARGET_COINS - 30)
if dump > 5 and best_bid:
print(f" [REBAL] Emergency sell {dump} — too long at {coins:.0f}")
post_order("sell", "limit", best_bid - 0.10, min(dump, 50))
elif coins < MIN_COINS + 20:
# Dangerously short — market buy some
grab = int(TARGET_COINS - coins - 30)
if grab > 5 and best_ask:
print(f" [REBAL] Emergency buy {grab} — too short at {coins:.0f}")
post_order("buy", "limit", best_ask + 0.10, min(grab, 50))
# Cleanup old orders (>60 seconds)
now = time.time()
stale = [oid for oid, info in my_orders.items() if now - info["time"] > 60]
for oid in stale:
cancel_order(oid)
if stale:
print(f" [CLEANUP] Cancelled {len(stale)} stale orders")
if __name__ == "__main__":
print("Taker-MeanRev starting up...")
# Cancel any leftover orders from a previous run
cancel_all()
time.sleep(1)
main_loop()