Hébergeur de fichiers indépendant

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