Skip to content

Commit 2831725

Browse files
committed
Add paper trading mode - test strategies without risking real money
1 parent d09dcc3 commit 2831725

File tree

1 file changed

+157
-22
lines changed

1 file changed

+157
-22
lines changed

scanner_pro_scalper.py

Lines changed: 157 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,50 @@
1919
API_KEY = os.environ.get('BINANCE_API_KEY', '')
2020
API_SECRET = os.environ.get('BINANCE_API_SECRET', '')
2121

22-
if not API_KEY or not API_SECRET:
23-
print("ERROR: BINANCE_API_KEY and BINANCE_API_SECRET must be set in environment")
24-
sys.exit(1)
25-
26-
client = Client(API_KEY, API_SECRET, tld='us')
27-
2822
# -------- Configuration --------
23+
PAPER_TRADING = True # Set to False for live trading
2924
REFRESH_INTERVAL = 10 # seconds
3025
RISK_USDT = 20 # USDT per trade
3126
POSITIONS_FILE = 'positions.json'
27+
PAPER_TRADES_FILE = 'paper_trades.json'
28+
29+
# -------- Paper Trading State --------
30+
paper_balance = 1000.0 # Starting paper balance in USDT
31+
paper_trades = [] # Trade history
32+
33+
def load_paper_state():
34+
global paper_balance, paper_trades
35+
if os.path.exists(PAPER_TRADES_FILE):
36+
with open(PAPER_TRADES_FILE) as f:
37+
data = json.load(f)
38+
paper_balance = data.get('balance', 1000.0)
39+
paper_trades = data.get('trades', [])
40+
41+
def save_paper_state():
42+
with open(PAPER_TRADES_FILE, 'w') as f:
43+
json.dump({'balance': paper_balance, 'trades': paper_trades}, f, indent=2)
44+
45+
# -------- Initialize --------
46+
if PAPER_TRADING:
47+
load_paper_state()
48+
print("=" * 50)
49+
print("🧻 PAPER TRADING MODE - NO REAL MONEY AT RISK")
50+
print(f" Starting balance: ${paper_balance:.2f} USDT")
51+
print("=" * 50)
52+
# Still need API for price data (read-only)
53+
if API_KEY and API_SECRET:
54+
client = Client(API_KEY, API_SECRET, tld='us')
55+
else:
56+
print("Note: Using public endpoints for price data")
57+
client = Client("", "", tld='us')
58+
else:
59+
if not API_KEY or not API_SECRET:
60+
print("ERROR: BINANCE_API_KEY and BINANCE_API_SECRET must be set for live trading")
61+
sys.exit(1)
62+
client = Client(API_KEY, API_SECRET, tld='us')
63+
print("=" * 50)
64+
print("⚠️ LIVE TRADING MODE - REAL MONEY AT RISK")
65+
print("=" * 50)
3266

3367
# -------- Persistence --------
3468
if os.path.exists(POSITIONS_FILE):
@@ -94,42 +128,143 @@ def dynamic_tp(candles):
94128
last = float(candles[-1][4])
95129
return [round(last + m*atr, 2) for m in (0.5,1.0,1.5,2.0)]
96130

131+
# -------- Trading Functions --------
132+
def execute_buy(symbol, qty, price):
133+
"""Execute a buy order (paper or live)."""
134+
global paper_balance
135+
136+
if PAPER_TRADING:
137+
cost = qty * price
138+
if cost > paper_balance:
139+
print(f"[PAPER] Insufficient balance: need ${cost:.2f}, have ${paper_balance:.2f}")
140+
return False
141+
paper_balance -= cost
142+
paper_trades.append({
143+
'time': datetime.now().isoformat(),
144+
'type': 'BUY',
145+
'symbol': symbol,
146+
'qty': qty,
147+
'price': price,
148+
'cost': cost
149+
})
150+
save_paper_state()
151+
print(f"[PAPER] BUY {symbol}: {qty} @ ${price:.4f} (cost: ${cost:.2f})")
152+
return True
153+
else:
154+
client.order_market_buy(symbol=symbol, quantity=qty)
155+
return True
156+
157+
def execute_sell(symbol, qty, price):
158+
"""Execute a sell order (paper or live)."""
159+
global paper_balance
160+
161+
if PAPER_TRADING:
162+
revenue = qty * price
163+
paper_balance += revenue
164+
paper_trades.append({
165+
'time': datetime.now().isoformat(),
166+
'type': 'SELL',
167+
'symbol': symbol,
168+
'qty': qty,
169+
'price': price,
170+
'revenue': revenue
171+
})
172+
save_paper_state()
173+
print(f"[PAPER] SELL {symbol}: {qty} @ ${price:.4f} (revenue: ${revenue:.2f})")
174+
return True
175+
else:
176+
client.order_limit_sell(symbol=symbol, quantity=qty, price=str(price))
177+
return True
178+
179+
def check_tp_hits():
180+
"""Check if any take-profit levels have been hit (paper mode only)."""
181+
global paper_balance
182+
183+
if not PAPER_TRADING:
184+
return # Live mode handles this via exchange orders
185+
186+
to_remove = []
187+
for sym, pos in positions.items():
188+
try:
189+
current_price = float(client.get_symbol_ticker(symbol=sym)['price'])
190+
hit_tps = [tp for tp in pos['tps'] if current_price >= tp]
191+
192+
if hit_tps:
193+
# Calculate shares per TP level
194+
share = pos['qty'] / len(pos['tps'])
195+
for tp in hit_tps:
196+
execute_sell(sym, share, tp)
197+
pos['tps'].remove(tp)
198+
pos['qty'] -= share
199+
200+
if not pos['tps']: # All TPs hit
201+
to_remove.append(sym)
202+
print(f"[PAPER] Position {sym} fully closed!")
203+
204+
save_positions()
205+
except Exception as e:
206+
print(f"Error checking {sym}: {e}")
207+
208+
for sym in to_remove:
209+
del positions[sym]
210+
211+
if to_remove:
212+
save_positions()
213+
97214
# -------- Main Loop --------
98215
def main():
99-
print("=== GridBot Pro Scalper Live ===")
216+
mode = "PAPER" if PAPER_TRADING else "LIVE"
217+
print(f"=== GridBot Pro Scalper ({mode}) ===")
218+
100219
while True:
101220
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
221+
222+
# Check for TP hits in paper mode
223+
if PAPER_TRADING and positions:
224+
check_tp_hits()
225+
102226
pairs = fetch_pairs()
103227
signal_found = False
228+
104229
for sym in pairs:
105230
kl = fetch_klines(sym)
106231
closes = [float(c[4]) for c in kl]
107232
vols = [float(c[5]) for c in kl]
233+
108234
# Indicators
109235
ema_fast = ema(closes[-5:], 5)
110236
ema_slow = ema(closes[-20:], 20)
111237
r = rsi(closes[-15:])
112238
vol_spike = vols[-1] >= 2 * (sum(vols[-11:-1])/10)
239+
113240
# Entry condition
114-
if ema_fast>ema_slow and r>50 and vol_spike:
241+
if ema_fast > ema_slow and r > 50 and vol_spike:
115242
if sym not in positions:
116-
# Place market buy
117243
price = float(client.get_symbol_ticker(symbol=sym)['price'])
118-
qty = round(RISK_USDT/price, 6)
119-
client.order_market_buy(symbol=sym, quantity=qty)
120-
# Place TPs
121-
tps = dynamic_tp(kl)
122-
share = round(qty/len(tps), 6)
123-
for tp in tps:
124-
client.order_limit_sell(symbol=sym, quantity=share, price=str(tp))
125-
positions[sym] = {'qty': qty, 'tps': tps}
126-
save_positions()
127-
print(f"[{now}] Bought {sym} qty={qty}, set TPs={tps}")
128-
signal_found = True
244+
qty = round(RISK_USDT / price, 6)
245+
246+
if execute_buy(sym, qty, price):
247+
tps = dynamic_tp(kl)
248+
249+
if not PAPER_TRADING:
250+
# Place limit sell orders on exchange
251+
share = round(qty / len(tps), 6)
252+
for tp in tps:
253+
client.order_limit_sell(symbol=sym, quantity=share, price=str(tp))
254+
255+
positions[sym] = {'qty': qty, 'tps': tps, 'entry': price}
256+
save_positions()
257+
print(f"[{now}] Bought {sym} qty={qty}, TPs={tps}")
258+
signal_found = True
129259
break
260+
130261
if not signal_found:
131-
print(f"[{now}] No entry signal found.")
262+
if PAPER_TRADING:
263+
print(f"[{now}] No signal | Balance: ${paper_balance:.2f} | Positions: {len(positions)}")
264+
else:
265+
print(f"[{now}] No entry signal found.")
266+
132267
time.sleep(REFRESH_INTERVAL)
133268

134-
if __name__=="__main__":
269+
if __name__ == "__main__":
135270
main()

0 commit comments

Comments
 (0)