import os.txt
À propos du fichier
- Type de fichier
- Fichier TXT de 45 Ko (text/x-python)
- Confidentialité
- Fichier public, envoyé le 6 octobre 2026 à 20:10, depuis l'adresse IP 5.187.x.x (GP)
- Sécurité
- Ne contient aucun Virus ou Malware connus - Dernière vérification: 3 jours
- Statistiques
- La présente page de téléchargement a été vue 16 fois depuis l'envoi du fichier
- Page de téléchargement
-
Aperçu du fichier
import os
import sys
import hashlib
import hmac
import time
import json
import math
import urllib.parse
import requests
import pandas as pd
import pandas_ta as ta
from datetime import datetime, time as dtime
from threading import Thread
from flask import Flask, render_template_string, jsonify, request
# ==========================================
# CONFIGURATION INITIALE ET CLÉS API PIONEX
# ==========================================
PIONEX_API_KEY = "5fEmm9xYKbbJnrW1vPo1yPupygjKRBTfWHqnEJz6LAkiF5nLVQ8pavXYz9fETK8GqJ"
PIONEX_SECRET_KEY = "D8wBdPpL7VPfMEdOd2XkGdyCRV2BoHb7yOpI0QQAaZ6sdYnKZxPmkQtKJARZ8iHc"
BASE_URL = "https://api.pionex.com"
OLLAMA_URL = "http://localhost:11434/api/generate"
OLLAMA_MODEL = "dolphin-llama3"
time_offset_ms = 0
bot_config = {
"symbol": "SOL_USDT",
"amount_usdt": 12.0, # Capital engagé en USDT
"take_profit_pct": 0.0100, # Max TP: +1.00% BRUT (+0.80% NET)
"min_tp_pct": 0.0060, # Min TP dynamique: +0.60% BRUT (+0.40% NET)
"stop_loss_pct": 0.0050, # -0.50% STOP-LOSS de base (Ajusté par ATR)
"timeframe": "5M", # Unité de temps des indicateurs
"rsi_trigger": 52.0, # Seuil RSI haussier ajusté
"is_running": False
}
bot_status = {
"price": 0.0,
"rsi": 0.0,
"stoch_k": 0.0,
"stoch_d": 0.0,
"bbl": 0.0,
"bbu": 0.0,
"ema9": 0.0,
"ema21": 0.0,
"ema200": 0.0,
"atr": 0.0,
"trend": "NEUTRE",
"decision": "HOLD",
"in_position": False,
"buy_price": 0.0,
"buy_time": "--",
"trade_duration": "--",
"crypto_qty": 0.0,
"amount_usdt": 12.0,
"target_price": 0.0,
"stop_loss_price": 0.0,
"max_price_seen": 0.0,
"is_be_active": False,
"current_profit_pct": 0.0,
"current_profit_usdt": 0.0,
"total_pnl_usdt": -0.30,
"gauge_percent": 0.0,
"pos_count": 0,
"total_trades": 11,
"winning_trades": 5,
"losing_trades": 6,
"win_rate": 45.5,
"last_update": "En attente...",
"logs": []
}
app = Flask(__name__)
def add_log(msg):
timestamp = time.strftime('%H:%M:%S')
entry = f"[{timestamp}] {msg}"
print(entry)
bot_status["logs"].insert(0, entry)
if len(bot_status["logs"]) > 70:
bot_status["logs"].pop()
def sync_pionex_time():
global time_offset_ms
try:
os.system("sudo timedatectl set-ntp true > /dev/null 2>&1")
os.system("sudo hwclock --systohc > /dev/null 2>&1")
except Exception:
pass
try:
res = requests.get(f"{BASE_URL}/api/v1/common/timestamp", timeout=3).json()
if res.get("result"):
server_time = res["data"]["timestamp"]
local_time = int(time.time() * 1000)
time_offset_ms = server_time - local_time
add_log(f"Horloge synchronisée avec Pionex (Décalage: {time_offset_ms} ms)")
else:
time_offset_ms = 0
except Exception as e:
add_log(f"⚠️ Erreur sync temps API : {e}")
time_offset_ms = 0
def get_pionex_timestamp():
return int(time.time() * 1000) + time_offset_ms
def is_forbidden_trading_time():
""" Pause de sécurité à l'aube (03h30 - 06h00 Guadeloupe UTC-4) """
now = datetime.now().time()
return dtime(3, 30) <= now <= dtime(6, 0)
def get_pionex_live_price(symbol):
try:
url = f"{BASE_URL}/api/v1/market/trades"
res = requests.get(url, params={"symbol": symbol, "limit": 1}, timeout=2).json()
if res.get("result") and res.get("data") and res["data"].get("trades"):
return float(res["data"]["trades"][0]["price"])
except Exception:
pass
try:
url_ticker = f"{BASE_URL}/api/v1/market/tickers"
res_t = requests.get(url_ticker, params={"symbol": symbol}, timeout=2).json()
if res_t.get("result") and res_t.get("data") and res_t["data"].get("tickers"):
return float(res_t["data"]["tickers"][0]["close"])
except Exception:
pass
return None
def get_klines(symbol, interval="5M", limit=250):
path = "/api/v1/market/klines"
url = f"{BASE_URL}{path}"
params = {"symbol": symbol, "interval": interval, "limit": limit}
try:
res = requests.get(url, params=params, timeout=3).json()
if res.get("result"):
data = res["data"]["klines"]
df = pd.DataFrame(data, columns=["time", "open", "high", "low", "close", "volume"])
df["time"] = df["time"].astype(int)
df = df.sort_values("time").reset_index(drop=True)
df["open"] = df["open"].astype(float)
df["close"] = df["close"].astype(float)
df["high"] = df["high"].astype(float)
df["low"] = df["low"].astype(float)
df["volume"] = df["volume"].astype(float)
return df
else:
add_log(f"⚠️ Erreur API Klines : {res.get('message', 'Paramètre invalide')}")
except Exception as e:
add_log(f"Erreur Réseau API Pionex : {e}")
return None
def format_crypto_size(symbol, quantity):
""" CORRECTION : SOL/USDT nécessite une précision à 2 décimales (step 0.01) sur Pionex """
if "PEPE" in symbol:
return str(int(quantity))
elif "DOGE" in symbol or "XRP" in symbol or "SOL" in symbol:
return f"{math.floor(quantity * 100) / 100:.2f}"
else:
return f"{math.floor(quantity * 1000) / 1000:.3f}"
def format_price(symbol, price):
if "PEPE" in symbol:
return f"{price:.8f}"
elif "DOGE" in symbol:
return f"{price:.5f}"
else:
return f"{price:.2f}"
def place_order(symbol, side, quantity_or_amount, order_type="MARKET", price=None):
path = "/api/v1/trade/order"
timestamp = get_pionex_timestamp()
params = {"timestamp": timestamp}
query_string = urllib.parse.urlencode(params)
body_data = {
"side": side.upper(),
"symbol": symbol,
"type": order_type.upper()
}
if order_type.upper() == "LIMIT" and price is not None:
body_data["price"] = format_price(symbol, price)
body_data["size"] = format_crypto_size(symbol, quantity_or_amount)
else:
if side.upper() == "BUY":
body_data["amount"] = str(round(quantity_or_amount, 2))
else:
body_data["size"] = format_crypto_size(symbol, quantity_or_amount)
body_str = json.dumps(body_data, sort_keys=True, separators=(',', ':'))
message = f"POST{path}?{query_string}{body_str}"
signature = hmac.new(
PIONEX_SECRET_KEY.encode('utf-8'),
message.encode('utf-8'),
hashlib.sha256
).hexdigest()
url = f"{BASE_URL}{path}?{query_string}"
headers = {
"PIONEX-KEY": PIONEX_API_KEY,
"PIONEX-SIGNATURE": signature,
"Content-Type": "application/json"
}
try:
response = requests.post(url, data=body_str, headers=headers, timeout=3)
return response.json()
except Exception as e:
add_log(f"Erreur Envoi Ordre ({side}) : {e}")
return {"result": False}
def calculate_indicators(df):
df['RSI'] = ta.rsi(df['close'], length=14)
stoch_rsi = ta.stochrsi(df['close'], length=14, rsi_length=14, k=3, d=3)
if stoch_rsi is not None and not stoch_rsi.empty:
df['STOCHRSIk'] = stoch_rsi.iloc[:, 0]
df['STOCHRSId'] = stoch_rsi.iloc[:, 1]
else:
df['STOCHRSIk'], df['STOCHRSId'] = 50.0, 50.0
df['EMA9'] = ta.ema(df['close'], length=9)
df['EMA21'] = ta.ema(df['close'], length=21)
df['EMA200'] = ta.ema(df['close'], length=200)
df['ATR'] = ta.atr(df['high'], df['low'], df['close'], length=14)
bb = ta.bbands(df['close'], length=20, std=2)
if bb is not None and not bb.empty:
df['BBL'] = bb.iloc[:, 0]
df['BBU'] = bb.iloc[:, 2]
else:
df['BBL'], df['BBU'] = df['close'], df['close']
return df
def ask_local_ai_fast(symbol, price, rsi, stoch_k, is_uptrend):
prompt = f"""Scalping pour {symbol} :
Prix Spot: {price} | RSI(14): {rsi:.1f} | StochRSI: {stoch_k:.1f} | Tendance Haussière Validée: {is_uptrend}
Réponds uniquement "BUY" si le rebond est confirmé, sinon "HOLD"."""
try:
response = requests.post(OLLAMA_URL, json={
"model": OLLAMA_MODEL,
"prompt": prompt,
"stream": False
}, timeout=2.0)
if response.status_code == 200:
raw_resp = response.json().get("response", "").strip().upper()
return "BUY" if "BUY" in raw_resp else "HOLD"
return "BUY" if is_uptrend else "HOLD"
except Exception:
return "BUY" if is_uptrend else "HOLD"
def run_trader():
global bot_status
sync_pionex_time()
in_position = False
buy_price = 0.0
trade_start_ts = 0
crypto_qty = 0.0
active_sl_price = 0.0
max_price_seen = 0.0
is_be_active = False
while True:
loop_start = time.time()
try:
if not bot_config["is_running"]:
bot_status["decision"] = "ARRÊTÉ"
bot_status["gauge_percent"] = 0.0
bot_status["last_update"] = time.strftime('%H:%M:%S')
time.sleep(1)
continue
symbol = bot_config["symbol"]
amount = bot_config["amount_usdt"]
max_tp_pct = bot_config["take_profit_pct"] # 1.00%
min_tp_pct = bot_config["min_tp_pct"] # 0.60%
tf = bot_config["timeframe"]
live_price = get_pionex_live_price(symbol)
df = get_klines(symbol, interval=tf, limit=250)
if df is not None and len(df) >= 200:
df = calculate_indicators(df)
last = df.iloc[-1]
prev = df.iloc[-2]
price = live_price if live_price is not None else float(last['close'])
rsi = float(last['RSI']) if pd.notna(last['RSI']) else 50.0
prev_rsi = float(prev['RSI']) if pd.notna(prev['RSI']) else 50.0
stoch_k = float(last['STOCHRSIk']) if pd.notna(last['STOCHRSIk']) else 50.0
stoch_d = float(last['STOCHRSId']) if pd.notna(last['STOCHRSId']) else 50.0
prev_stoch_k = float(prev['STOCHRSIk']) if pd.notna(prev['STOCHRSIk']) else 50.0
prev_stoch_d = float(prev['STOCHRSId']) if pd.notna(prev['STOCHRSId']) else 50.0
bbl = float(last['BBL']) if pd.notna(last['BBL']) else price
bbu = float(last['BBU']) if pd.notna(last['BBU']) else price
ema9 = float(last['EMA9']) if pd.notna(last['EMA9']) else price
ema21 = float(last['EMA21']) if pd.notna(last['EMA21']) else price
ema200 = float(last['EMA200']) if pd.notna(last['EMA200']) else price
atr = float(last['ATR']) if pd.notna(last['ATR']) else (price * 0.005)
calculated_sl_pct = max(0.0040, min(0.0080, (atr * 1.5) / price))
bot_status["price"] = price
bot_status["rsi"] = rsi
bot_status["stoch_k"] = stoch_k
bot_status["stoch_d"] = stoch_d
bot_status["bbl"] = bbl
bot_status["bbu"] = bbu
bot_status["ema9"] = ema9
bot_status["ema21"] = ema21
bot_status["ema200"] = ema200
bot_status["atr"] = atr
bot_status["last_update"] = time.strftime('%H:%M:%S')
# CONDITION TENDANCE SOUPLE & FLUIDE : Prix au-dessus de l'EMA 200
is_uptrend_macro = price > ema200
is_green_candle = last['close'] > last['open']
is_price_rising = price > float(prev['close'])
rsi_rebounding = rsi > prev_rsi
stoch_cross = (stoch_k > stoch_d) and (prev_stoch_k <= prev_stoch_d)
is_reversal_confirmed = (is_green_candle or is_price_rising) and rsi_rebounding
bot_status["trend"] = "HAUSSIÈRE (MACRO) 🟢" if is_uptrend_macro else "NEUTRE / BAISSIÈRE 🔴"
if not in_position:
bot_status["gauge_percent"] = 0.0
bot_status["current_profit_pct"] = 0.0
bot_status["current_profit_usdt"] = 0.0
bot_status["trade_duration"] = "--"
bot_status["is_be_active"] = False
# SIGNAL FLUIDE : Tendance haussière + RSI < 52 + Croisement StochRSI + Rebond
algo_signal = is_uptrend_macro and (rsi < bot_config["rsi_trigger"]) and stoch_cross and is_reversal_confirmed
if is_forbidden_trading_time():
bot_status["decision"] = "PAUSE RISQUE (03H30-06H00)"
add_log(f"[{symbol}] ⏸️ Pause créneau risque à l'aube (UTC-4) : Achat ignoré.")
else:
decision = "HOLD"
if algo_signal:
ai_decision = ask_local_ai_fast(symbol, price, rsi, stoch_k, is_reversal_confirmed)
if ai_decision == "BUY":
decision = "BUY"
bot_status["decision"] = decision
bot_status["in_position"] = False
add_log(f"[{symbol}] Prix: ${price:.2f} | RSI: {rsi:.1f} | StochK: {stoch_k:.1f} | Rebond: {is_reversal_confirmed} -> {decision}")
if decision == "BUY":
order_res = place_order(symbol, "BUY", amount, order_type="MARKET")
if order_res.get("result"):
in_position = True
buy_price = price
trade_start_ts = time.time()
max_price_seen = buy_price
crypto_qty = amount / buy_price
is_be_active = False
active_sl_price = buy_price * (1 - calculated_sl_pct)
target_price = buy_price * (1 + max_tp_pct)
bot_status["buy_price"] = buy_price
bot_status["buy_time"] = time.strftime('%H:%M:%S')
bot_status["crypto_qty"] = crypto_qty
bot_status["amount_usdt"] = amount
bot_status["target_price"] = target_price
bot_status["stop_loss_price"] = active_sl_price
bot_status["max_price_seen"] = max_price_seen
bot_status["is_be_active"] = False
bot_status["pos_count"] = 1
bot_status["in_position"] = True
qte_str = format_crypto_size(symbol, crypto_qty)
add_log(f"✅ ACHAT Marché à ${buy_price:.2f} (Qté exacte: {qte_str} {symbol.split('_')[0]}). TP: ${target_price:.2f} | SL: ${active_sl_price:.2f}")
else:
add_log(f"❌ Échec achat : {order_res}")
else:
bot_status["in_position"] = True
if price > max_price_seen:
max_price_seen = price
dur_sec = int(time.time() - trade_start_ts)
mins, secs = divmod(dur_sec, 60)
bot_status["trade_duration"] = f"{mins:02d}m {secs:02d}s"
profit_brut = ((price - buy_price) / buy_price) * 100.0
profit_net = profit_brut - 0.20 # Frais Achat + Vente (0.10% + 0.10%)
profit_usdt = (price - buy_price) * crypto_qty - (amount * 0.002)
# VERROUILLAGE BREAK-EVEN Dès +0.35% Brut
if profit_brut >= 0.35:
break_even_price = buy_price * 1.0020 # Couvre les frais à 100%
if active_sl_price < break_even_price:
active_sl_price = break_even_price
is_be_active = True
add_log(f"🛡️ POSITION SÉCURISÉE AU BREAK-EVEN (+0.20% inclus frais). Trade désormais sans risque !")
bot_status["target_price"] = buy_price * (1 + max_tp_pct)
bot_status["stop_loss_price"] = active_sl_price
bot_status["max_price_seen"] = max_price_seen
bot_status["is_be_active"] = is_be_active
bot_status["current_profit_pct"] = profit_net
bot_status["current_profit_usdt"] = profit_usdt
total_range = (buy_price * (1 + max_tp_pct)) - active_sl_price
current_dist = price - active_sl_price
gauge = max(0.0, min(100.0, (current_dist / total_range) * 100.0)) if total_range > 0 else 50.0
bot_status["gauge_percent"] = round(gauge, 1)
# DECISION DE VENTE
is_max_tp_reached = price >= (buy_price * (1 + max_tp_pct))
is_min_tp_reached = profit_brut >= (min_tp_pct * 100) # >= +0.60%
smart_tp_condition = is_min_tp_reached and (price >= bbu or rsi >= 68.0 or price <= (max_price_seen * 0.9985))
is_sl_triggered = price <= active_sl_price
if is_max_tp_reached or smart_tp_condition:
bot_status["decision"] = "TAKE PROFIT"
elif is_sl_triggered:
bot_status["decision"] = "STOP LOSS / BE"
else:
bot_status["decision"] = "HOLD"
add_log(f"Position active | Prix Direct: ${price:.2f} | PnL Net: {profit_net:+.2f}% (${profit_usdt:+.2f}) | Prog: {gauge:.1f}%")
# VENTE TP
if is_max_tp_reached or smart_tp_condition:
tp_type = "MAX (+1.00%)" if is_max_tp_reached else f"SMART (+{profit_net:.2f}% NET)"
qte_str = format_crypto_size(symbol, crypto_qty)
add_log(f"🎯 TAKE PROFIT {tp_type} ATTEINT (${price:.2f}) ! Vente de {qte_str} {symbol.split('_')[0]}...")
order_res = place_order(symbol, "SELL", crypto_qty, order_type="MARKET")
if order_res.get("result"):
bot_status["winning_trades"] += 1
bot_status["total_trades"] += 1
bot_status["total_pnl_usdt"] += profit_usdt
bot_status["win_rate"] = (bot_status["winning_trades"] / bot_status["total_trades"]) * 100.0
add_log(f"✅ PROFIT ENCAISSÉ ! Net: {profit_net:+.2f}% (${profit_usdt:+.2f} USDT)")
in_position = False
buy_price = 0.0
crypto_qty = 0.0
max_price_seen = 0.0
bot_status["in_position"] = False
bot_status["pos_count"] = 0
bot_status["gauge_percent"] = 100.0
else:
add_log(f"❌ Échec Vente TP : {order_res}")
# VENTE SL / BE
elif is_sl_triggered:
qte_str = format_crypto_size(symbol, crypto_qty)
is_be = active_sl_price >= buy_price
sl_type = "BREAK-EVEN (FRAIS COUVERTS)" if is_be else "STOP LOSS DÉCLENCHÉ"
add_log(f"⚠️ {sl_type} (${price:.2f}) ! Vente de sécurité...")
order_res = place_order(symbol, "SELL", crypto_qty, order_type="MARKET")
if order_res.get("result"):
if not is_be:
bot_status["losing_trades"] += 1
else:
bot_status["winning_trades"] += 1
bot_status["total_trades"] += 1
bot_status["total_pnl_usdt"] += profit_usdt
bot_status["win_rate"] = (bot_status["winning_trades"] / bot_status["total_trades"]) * 100.0
add_log(f"🛑 CLÔTURE EXÉCUTÉE. PnL Net: {profit_net:+.2f}% (${profit_usdt:+.2f} USDT)")
in_position = False
buy_price = 0.0
crypto_qty = 0.0
max_price_seen = 0.0
bot_status["in_position"] = False
bot_status["pos_count"] = 0
bot_status["gauge_percent"] = 0.0
else:
add_log(f"❌ Échec Vente SL : {order_res}")
except Exception as e:
add_log(f"Erreur Boucle Trading : {e}")
elapsed = time.time() - loop_start
time.sleep(max(0, 1.0 - elapsed))
# ==========================================
# INTERFACE WEB HTML / CSS / JS (FLASK)
# ==========================================
HTML_TEMPLATE = """
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<title>Bot Scalping Pro Spot - Dashboard Temps Réel</title>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background-color: #0a0d14; color: #d1d5db; margin: 0; padding: 20px; }
.header-title { text-align: center; color: #ffffff; font-size: 24px; font-weight: 700; margin-bottom: 20px; letter-spacing: 0.5px; }
.main-layout { display: grid; grid-template-columns: 340px 1fr; gap: 20px; max-width: 1400px; margin: 0 auto; }
.card { background-color: #121721; border: 1px solid #1e2638; border-radius: 10px; padding: 20px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); }
.top-stats { display: grid; grid-template-columns: repeat(6, 1fr); gap: 12px; margin-bottom: 20px; }
.stat-card { background-color: #121721; border: 1px solid #1e2638; border-radius: 8px; padding: 14px; text-align: center; }
.stat-label { font-size: 11px; color: #8b98a5; font-weight: 600; text-transform: uppercase; margin-bottom: 4px; }
.stat-value { font-size: 20px; font-weight: bold; color: #22c55e; }
.stat-value.amber { color: #f59e0b; }
.stat-value.blue { color: #3b82f6; }
.stat-value.purple { color: #a855f7; }
.stat-value.red { color: #ef4444; }
.card-title { font-size: 16px; font-weight: 600; color: #ffffff; margin-top: 0; margin-bottom: 15px; text-align: center; }
.bot-status-text { text-align: center; font-weight: bold; font-size: 15px; margin-bottom: 20px; padding: 8px; border-radius: 6px; }
.status-stopped { background-color: rgba(239, 68, 68, 0.15); color: #ef4444; }
.status-running { background-color: rgba(34, 197, 94, 0.15); color: #22c55e; }
label { display: block; font-size: 12px; color: #9ca3af; margin-bottom: 6px; }
select, input { width: 100%; background-color: #0b0e14; border: 1px solid #232d3f; border-radius: 6px; color: #ffffff; padding: 10px; box-sizing: border-box; margin-bottom: 15px; font-size: 14px; }
.btn { width: 100%; padding: 12px; border-radius: 6px; border: none; font-weight: 700; font-size: 14px; cursor: pointer; transition: 0.2s; margin-bottom: 10px; }
.btn-green { background-color: #10b981; color: white; }
.btn-green:hover { background-color: #059669; }
.btn-red { background-color: #ef4444; color: white; }
.btn-red:hover { background-color: #dc2626; }
.btn-orange { background-color: #f59e0b; color: white; }
.trade-details-box { background-color: #161f30; border: 1px solid #2b3854; border-radius: 8px; padding: 18px; margin-bottom: 20px; }
.trade-title-bar { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #232d3f; padding-bottom: 10px; margin-bottom: 12px; }
.trade-title-text { font-size: 14px; font-weight: bold; color: #ffffff; }
.trade-badge { font-size: 11px; font-weight: bold; padding: 4px 10px; border-radius: 12px; }
.badge-green { background-color: rgba(34, 197, 94, 0.2); color: #22c55e; border: 1px solid #22c55e; }
.badge-gray { background-color: rgba(156, 163, 175, 0.2); color: #9ca3af; border: 1px solid #4b5563; }
.trade-details-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px; }
.trade-item { background-color: #0b0e14; border: 1px solid #1e2638; border-radius: 6px; padding: 10px; text-align: center; }
.trade-item-title { font-size: 10px; color: #8b98a5; font-weight: 700; text-transform: uppercase; margin-bottom: 4px; }
.trade-item-val { font-size: 15px; font-weight: bold; color: #ffffff; }
.trade-item-sub { font-size: 10px; color: #6b7280; margin-top: 2px; }
.gauge-card { background-color: #121721; border: 1px solid #1e2638; border-radius: 8px; padding: 16px; margin-bottom: 20px; }
.gauge-header { display: flex; justify-content: space-between; font-size: 13px; color: #9ca3af; margin-bottom: 8px; font-weight: 600; }
.progress-bg { width: 100%; height: 20px; background-color: #0b0e14; border: 1px solid #232d3f; border-radius: 10px; overflow: hidden; position: relative; }
.progress-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #ef4444 0%, #f59e0b 40%, #22c55e 100%); transition: width 0.3s ease-in-out; }
.gauge-labels { display: flex; justify-content: space-between; font-size: 11px; margin-top: 6px; color: #6b7280; font-weight: 600; }
.indicators-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-bottom: 20px; }
.ind-box { background-color: #0b0e14; border: 1px solid #1e2638; padding: 10px; border-radius: 6px; text-align: center; }
.ind-title { font-size: 10px; color: #6b7280; font-weight: bold; }
.ind-val { font-size: 14px; font-weight: bold; color: #e5e7eb; margin-top: 2px; }
.log-container { background-color: #05070a; border: 1px solid #1e2638; border-radius: 6px; padding: 15px; height: 320px; overflow-y: auto; font-family: 'Courier New', Courier, monospace; font-size: 12px; line-height: 1.6; }
.log-line { color: #22c55e; margin-bottom: 4px; }
</style>
<script>
async function fetchStatus() {
try {
const res = await fetch('/api/status');
const data = await res.json();
const tpBrut = data.config.take_profit_pct * 100;
const tpNet = tpBrut - 0.20;
document.getElementById('header_title').innerText = "⚡ Scalping Pro Spot (Conditions Fluides + Réactivité Accrue)";
document.getElementById('stat_price').innerText = '$' + data.status.price.toFixed(2);
const pnlNet = data.status.current_profit_pct;
const pnlUsdt = data.status.current_profit_usdt;
const pnlElem = document.getElementById('stat_pnl');
pnlElem.innerText = (pnlNet >= 0 ? '+' : '') + pnlNet.toFixed(2) + " % (" + (pnlUsdt >= 0 ? '+' : '') + pnlUsdt.toFixed(2) + " $)";
pnlElem.style.color = pnlNet >= 0 ? '#22c55e' : '#ef4444';
const totalPnlElem = document.getElementById('stat_total_pnl');
totalPnlElem.innerText = (data.status.total_pnl_usdt >= 0 ? '+' : '') + data.status.total_pnl_usdt.toFixed(2) + " $";
totalPnlElem.style.color = data.status.total_pnl_usdt >= 0 ? '#22c55e' : '#ef4444';
document.getElementById('stat_winrate').innerText = data.status.win_rate.toFixed(1) + "% (" + data.status.winning_trades + "W / " + data.status.losing_trades + "L)";
document.getElementById('stat_trades').innerText = data.status.total_trades;
document.getElementById('stat_action').innerText = data.status.decision;
const badgeElem = document.getElementById('trade_badge');
if (data.status.in_position) {
badgeElem.innerText = "🟢 POSITION EN COURS (ACHAT ACTIF)";
badgeElem.className = "trade-badge badge-green";
document.getElementById('val_type_order').innerText = "ACHAT MARCHÉ";
document.getElementById('val_crypto_qty').innerText = data.status.crypto_qty.toFixed(2) + " SOL";
document.getElementById('sub_invest').innerText = "Capital: $" + data.status.amount_usdt.toFixed(2) + " USDT";
document.getElementById('val_entry_price').innerText = "$" + data.status.buy_price.toFixed(2);
document.getElementById('sub_entry_time').innerText = "Heure: " + data.status.buy_time + " | Durée: " + data.status.trade_duration;
document.getElementById('val_target_price').innerText = "$" + data.status.target_price.toFixed(2);
document.getElementById('sub_target_pct').innerText = "Max TP: +" + tpBrut.toFixed(2) + "% (+" + tpNet.toFixed(2) + "% Net)";
document.getElementById('val_stop_price').innerText = "$" + data.status.stop_loss_price.toFixed(2);
document.getElementById('sub_be_state').innerText = data.status.is_be_active ? "🛡️ Break-Even : ACTIF" : "SL Dynamique ATR";
document.getElementById('sub_be_state').style.color = data.status.is_be_active ? "#22c55e" : "#8b98a5";
document.getElementById('val_max_seen').innerText = "$" + data.status.max_price_seen.toFixed(2);
const pnlValElem = document.getElementById('val_current_pnl');
pnlValElem.innerText = (pnlNet >= 0 ? '+' : '') + pnlNet.toFixed(2) + "% (" + (pnlUsdt >= 0 ? '+' : '') + pnlUsdt.toFixed(2) + " $)";
pnlValElem.style.color = pnlNet >= 0 ? "#22c55e" : "#ef4444";
} else {
badgeElem.innerText = "⚪ EN ATTENTE D'UN SIGNAL HAUSSIER";
badgeElem.className = "trade-badge badge-gray";
document.getElementById('val_type_order').innerText = "--";
document.getElementById('val_crypto_qty').innerText = "--";
document.getElementById('sub_invest').innerText = "Capital Réglé: $" + data.config.amount_usdt.toFixed(2) + " USDT";
document.getElementById('val_entry_price').innerText = "--";
document.getElementById('sub_entry_time').innerText = "Tendance Macro: " + (data.status.trend || "NEUTRE");
document.getElementById('val_target_price').innerText = "--";
document.getElementById('sub_target_pct').innerText = "Objectif: +1.00% Brut";
document.getElementById('val_stop_price').innerText = "--";
document.getElementById('sub_be_state').innerText = "Break-Even: Inactif";
document.getElementById('sub_be_state').style.color = "#8b98a5";
document.getElementById('val_max_seen').innerText = "--";
document.getElementById('val_current_pnl').innerText = "--";
document.getElementById('val_current_pnl').style.color = "#ffffff";
}
const gaugePct = data.status.gauge_percent || 0;
document.getElementById('gauge_val').innerText = gaugePct.toFixed(1) + "%";
document.getElementById('gauge_bar').style.width = gaugePct + "%";
document.getElementById('lbl_sl').innerText = "SL/BE: $" + data.status.stop_loss_price.toFixed(2);
document.getElementById('lbl_buy').innerText = "Achat: $" + data.status.buy_price.toFixed(2);
document.getElementById('lbl_tp').innerText = "Max TP: $" + data.status.target_price.toFixed(2);
document.getElementById('ind_rsi').innerText = data.status.rsi.toFixed(1);
document.getElementById('ind_stoch').innerText = data.status.stoch_k.toFixed(1);
document.getElementById('ind_bbl').innerText = "$" + data.status.bbl.toFixed(2);
document.getElementById('ind_bbu').innerText = "$" + data.status.bbu.toFixed(2);
const statusDiv = document.getElementById('bot_state_text');
const btn = document.getElementById('toggle_btn');
if (data.config.is_running) {
statusDiv.innerText = "BOT ACTIF (" + data.config.timeframe + ")";
statusDiv.className = "bot-status-text status-running";
btn.innerText = "ARRÊTER LE BOT";
btn.className = "btn btn-red";
} else {
statusDiv.innerText = "BOT ARRÊTÉ";
statusDiv.className = "bot-status-text status-stopped";
btn.innerText = "DÉMARRER LE BOT";
btn.className = "btn btn-green";
}
const logBox = document.getElementById('logs');
logBox.innerHTML = data.status.logs.map(l => `<div class="log-line">🟢 ${l}</div>`).join('');
} catch(e) {}
}
async function updateConfig() {
const symbol = document.getElementById('symbol').value;
const amount = document.getElementById('amount').value;
const tf = document.getElementById('timeframe').value;
const tp = document.getElementById('tp_pct').value;
const sl = document.getElementById('sl_pct').value;
await fetch('/api/config', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
symbol: symbol,
amount_usdt: parseFloat(amount),
timeframe: tf,
take_profit_pct: parseFloat(tp) / 100.0,
stop_loss_pct: parseFloat(sl) / 100.0
})
});
fetchStatus();
}
async function toggleBot() {
await updateConfig();
await fetch('/api/toggle', { method: 'POST' });
fetchStatus();
}
async function emergencySell() {
if (confirm("Voulez-vous vraiment vendre immédiatement au marché ?")) {
await fetch('/api/emergency_sell', { method: 'POST' });
fetchStatus();
}
}
setInterval(fetchStatus, 1000);
</script>
</head>
<body onload="fetchStatus()">
<div id="header_title" class="header-title">⚡ Scalping Pro Spot (Chargement...)</div>
<div class="main-layout">
<div class="card">
<div class="card-title">Configuration & Contrôle</div>
<div id="bot_state_text" class="bot-status-text status-stopped">BOT ARRÊTÉ</div>
<label for="symbol">Sélectionner la Crypto :</label>
<select id="symbol" onchange="updateConfig()">
<option value="SOL_USDT" selected>SOL / USDT (Recommandé)</option>
<option value="BTC_USDT">BTC / USDT</option>
<option value="ETH_USDT">ETH / USDT</option>
<option value="DOGE_USDT">DOGE / USDT</option>
<option value="PEPE_USDT">PEPE / USDT</option>
</select>
<label for="timeframe">Unité de Temps Indicateurs :</label>
<select id="timeframe" onchange="updateConfig()">
<option value="5M" selected>5 Minutes (Recommandé)</option>
<option value="1M">1 Minute (Scalping Rapide)</option>
<option value="15M">15 Minutes (Tendance)</option>
</select>
<label for="amount">Capital Investi (USDT) :</label>
<input type="number" id="amount" value="12" min="10" step="1" onchange="updateConfig()">
<label for="tp_pct">Max Take Profit (%) :</label>
<input type="number" id="tp_pct" value="1.0" step="0.1" onchange="updateConfig()">
<label for="sl_pct">Protection Stop Loss (%) :</label>
<input type="number" id="sl_pct" value="0.5" step="0.1" onchange="updateConfig()">
<button id="toggle_btn" onclick="toggleBot()" class="btn btn-green">DÉMARRER LE BOT</button>
<button onclick="emergencySell()" class="btn btn-orange">🚨 VENTE URGENCE MARCHÉ</button>
</div>
<div>
<div class="top-stats">
<div class="stat-card">
<div class="stat-label">Prix Direct Spot</div>
<div class="stat-value blue" id="stat_price">$0.00</div>
</div>
<div class="stat-card">
<div class="stat-label">PnL En Cours Net</div>
<div class="stat-value" id="stat_pnl">+0.00%</div>
</div>
<div class="stat-card">
<div class="stat-label">Profit Total Cumulé</div>
<div class="stat-value red" id="stat_total_pnl">-0.30 $</div>
</div>
<div class="stat-card">
<div class="stat-label">Taux Réussite</div>
<div class="stat-value" style="color:#00e676" id="stat_winrate">45.5%</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Trades</div>
<div class="stat-value purple" id="stat_trades">11</div>
</div>
<div class="stat-card">
<div class="stat-label">Décision</div>
<div class="stat-value amber" id="stat_action">HOLD</div>
</div>
</div>
<div class="trade-details-box">
<div class="trade-title-bar">
<span class="trade-title-text">📊 DÉTAILS DE L'ORDRE & POSITION ACTIVE</span>
<span id="trade_badge" class="trade-badge badge-gray">⚪ EN ATTENTE D'UN SIGNAL HAUSSIER</span>
</div>
<div class="trade-details-grid">
<div class="trade-item">
<div class="trade-item-title">Type Ordre / Qté</div>
<div class="trade-item-val" id="val_type_order" style="color:#f59e0b;">--</div>
<div class="trade-item-val" id="val_crypto_qty" style="font-size:13px; color:#ffffff; margin-top:2px;">--</div>
<div class="trade-item-sub" id="sub_invest">Capital: $12.00 USDT</div>
</div>
<div class="trade-item">
<div class="trade-item-title">Prix d'Entrée & Temps</div>
<div class="trade-item-val" id="val_entry_price" style="color:#3b82f6;">--</div>
<div class="trade-item-sub" id="sub_entry_time">Heure: -- | Durée: --</div>
</div>
<div class="trade-item">
<div class="trade-item-title">Max Take-Profit ($)</div>
<div class="trade-item-val" id="val_target_price" style="color:#22c55e;">--</div>
<div class="trade-item-sub" id="sub_target_pct">Max TP: +1.00% (+0.80% Net)</div>
</div>
<div class="trade-item">
<div class="trade-item-title">Stop Loss / Break-Even</div>
<div class="trade-item-val" id="val_stop_price" style="color:#ef4444;">--</div>
<div class="trade-item-sub" id="sub_be_state">Break-Even: Inactif</div>
</div>
<div class="trade-item">
<div class="trade-item-title">Sommet Atteint ($)</div>
<div class="trade-item-val" id="val_max_seen" style="color:#a855f7;">--</div>
<div class="trade-item-sub">Plus Haut du Trade</div>
</div>
<div class="trade-item" style="grid-column: span 3;">
<div class="trade-item-title">Gain / Perte Latent Net (Frais Déduits)</div>
<div class="trade-item-val" id="val_current_pnl" style="font-size:18px;">--</div>
<div class="trade-item-sub">Calculé en temps réel avec 0.20% de frais Pionex inclus</div>
</div>
</div>
</div>
<div class="gauge-card">
<div class="gauge-header">
<span>PROGRESSION DU TRADE VERS TP</span>
<span id="gauge_val" style="color: #22c55e;">0.0%</span>
</div>
<div class="progress-bg">
<div id="gauge_bar" class="progress-fill"></div>
</div>
<div class="gauge-labels">
<span id="lbl_sl" style="color:#ef4444;">SL/BE: $0.00</span>
<span id="lbl_buy" style="color:#3b82f6;">Achat: $0.00</span>
<span id="lbl_tp" style="color:#22c55e;">Max TP: $0.00</span>
</div>
</div>
<div class="indicators-grid">
<div class="ind-box">
<div class="ind-title">RSI (14)</div>
<div class="ind-val" id="ind_rsi">0.0</div>
</div>
<div class="ind-box">
<div class="ind-title">STOCHRSI K</div>
<div class="ind-val" id="ind_stoch">0.0</div>
</div>
<div class="ind-box">
<div class="ind-title">BAS BOLLINGER</div>
<div class="ind-val" id="ind_bbl">$0.00</div>
</div>
<div class="ind-box">
<div class="ind-title">HAUT BOLLINGER</div>
<div class="ind-val" id="ind_bbu">$0.00</div>
</div>
</div>
<div class="card">
<div class="card-title" style="text-align: left;">Flux d'Activité et Execution Temps Réel</div>
<div id="logs" class="log-container">
<div class="log-line">🟢 Bot réajusté (Conditions débloquées, réactivité optimale).</div>
</div>
</div>
</div>
</div>
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_TEMPLATE)
@app.route('/api/status')
def api_status():
return jsonify({"status": bot_status, "config": bot_config})
@app.route('/api/config', methods=['POST'])
def api_config():
data = request.json
if "symbol" in data:
bot_config["symbol"] = data["symbol"]
if "amount_usdt" in data:
bot_config["amount_usdt"] = float(data["amount_usdt"])
if "timeframe" in data:
bot_config["timeframe"] = data["timeframe"]
if "take_profit_pct" in data:
bot_config["take_profit_pct"] = float(data["take_profit_pct"])
if "stop_loss_pct" in data:
bot_config["stop_loss_pct"] = float(data["stop_loss_pct"])
return jsonify({"success": True})
@app.route('/api/toggle', methods=['POST'])
def api_toggle():
bot_config["is_running"] = not bot_config["is_running"]
state = "DÉMARRÉ" if bot_config["is_running"] else "ARRÊTÉ"
add_log(f"Bot {state}.")
return jsonify({"is_running": bot_config["is_running"]})
@app.route('/api/emergency_sell', methods=['POST'])
def api_emergency_sell():
if bot_status["in_position"]:
add_log("🚨 Vente forcée d'urgence déclenchée...")
qty = bot_status["crypto_qty"]
res = place_order(bot_config["symbol"], "SELL", qty, order_type="MARKET")
if res.get("result"):
bot_status["in_position"] = False
bot_status["pos_count"] = 0
bot_status["gauge_percent"] = 0.0
add_log("✅ Vente forcée exécutée.")
return jsonify({"success": True})
else:
add_log(f"❌ Échec Vente d'urgence : {res}")
return jsonify({"success": False, "message": "Aucune position active."})
if __name__ == '__main__':
Thread(target=run_trader, daemon=True).start()
app.run(host='0.0.0.0', port=5000)
Partager le fichier import os.txt sur le Web et les réseaux sociaux:
Télécharger le fichier import os.txt
Télécharger import os.txt