-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathclose_on_profit.py
More file actions
140 lines (121 loc) · 4.03 KB
/
close_on_profit.py
File metadata and controls
140 lines (121 loc) · 4.03 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
#!/usr/bin/env python3
"""
Close short positions automatically when unrealized profit percent >= threshold + fee
"""
import os, json, time
from pathlib import Path
from dotenv import load_dotenv
from krakenex import API
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)
# Config
MIN_PROFIT_PCT = 3.0 # user-specified base percent
FEE_RATE = 0.002 # assumed taker fee (0.2%) for closing
REQUIRED_PCT = MIN_PROFIT_PCT + (FEE_RATE*100)
MIN_NOTIONAL_EUR = 1.0 # don't attempt to close tiny positions
LOG_PATH = _HERE / 'logs' / 'close_on_profit.log'
def log(msg):
ts = time.strftime('%Y-%m-%d %H:%M:%S')
line = f"{ts} - {msg}\n"
with open(LOG_PATH,'a') as f:
f.write(line)
print(line, end='')
def fetch_open_positions():
r = api.query_private('OpenPositions')
if r.get('error'):
raise RuntimeError(f"OpenPositions error: {r.get('error')}")
return r.get('result',{})
def fetch_tickers(pairs):
if not pairs:
return {}
pair_str = ','.join(pairs)
r = api.query_public('Ticker', {'pair': pair_str})
if r.get('error'):
raise RuntimeError(f"Ticker error: {r.get('error')}")
return r.get('result',{})
def compute_pnl_pct(pos, tickers):
vol = float(pos.get('vol',0))
cost = float(pos.get('cost',0))
typ = pos.get('type')
pair = pos.get('pair')
if vol <= 0 or cost <= 0:
return None
entry = cost/vol
# find current price
cur = None
for k in tickers.keys():
if k.lower().endswith(pair.lower()):
try:
cur = float(tickers[k]['c'][0])
except:
cur = None
break
if cur is None:
return None
if typ == 'sell':
pnl = (entry - cur) * vol
else:
pnl = (cur - entry) * vol
pnl_pct = (pnl / cost) * 100.0
return pnl_pct, pnl, cost, vol, entry, cur
def close_position(pair, vol):
# market buy to close short
params = {'pair': pair, 'type': 'buy', 'ordertype': 'market', 'volume': str(vol)}
with acquire_order_lock(timeout_seconds=5.0) as locked:
if not locked:
return {'error': ['order lock busy']}
r = api.query_private('AddOrder', params)
return r
def main():
try:
positions = fetch_open_positions()
except Exception as e:
log(f"ERROR fetching positions: {e}")
return 1
pairs = set(p.get('pair') for p in positions.values())
try:
tickers = fetch_tickers(pairs)
except Exception as e:
log(f"ERROR fetching tickers: {e}")
tickers = {}
to_close = []
for pid, pos in positions.items():
typ = pos.get('type')
if typ != 'sell':
continue
res = compute_pnl_pct(pos, tickers)
if not res:
continue
pnl_pct, pnl, cost, vol, entry, cur = res
log(f"Pos {pid} {pos.get('pair')} vol={vol:.8f} entry={entry:.6f} cur={cur} pnl_pct={pnl_pct:.3f} pnl={pnl:.2f}")
if cost < MIN_NOTIONAL_EUR:
continue
if pnl_pct >= REQUIRED_PCT:
to_close.append((pid, pos.get('pair'), vol, pnl, pnl_pct))
if not to_close:
log('No positions meet profit threshold')
return 0
log(f"Closing {len(to_close)} positions meeting threshold ({REQUIRED_PCT:.2f}%)")
results = []
for pid,pair,vol,pnl,pct in to_close:
log(f"Attempting close {pid} {pair} vol={vol:.8f} pnl_pct={pct:.3f}")
try:
r = close_position(pair, vol)
results.append({'pid':pid,'pair':pair,'vol':vol,'resp':r})
log(f"API resp: {r}")
except Exception as e:
log(f"Exception closing {pid}: {e}")
time.sleep(1.0)
# write results
out = _HERE / 'logs' / 'close_on_profit_results.json'
with open(out+'.tmp','w') as f:
json.dump(results, f, indent=2)
os.replace(out+'.tmp', out)
log('Done')
return 0
if __name__ == '__main__':
main()