multi_pin / app.py
toanvv7's picture
Update app.py
11f0681 verified
import requests
import pandas as pd
import time
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
import os
import threading
import gradio as gr
from dotenv import load_dotenv
import hmac
import hashlib
import json
import base64
from decimal import Decimal, ROUND_DOWN
import math
# ==============================================================================
# ========== CẤU HÌNH HỆ THỐNG ==========
# ==============================================================================
if os.path.exists(".env"):
load_dotenv(".env")
OKX_API_KEY = os.environ.get("OKX_API_KEY")
OKX_SECRET_KEY = os.environ.get("OKX_SECRET_KEY")
OKX_PASSPHRASE = os.environ.get("OKX_PASSPHRASE")
OKX_BASE_URL = "https://www.okx.com"
SLACK_WEBHOOK_URL = os.environ.get("SLACK_WEBHOOK_URL")
VIETNAM_TZ = ZoneInfo("Asia/Ho_Chi_Minh")
# 1. CHỈ KIỂM TRA BTC VÀ XAU (ĐÃ XÓA XAG)
SYMBOLS = ["BTC-USDT-SWAP", "XAU-USDT-SWAP"]
CONFIG = {
"RUNNING": False,
"BTC": {
"amount": 10.0, "lev": 25, "body_pct": 0.03,
"long_l_wick": 0.13, "long_u_wick": 0.03,
"short_u_wick": 0.13, "short_l_wick": 0.03
},
"XAU": {
"amount": 10.0, "lev": 25, "body_pct": 0.03,
"long_l_wick": 0.13, "long_u_wick": 0.03,
"short_u_wick": 0.13, "short_l_wick": 0.03
},
"SL_BUFFER": 0.13,
"RR": 3.0,
"LAST_PROCESSED_MIN": -1
}
# ==============================================================================
# ========== HÀM HỖ TRỢ API & LOGGING ==========
# ==============================================================================
def send_slack_msg(msg):
"""Gửi thông báo đến Slack qua Webhook."""
print(f">>> SLACK LOG: {msg}")
if SLACK_WEBHOOK_URL:
try:
payload = {"text": f"🔔 *TÍN HIỆU CHIẾN THUẬT RÂU*: {msg}"}
requests.post(SLACK_WEBHOOK_URL, json=payload, timeout=10)
except Exception as e:
print(f"Lỗi gửi Slack: {e}")
def print_log_table(data_list):
header = f"{'SYMBOL':<12} | {'SIDE':<6} | {'BODY%':<8} | {'U-WICK%':<8} | {'L-WICK%':<8} | {'ACTION':<10}"
separator = "-" * len(header)
print(f"\n🕒 QUÉT LÚC: {datetime.now(VIETNAM_TZ).strftime('%Y-%m-%d %H:%M:%S')}")
print(separator)
print(header)
print(separator)
for d in data_list:
print(f"{d['sym']:<12} | {d['side']:<6} | {d['body']:<7.3f}% | {d['u_wick']:<7.3f}% | {d['l_wick']:<7.3f}% | {d['act']:<10}")
print(separator + "\n")
def okx_request(method, endpoint, body=None):
# Hàm này giữ nguyên để lấy thông tin tài khoản nếu cần,
# nhưng phần đặt lệnh đã được comment out ở dưới.
try:
ts = datetime.now(timezone.utc).isoformat(timespec='milliseconds').replace("+00:00", "Z")
body_str = json.dumps(body) if body else ""
message = ts + method + endpoint + body_str
mac = hmac.new(bytes(OKX_SECRET_KEY, 'utf-8'), bytes(message, 'utf-8'), hashlib.sha256)
sign = base64.b64encode(mac.digest()).decode()
headers = {
'OK-ACCESS-KEY': OKX_API_KEY, 'OK-ACCESS-SIGN': sign,
'OK-ACCESS-TIMESTAMP': ts, 'OK-ACCESS-PASSPHRASE': OKX_PASSPHRASE,
'Content-Type': 'application/json'
}
res = requests.request(method, OKX_BASE_URL + endpoint, headers=headers, data=body_str, timeout=15)
return res.json()
except Exception as e:
return {"code": "-1", "msg": str(e)}
def get_market_rules(symbol):
try:
url = f"{OKX_BASE_URL}/api/v5/public/instruments?instType=SWAP&instId={symbol}"
res = requests.get(url, timeout=10).json()
if res.get('code') == '0' and res['data']:
inst = res['data'][0]
prec = len(inst['tickSz'].split('.')[-1]) if '.' in inst['tickSz'] else 0
return {
"lotSz": float(inst['lotSz']),
"tickSz": float(inst['tickSz']),
"prec": prec,
"ctVal": float(inst.get('ctVal', 1)),
"minSz": float(inst['minSz'])
}
except Exception as e:
print(f"Lỗi lấy rules cho {symbol}: {e}")
return None
# ==============================================================================
# ========== CORE LOGIC STRATEGY ==========
# ==============================================================================
def run_scanner():
scan_results = []
for symbol in SYMBOLS:
try:
url = f"{OKX_BASE_URL}/api/v5/market/history-candles?instId={symbol}&bar=5m&limit=5"
resp = requests.get(url, timeout=10).json()
if not resp or not resp.get('data'): continue
df = pd.DataFrame(resp['data'], columns=['ts','o','h','l','c','v','volCcy','volCcyQuote','confirm'])
df[['o','h','l','c']] = df[['o','h','l','c']].astype(float)
n_curr, n0 = df.iloc[0], df.iloc[1]
cfg_key = "BTC" if "BTC" in symbol else "XAU"
CONF = CONFIG[cfg_key]
# 2. LOGIC TÍNH TOÁN % (Giữ nguyên logic cũ theo yêu cầu)
body_val0 = abs(n0['c'] - n0['o'])
u_wick_abs = n0['h'] - max(n0['c'], n0['o'])
l_wick_abs = min(n0['c'], n0['o']) - n0['l']
body_pct0 = (body_val0 * 100 / n0['o']) if n0['o'] != 0 else 0
u_wick_pct0 = (u_wick_abs * 100 / n0['c']) if n0['c'] != 0 else 0
l_wick_pct0 = (l_wick_abs * 100 / n0['c']) if n0['c'] != 0 else 0
action = "WAIT"
# 3. ĐIỀU KIỆN VÀO LỆNH THEO RÂU NẾN N0
# Kiểm tra thân nến tối thiểu
if body_pct0 >= CONF['body_pct']:
# LONG: Râu dưới >= X% và râu trên <= Y%
if l_wick_pct0 >= CONF['long_l_wick'] and u_wick_pct0 <= CONF['long_u_wick']:
action = "🔵 LONG ALERT"
execute_trade(symbol, "long", n0, n_curr, CONF)
# SHORT: Râu trên >= X% và râu dưới <= Y%
elif u_wick_pct0 >= CONF['short_u_wick'] and l_wick_pct0 <= CONF['short_l_wick']:
action = "🔴 SHORT ALERT"
execute_trade(symbol, "short", n0, n_curr, CONF)
scan_results.append({
"sym": symbol.replace("-SWAP",""),
"side": "GREEN" if n0['c'] > n0['o'] else "RED",
"body": body_pct0, "u_wick": u_wick_pct0, "l_wick": l_wick_pct0,
"act": action
})
except Exception as e:
print(f"❌ Error scanning {symbol}: {e}")
if scan_results:
print_log_table(scan_results)
def execute_trade(symbol, side, n0_data, n_curr_data, CONF):
"""
Hàm này giờ chỉ thực hiện tính toán thông số và gửi Slack Alert.
Toàn bộ code API đặt lệnh đã được COMMENT OUT.
"""
try:
rules = get_market_rules(symbol)
if not rules: return
target_lev = int(CONF['lev'])
curr_close = n_curr_data['c']
# Tính toán giá Entry/SL/TP cơ bản để gửi cảnh báo
if side == "long":
entry = curr_close
sl_price = n0_data['l'] * (1 - CONFIG["SL_BUFFER"] / 100)
else:
entry = curr_close
sl_price = n0_data['h'] * (1 + CONFIG["SL_BUFFER"] / 100)
risk = abs(entry - sl_price)
tp_price = (entry + risk * CONFIG["RR"]) if side == "long" else (entry - risk * CONFIG["RR"])
fmt_entry = "{:.{}f}".format(entry, rules['prec'])
fmt_sl = "{:.{}f}".format(sl_price, rules['prec'])
fmt_tp = "{:.{}f}".format(tp_price, rules['prec'])
# GỬI CẢNH BÁO SLACK
msg = (f"🚀 *PHÁT HIỆN TÍN HIỆU {side.upper()}*\n"
f"- Symbol: {symbol}\n"
f"- Giá hiện tại: {fmt_entry}\n"
f"- Gợi ý SL: {fmt_sl}\n"
f"- Gợi ý TP: {fmt_tp}\n"
f"- Râu nến N0 đạt điều kiện thiết lập.")
send_slack_msg(msg)
# ======================================================================
# ========== PHẦN CODE ĐẶT LỆNH - ĐÃ COMMENT OUT THEO YÊU CẦU ==========
# ======================================================================
"""
# Đặt Leverage
okx_request("POST", "/api/v5/account/set-leverage", {
"instId": symbol, "lever": str(target_lev), "mgnMode": "isolated", "posSide": side
})
size = math.floor((CONF['amount'] * target_lev / (entry * rules['ctVal'])) / rules['lotSz']) * rules['lotSz']
if size < rules['minSz']:
print(f"⚠️ {symbol}: Size {size} < Min {rules['minSz']}")
return
body = {
"instId": symbol, "tdMode": "isolated", "side": "buy" if side == "long" else "sell",
"posSide": side, "ordType": "limit", "px": fmt_entry, "sz": str(size),
"attachAlgoOrds": [
{"attachAlgoOrdType": "sl", "slTriggerPx": fmt_sl, "slOrdPx": "-1"},
{"attachAlgoOrdType": "tp", "tpTriggerPx": fmt_tp, "tpOrdPx": "-1"}
]
}
res = okx_request("POST", "/api/v5/trade/order", body)
"""
# ======================================================================
except Exception as e:
print(f"🚨 LỖI HÀM CẢNH BÁO ({symbol}): {str(e)}")
# ==============================================================================
# ========== GRADIO UI & LOOP ==========
# ==============================================================================
def update_ui(btc_amt, btc_lev, btc_body, b_l_l, b_l_u, b_s_u, b_s_l,
xau_amt, xau_lev, xau_body, x_l_l, x_l_u, x_s_u, x_s_l,
rr, running):
CONFIG["BTC"].update({
"amount": btc_amt, "lev": btc_lev, "body_pct": btc_body,
"long_l_wick": b_l_l, "long_u_wick": b_l_u, "short_u_wick": b_s_u, "short_l_wick": b_s_l
})
CONFIG["XAU"].update({
"amount": xau_amt, "lev": xau_lev, "body_pct": xau_body,
"long_l_wick": x_l_l, "long_u_wick": x_l_u, "short_u_wick": x_s_u, "short_l_wick": x_s_l
})
CONFIG["RR"] = rr
CONFIG["RUNNING"] = running
status = "BẬT" if running else "TẮT"
return f"Hệ thống ALERT {status} - Đã cập nhật chiến lược râu nến cho BTC & XAU."
def main_loop():
while True:
if CONFIG["RUNNING"]:
now = datetime.now(VIETNAM_TZ)
# Quét mỗi 5 phút (nến 5m)
if now.minute % 5 == 0 and now.minute != CONFIG["LAST_PROCESSED_MIN"]:
time.sleep(5) # Đợi nến đóng hẳn
run_scanner()
CONFIG["LAST_PROCESSED_MIN"] = now.minute
time.sleep(1)
threading.Thread(target=main_loop, daemon=True).start()
with gr.Blocks(title="OKX Wick Alert V1.0") as demo:
gr.Markdown("# 🤖 OKX Wick Strategy Alert (Chỉ cảnh báo Slack)")
with gr.Row():
with gr.Column():
gr.Markdown("### 🟠 Cấu hình BTC")
b_amt = gr.Number(label="Vốn giả định (USDT)", value=10)
b_lev = gr.Slider(1, 100, 25, label="Đòn bẩy")
b_body = gr.Number(label="Thân nến tối thiểu (%)", value=0.03)
with gr.Row():
b_l_l = gr.Number(label="LONG: Râu Dưới >= (%)", value=0.13)
b_l_u = gr.Number(label="LONG: Râu Trên <= (%)", value=0.03)
with gr.Row():
b_s_u = gr.Number(label="SHORT: Râu Trên >= (%)", value=0.13)
b_s_l = gr.Number(label="SHORT: Râu Dưới <= (%)", value=0.03)
with gr.Column():
gr.Markdown("### 🟡 Cấu hình XAU (Vàng)")
x_amt = gr.Number(label="Vốn giả định (USDT)", value=10)
x_lev = gr.Slider(1, 100, 25, label="Đòn bẩy")
x_body = gr.Number(label="Thân nến tối thiểu (%)", value=0.03)
with gr.Row():
x_l_l = gr.Number(label="LONG: Râu Dưới >= (%)", value=0.13)
x_l_u = gr.Number(label="LONG: Râu Trên <= (%)", value=0.03)
with gr.Row():
x_s_u = gr.Number(label="SHORT: Râu Trên >= (%)", value=0.13)
x_s_l = gr.Number(label="SHORT: Râu Dưới <= (%)", value=0.03)
with gr.Row():
n_rr = gr.Number(label="Tỉ lệ R:R", value=3.0)
c_run = gr.Checkbox(label="KÍCH HOẠT QUÉT TÍN HIỆU")
btn = gr.Button("CẬP NHẬT CẤU HÌNH", variant="primary")
out = gr.Textbox(label="Trạng thái")
btn.click(update_ui,
[b_amt, b_lev, b_body, b_l_l, b_l_u, b_s_u, b_s_l,
x_amt, x_lev, x_body, x_l_l, x_l_u, x_s_u, x_s_l,
n_rr, c_run], out)
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)