-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathclose_everything_now.py
More file actions
201 lines (186 loc) · 6.75 KB
/
close_everything_now.py
File metadata and controls
201 lines (186 loc) · 6.75 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
#!/usr/bin/env python3
"""
Close every open position immediately (market where possible).
Strategy:
- First close long positions (type != 'sell') with market SELL to free margin.
- Then attempt to close short positions (type == 'sell') with market BUY.
- If an AddOrder fails due to insufficient funds, try partial close by scaling volume down to min_auto_scale_notional (from config.toml) in steps.
- Log results to logs/close_everything_results.json and logs/close_everything.log
WARNING: This will place live market/limit orders on Kraken. Use only when you want to liquidate.
"""
import time, json, os
from pathlib import Path
from dotenv import load_dotenv
from krakenex import API
import toml
from order_lock import acquire_order_lock
_HERE = Path(__file__).parent
load_dotenv(_HERE / '.env')
API_KEY=os.getenv('KRAKEN_API_KEY')
API_SECRET=os.getenv('KRAKEN_API_SECRET')
api=API(API_KEY, API_SECRET)
CFG_PATH = _HERE / 'config.toml'
LOG = _HERE / 'logs' / 'close_everything.log'
OUT = _HERE / 'logs' / 'close_everything_results.json'
def log(msg):
ts=time.strftime('%Y-%m-%d %H:%M:%S')
line=f"{ts} - {msg}\n"
with open(LOG,'a') as f:
f.write(line)
print(line,end='')
# load config for min_auto_scale_notional
try:
cfg = toml.load(CFG_PATH)
risk = cfg.get('risk_management',{})
min_auto = float(risk.get('min_auto_scale_notional', 0.2))
except Exception:
min_auto = 0.2
# helper
def query_private(cmd, params=None):
try:
if cmd == 'AddOrder':
with acquire_order_lock(timeout_seconds=5.0) as locked:
if not locked:
return {'error': ['order lock busy']}
if params:
return api.query_private(cmd, params)
return api.query_private(cmd, {})
if params:
return api.query_private(cmd, params)
return api.query_private(cmd, {})
except Exception as e:
return {'error':[str(e)]}
log('Starting full liquidation run (close_everything_now)')
# fetch open positions
resp = query_private('OpenPositions')
if resp.get('error'):
log(f'OpenPositions error: {resp.get("error")}')
raise SystemExit(1)
positions = resp.get('result',{})
if not positions:
log('No open positions to close')
open_positions = []
else:
open_positions = []
for pid,p in positions.items():
p['pid']=pid
open_positions.append(p)
# fetch tickers
pairs = set(p.get('pair') for p in open_positions)
pair_str = ','.join(pairs) if pairs else ''
if pair_str:
r = api.query_public('Ticker', {'pair': pair_str})
tickers = r.get('result',{}) if not r.get('error') else {}
else:
tickers={}
# partition
longs=[]
shorts=[]
for p in open_positions:
if p.get('type')=='sell':
shorts.append(p)
else:
longs.append(p)
log(f'Found {len(longs)} long(s) and {len(shorts)} short(s) to close')
results=[]
# function to get current price
def get_price_for_pair(pair):
for k in tickers.keys():
if k.lower().endswith(pair.lower()):
try:
return float(tickers[k]['c'][0])
except:
return None
# fallback: public ticker single
try:
r=api.query_public('Ticker',{'pair':pair})
if not r.get('error'):
return float(r.get('result',{}).get(next(iter(r.get('result'))))['c'][0])
except Exception:
return None
return None
# close longs first (sell)
for p in longs:
pid=p.get('pid')
pair=p.get('pair')
vol=float(p.get('vol',0))
price = get_price_for_pair(pair)
log(f'Attempting to close LONG {pid} {pair} vol={vol} (market SELL)')
if vol<=0:
log('Zero volume, skipping')
continue
params={'pair':pair,'type':'sell','ordertype':'market','volume':str(vol)}
r=query_private('AddOrder', params)
if r.get('error'):
log(f'AddOrder error for LONG {pid}: {r.get("error")}')
# try partial scale down progressively
scaled=vol
while scaled>=min_auto:
scaled = round(scaled/2,8)
if scaled < min_auto:
break
log(f'Trying partial SELL vol={scaled}')
params['volume']=str(scaled)
r=query_private('AddOrder', params)
if not r.get('error'):
log(f'Partial SELL succeeded for {pid} vol={scaled} -> {r.get("result")}')
results.append({'pid':pid,'side':'long','action':'partial_sell','vol':scaled,'resp':r})
break
else:
log(f'Partial SELL failed: {r.get("error")}')
else:
log(f'Could not close LONG {pid}')
results.append({'pid':pid,'side':'long','action':'failed','error':r.get('error')})
else:
log(f'Closed LONG {pid} successfully -> {r.get("result")}')
results.append({'pid':pid,'side':'long','action':'closed','resp':r})
time.sleep(1.0)
# refresh tradebalance/tickers
try:
tb = query_private('TradeBalance')
if not tb.get('error'):
tickers_update = api.query_public('Ticker', {'pair': pair_str}).get('result',{}) if pair_str else {}
tickers.update(tickers_update)
except Exception:
pass
# attempt to close shorts (buy)
for p in sorted(shorts, key=lambda x: float(x.get('cost',0))):
pid=p.get('pid')
pair=p.get('pair')
vol=float(p.get('vol',0))
price = get_price_for_pair(pair)
log(f'Attempting to close SHORT {pid} {pair} vol={vol} (market BUY)')
if vol<=0:
log('Zero volume, skipping')
continue
params={'pair':pair,'type':'buy','ordertype':'market','volume':str(vol)}
r=query_private('AddOrder', params)
if r.get('error'):
log(f'AddOrder error for SHORT {pid}: {r.get("error")}')
# try partial close scaling down
scaled=vol
while scaled>=min_auto:
scaled = round(scaled/2,8)
if scaled < min_auto:
break
log(f'Trying partial BUY vol={scaled}')
params['volume']=str(scaled)
r=query_private('AddOrder', params)
if not r.get('error'):
log(f'Partial BUY succeeded for {pid} vol={scaled} -> {r.get("result")}')
results.append({'pid':pid,'side':'short','action':'partial_buy','vol':scaled,'resp':r})
break
else:
log(f'Partial BUY failed: {r.get("error")}')
else:
log(f'Could not close SHORT {pid}')
results.append({'pid':pid,'side':'short','action':'failed','error':r.get('error')})
else:
log(f'Closed SHORT {pid} successfully -> {r.get("result")}')
results.append({'pid':pid,'side':'short','action':'closed','resp':r})
time.sleep(1.0)
# write results
with open(OUT+'.tmp','w') as f:
json.dump(results,f,indent=2)
os.replace(OUT+'.tmp', OUT)
log('Finished liquidation run')