Update app.py
Browse files
app.py
CHANGED
|
@@ -51,9 +51,9 @@ VIETNAM_TZ = ZoneInfo("Asia/Ho_Chi_Minh")
|
|
| 51 |
CONFIG = {
|
| 52 |
"RUNNING": False,
|
| 53 |
"AMOUNT": 10.0,
|
| 54 |
-
"LEV_MAIN": 25,
|
| 55 |
-
"LEV_ALTS": 15,
|
| 56 |
-
"LEV_HIGHVOL": 10,
|
| 57 |
"WICK_MAIN": 0.13,
|
| 58 |
"WICK_ALTS": 0.25,
|
| 59 |
"WICK_NEW": 0.4,
|
|
@@ -72,10 +72,8 @@ MARKET_DATA_CACHE = {}
|
|
| 72 |
|
| 73 |
def send_slack(msg):
|
| 74 |
if SLACK_WEBHOOK_URL:
|
| 75 |
-
try:
|
| 76 |
-
|
| 77 |
-
except:
|
| 78 |
-
pass
|
| 79 |
|
| 80 |
def okx_request(method, endpoint, body=None):
|
| 81 |
try:
|
|
@@ -106,15 +104,15 @@ def get_market_rules(symbol):
|
|
| 106 |
res = requests.get(url, timeout=10).json()
|
| 107 |
if res.get('code') == '0' and res['data']:
|
| 108 |
inst = res['data'][0]
|
|
|
|
| 109 |
data = {"lotSz": float(inst['lotSz']), "tickSz": float(inst['tickSz']),
|
| 110 |
-
"prec":
|
| 111 |
-
"ctVal": float(inst.get('ctVal', 1)), "minSz": float(inst['minSz'])}
|
| 112 |
MARKET_DATA_CACHE[symbol] = data
|
| 113 |
return data
|
| 114 |
except: return None
|
| 115 |
|
| 116 |
# ==============================================================================
|
| 117 |
-
# ========== CORE LOGIC ==========
|
| 118 |
# ==============================================================================
|
| 119 |
|
| 120 |
def calculate_wick_pct(row):
|
|
@@ -126,67 +124,60 @@ def calculate_wick_pct(row):
|
|
| 126 |
def execute_trade(symbol, side, n0_data, n_curr_data):
|
| 127 |
try:
|
| 128 |
rules = get_market_rules(symbol)
|
| 129 |
-
if not rules:
|
| 130 |
-
send_slack(f"❌ LỖI: Không lấy được thông số thị trường cho {symbol}")
|
| 131 |
-
return
|
| 132 |
|
| 133 |
-
#
|
| 134 |
-
if symbol in BTC_ETH_GOLD:
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
target_lev = int(CONFIG["LEV_ALTS"])
|
| 138 |
-
else:
|
| 139 |
-
target_lev = int(CONFIG["LEV_HIGHVOL"])
|
| 140 |
|
| 141 |
-
# Set Leverage cho OKX
|
| 142 |
okx_request("POST", "/api/v5/account/set-leverage", {
|
| 143 |
"instId": symbol, "lever": str(target_lev), "mgnMode": "isolated", "posSide": side
|
| 144 |
})
|
| 145 |
|
| 146 |
-
# Logic Entry
|
| 147 |
curr_close, curr_high, curr_low = n_curr_data['c'], n_curr_data['h'], n_curr_data['l']
|
| 148 |
if side == "long":
|
| 149 |
entry = curr_close if curr_close >= curr_high else min(n0_data['o'], n0_data['c'])
|
| 150 |
else:
|
| 151 |
entry = curr_close if curr_close <= curr_low else max(n0_data['o'], n0_data['c'])
|
| 152 |
|
| 153 |
-
|
| 154 |
buffer = CONFIG["SL_BUFFER_PCT"] / 100
|
| 155 |
sl_price = n0_data['l'] * (1 - buffer) if side == "long" else n0_data['h'] * (1 + buffer)
|
| 156 |
risk = abs(entry - sl_price)
|
| 157 |
-
if risk == 0:
|
| 158 |
-
send_slack(f"⚠️ {symbol}: Bỏ qua lệnh do Risk = 0")
|
| 159 |
-
return
|
| 160 |
|
| 161 |
-
|
| 162 |
-
tp = round((entry + risk * CONFIG["RR_RATIO"]) if side == "long" else (entry - risk * CONFIG["RR_RATIO"]), rules['prec'])
|
| 163 |
|
| 164 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
size = math.floor((CONFIG["AMOUNT"] * target_lev / (entry * rules['ctVal'])) / rules['lotSz']) * rules['lotSz']
|
| 166 |
-
if size < rules['minSz']:
|
| 167 |
-
send_slack(f"⚠️ {symbol}: Size {size} nhỏ hơn min {rules['minSz']}")
|
| 168 |
-
return
|
| 169 |
|
| 170 |
body = {
|
| 171 |
"instId": symbol, "tdMode": "isolated", "side": "buy" if side == "long" else "sell",
|
| 172 |
-
"posSide": side, "ordType": "limit", "px":
|
| 173 |
"attachAlgoOrds": [
|
| 174 |
-
{"attachAlgoOrdType": "sl", "slTriggerPx":
|
| 175 |
-
{"attachAlgoOrdType": "tp", "tpTriggerPx":
|
| 176 |
]
|
| 177 |
}
|
| 178 |
res = okx_request("POST", "/api/v5/trade/order", body)
|
| 179 |
|
| 180 |
if res and res.get('code') == '0':
|
| 181 |
-
msg = f"🚀 ✅ {side.upper()} {symbol} (x{target_lev}) | Entry: {
|
| 182 |
-
print(f" └─ ✅ {msg}", flush=True)
|
| 183 |
send_slack(msg)
|
|
|
|
| 184 |
else:
|
| 185 |
err_msg = res.get('msg', 'Unknown Error')
|
| 186 |
-
|
| 187 |
-
msg = f"❌ ⛔ ĐẶT LỆNH LỖI: {symbol} | {side.upper()}\nLý do: {err_msg} (Code: {err_code})"
|
| 188 |
-
print(f" └─ {msg}", flush=True)
|
| 189 |
send_slack(msg)
|
|
|
|
| 190 |
|
| 191 |
except Exception as e:
|
| 192 |
send_slack(f"🚨 CRITICAL ERROR: {symbol} | {str(e)}")
|
|
@@ -218,8 +209,7 @@ def scan_symbol(symbol, X, Y, open_symbols, current_count):
|
|
| 218 |
if symbol not in open_symbols:
|
| 219 |
return current_count + 1
|
| 220 |
return current_count
|
| 221 |
-
except:
|
| 222 |
-
return current_count
|
| 223 |
|
| 224 |
def run_market_scan():
|
| 225 |
open_symbols = get_all_open_positions()
|
|
@@ -230,10 +220,8 @@ def run_market_scan():
|
|
| 230 |
|
| 231 |
for s in BTC_ETH_GOLD:
|
| 232 |
current_count = scan_symbol(s, CONFIG["WICK_MAIN"], Y, open_symbols, current_count)
|
| 233 |
-
|
| 234 |
for s in ALTS_STANDARD:
|
| 235 |
current_count = scan_symbol(s, CONFIG["WICK_ALTS"], Y, open_symbols, current_count)
|
| 236 |
-
|
| 237 |
for s in ALTS_HIGH_VOL:
|
| 238 |
current_count = scan_symbol(s, CONFIG["WICK_NEW"], Y, open_symbols, current_count)
|
| 239 |
|
|
@@ -243,19 +231,11 @@ def run_market_scan():
|
|
| 243 |
|
| 244 |
def update_ui(amt, lev_m, lev_a, lev_h, m_main, m_alt, m_new, sub, rr, buf, run):
|
| 245 |
CONFIG.update({
|
| 246 |
-
"AMOUNT": amt,
|
| 247 |
-
"
|
| 248 |
-
"
|
| 249 |
-
"LEV_HIGHVOL": lev_h,
|
| 250 |
-
"WICK_MAIN": m_main,
|
| 251 |
-
"WICK_ALTS": m_alt,
|
| 252 |
-
"WICK_NEW": m_new,
|
| 253 |
-
"WICK_SUB": sub,
|
| 254 |
-
"RR_RATIO": rr,
|
| 255 |
-
"SL_BUFFER_PCT": buf,
|
| 256 |
-
"RUNNING": run
|
| 257 |
})
|
| 258 |
-
return f"Bot: {'ON' if run else 'OFF'} |
|
| 259 |
|
| 260 |
def main_loop():
|
| 261 |
while True:
|
|
@@ -269,37 +249,27 @@ def main_loop():
|
|
| 269 |
|
| 270 |
threading.Thread(target=main_loop, daemon=True).start()
|
| 271 |
|
| 272 |
-
with gr.Blocks(title="OKX Bot
|
| 273 |
-
gr.Markdown("# 🤖 OKX Bot
|
| 274 |
-
|
| 275 |
with gr.Tab("Cấu hình Giao dịch"):
|
| 276 |
with gr.Row():
|
| 277 |
n_amt = gr.Number(label="Vốn/Lệnh (USDT)", value=10)
|
| 278 |
n_rr = gr.Number(label="R:R Ratio", value=1.5)
|
| 279 |
n_buf = gr.Number(label="SL Buffer (%)", value=0.15)
|
| 280 |
-
|
| 281 |
-
gr.Markdown("### 🎚️ Thiết lập Đòn bẩy (Leverage 1 - 100)")
|
| 282 |
with gr.Row():
|
| 283 |
-
lev_main = gr.Slider(
|
| 284 |
-
lev_alts = gr.Slider(
|
| 285 |
-
lev_highvol = gr.Slider(
|
| 286 |
-
|
| 287 |
-
with gr.Tab("Cấu hình Kỹ thuật (Râu nến)"):
|
| 288 |
with gr.Row():
|
| 289 |
n_main = gr.Number(label="Râu BTC/ETH", value=0.13)
|
| 290 |
n_alt = gr.Number(label="Râu Alt Top", value=0.25)
|
| 291 |
n_new = gr.Number(label="Râu High Vol", value=0.4)
|
| 292 |
n_sub = gr.Number(label="Râu Phụ Max", value=0.05)
|
| 293 |
-
|
| 294 |
c_run = gr.Checkbox(label="KÍCH HOẠT BOT")
|
| 295 |
-
btn = gr.Button("LƯU
|
| 296 |
-
out = gr.Textbox(label="
|
| 297 |
-
|
| 298 |
-
btn.click(
|
| 299 |
-
update_ui,
|
| 300 |
-
[n_amt, lev_main, lev_alts, lev_highvol, n_main, n_alt, n_new, n_sub, n_rr, n_buf, c_run],
|
| 301 |
-
out
|
| 302 |
-
)
|
| 303 |
|
| 304 |
if __name__ == "__main__":
|
| 305 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 51 |
CONFIG = {
|
| 52 |
"RUNNING": False,
|
| 53 |
"AMOUNT": 10.0,
|
| 54 |
+
"LEV_MAIN": 25,
|
| 55 |
+
"LEV_ALTS": 15,
|
| 56 |
+
"LEV_HIGHVOL": 10,
|
| 57 |
"WICK_MAIN": 0.13,
|
| 58 |
"WICK_ALTS": 0.25,
|
| 59 |
"WICK_NEW": 0.4,
|
|
|
|
| 72 |
|
| 73 |
def send_slack(msg):
|
| 74 |
if SLACK_WEBHOOK_URL:
|
| 75 |
+
try: requests.post(SLACK_WEBHOOK_URL, json={"text": msg}, timeout=5)
|
| 76 |
+
except: pass
|
|
|
|
|
|
|
| 77 |
|
| 78 |
def okx_request(method, endpoint, body=None):
|
| 79 |
try:
|
|
|
|
| 104 |
res = requests.get(url, timeout=10).json()
|
| 105 |
if res.get('code') == '0' and res['data']:
|
| 106 |
inst = res['data'][0]
|
| 107 |
+
prec = len(inst['tickSz'].split('.')[-1]) if '.' in inst['tickSz'] else 0
|
| 108 |
data = {"lotSz": float(inst['lotSz']), "tickSz": float(inst['tickSz']),
|
| 109 |
+
"prec": prec, "ctVal": float(inst.get('ctVal', 1)), "minSz": float(inst['minSz'])}
|
|
|
|
| 110 |
MARKET_DATA_CACHE[symbol] = data
|
| 111 |
return data
|
| 112 |
except: return None
|
| 113 |
|
| 114 |
# ==============================================================================
|
| 115 |
+
# ========== CORE LOGIC (VẬN HÀNH THEO APP.PY) ==========
|
| 116 |
# ==============================================================================
|
| 117 |
|
| 118 |
def calculate_wick_pct(row):
|
|
|
|
| 124 |
def execute_trade(symbol, side, n0_data, n_curr_data):
|
| 125 |
try:
|
| 126 |
rules = get_market_rules(symbol)
|
| 127 |
+
if not rules: return
|
|
|
|
|
|
|
| 128 |
|
| 129 |
+
# 1. Chọn đòn bẩy theo nhóm
|
| 130 |
+
if symbol in BTC_ETH_GOLD: target_lev = int(CONFIG["LEV_MAIN"])
|
| 131 |
+
elif symbol in ALTS_STANDARD: target_lev = int(CONFIG["LEV_ALTS"])
|
| 132 |
+
else: target_lev = int(CONFIG["LEV_HIGHVOL"])
|
|
|
|
|
|
|
|
|
|
| 133 |
|
|
|
|
| 134 |
okx_request("POST", "/api/v5/account/set-leverage", {
|
| 135 |
"instId": symbol, "lever": str(target_lev), "mgnMode": "isolated", "posSide": side
|
| 136 |
})
|
| 137 |
|
| 138 |
+
# 2. Logic Entry khớp 100% app.py gốc
|
| 139 |
curr_close, curr_high, curr_low = n_curr_data['c'], n_curr_data['h'], n_curr_data['l']
|
| 140 |
if side == "long":
|
| 141 |
entry = curr_close if curr_close >= curr_high else min(n0_data['o'], n0_data['c'])
|
| 142 |
else:
|
| 143 |
entry = curr_close if curr_close <= curr_low else max(n0_data['o'], n0_data['c'])
|
| 144 |
|
| 145 |
+
# 3. Tính toán SL/TP với độ chính xác chuẩn sàn
|
| 146 |
buffer = CONFIG["SL_BUFFER_PCT"] / 100
|
| 147 |
sl_price = n0_data['l'] * (1 - buffer) if side == "long" else n0_data['h'] * (1 + buffer)
|
| 148 |
risk = abs(entry - sl_price)
|
| 149 |
+
if risk == 0: return
|
|
|
|
|
|
|
| 150 |
|
| 151 |
+
tp_price = (entry + risk * CONFIG["RR_RATIO"]) if side == "long" else (entry - risk * CONFIG["RR_RATIO"])
|
|
|
|
| 152 |
|
| 153 |
+
# Format string tránh lỗi Code 1
|
| 154 |
+
fmt_entry = "{:.{}f}".format(entry, rules['prec'])
|
| 155 |
+
fmt_sl = "{:.{}f}".format(sl_price, rules['prec'])
|
| 156 |
+
fmt_tp = "{:.{}f}".format(tp_price, rules['prec'])
|
| 157 |
+
|
| 158 |
+
# 4. Tính Size
|
| 159 |
size = math.floor((CONFIG["AMOUNT"] * target_lev / (entry * rules['ctVal'])) / rules['lotSz']) * rules['lotSz']
|
| 160 |
+
if size < rules['minSz']: return
|
|
|
|
|
|
|
| 161 |
|
| 162 |
body = {
|
| 163 |
"instId": symbol, "tdMode": "isolated", "side": "buy" if side == "long" else "sell",
|
| 164 |
+
"posSide": side, "ordType": "limit", "px": fmt_entry, "sz": str(size),
|
| 165 |
"attachAlgoOrds": [
|
| 166 |
+
{"attachAlgoOrdType": "sl", "slTriggerPx": fmt_sl, "slOrdPx": "-1"},
|
| 167 |
+
{"attachAlgoOrdType": "tp", "tpTriggerPx": fmt_tp, "tpOrdPx": "-1"}
|
| 168 |
]
|
| 169 |
}
|
| 170 |
res = okx_request("POST", "/api/v5/trade/order", body)
|
| 171 |
|
| 172 |
if res and res.get('code') == '0':
|
| 173 |
+
msg = f"🚀 ✅ {side.upper()} {symbol} (x{target_lev}) | Entry: {fmt_entry} | SL: {fmt_sl}"
|
|
|
|
| 174 |
send_slack(msg)
|
| 175 |
+
print(f" └─ ✅ {msg}", flush=True)
|
| 176 |
else:
|
| 177 |
err_msg = res.get('msg', 'Unknown Error')
|
| 178 |
+
msg = f"❌ ĐẶT LỆNH LỖI: {symbol} | {side.upper()} | {err_msg} (Code: {res.get('code')})"
|
|
|
|
|
|
|
| 179 |
send_slack(msg)
|
| 180 |
+
print(f" └─ {msg}", flush=True)
|
| 181 |
|
| 182 |
except Exception as e:
|
| 183 |
send_slack(f"🚨 CRITICAL ERROR: {symbol} | {str(e)}")
|
|
|
|
| 209 |
if symbol not in open_symbols:
|
| 210 |
return current_count + 1
|
| 211 |
return current_count
|
| 212 |
+
except: return current_count
|
|
|
|
| 213 |
|
| 214 |
def run_market_scan():
|
| 215 |
open_symbols = get_all_open_positions()
|
|
|
|
| 220 |
|
| 221 |
for s in BTC_ETH_GOLD:
|
| 222 |
current_count = scan_symbol(s, CONFIG["WICK_MAIN"], Y, open_symbols, current_count)
|
|
|
|
| 223 |
for s in ALTS_STANDARD:
|
| 224 |
current_count = scan_symbol(s, CONFIG["WICK_ALTS"], Y, open_symbols, current_count)
|
|
|
|
| 225 |
for s in ALTS_HIGH_VOL:
|
| 226 |
current_count = scan_symbol(s, CONFIG["WICK_NEW"], Y, open_symbols, current_count)
|
| 227 |
|
|
|
|
| 231 |
|
| 232 |
def update_ui(amt, lev_m, lev_a, lev_h, m_main, m_alt, m_new, sub, rr, buf, run):
|
| 233 |
CONFIG.update({
|
| 234 |
+
"AMOUNT": amt, "LEV_MAIN": lev_m, "LEV_ALTS": lev_a, "LEV_HIGHVOL": lev_h,
|
| 235 |
+
"WICK_MAIN": m_main, "WICK_ALTS": m_alt, "WICK_NEW": m_new, "WICK_SUB": sub,
|
| 236 |
+
"RR_RATIO": rr, "SL_BUFFER_PCT": buf, "RUNNING": run
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 237 |
})
|
| 238 |
+
return f"Bot: {'ON' if run else 'OFF'} | Logic: app.py (Synced)"
|
| 239 |
|
| 240 |
def main_loop():
|
| 241 |
while True:
|
|
|
|
| 249 |
|
| 250 |
threading.Thread(target=main_loop, daemon=True).start()
|
| 251 |
|
| 252 |
+
with gr.Blocks(title="OKX Bot V18.0 Professional") as demo:
|
| 253 |
+
gr.Markdown("# 🤖 OKX Bot V18.0 - app.py Logic Synced")
|
|
|
|
| 254 |
with gr.Tab("Cấu hình Giao dịch"):
|
| 255 |
with gr.Row():
|
| 256 |
n_amt = gr.Number(label="Vốn/Lệnh (USDT)", value=10)
|
| 257 |
n_rr = gr.Number(label="R:R Ratio", value=1.5)
|
| 258 |
n_buf = gr.Number(label="SL Buffer (%)", value=0.15)
|
|
|
|
|
|
|
| 259 |
with gr.Row():
|
| 260 |
+
lev_main = gr.Slider(1, 100, 25, step=1, label="Leverage BTC/ETH Group")
|
| 261 |
+
lev_alts = gr.Slider(1, 100, 15, step=1, label="Leverage Alts Top Group")
|
| 262 |
+
lev_highvol = gr.Slider(1, 100, 10, step=1, label="Leverage High Vol Group")
|
| 263 |
+
with gr.Tab("Cấu hình Râu"):
|
|
|
|
| 264 |
with gr.Row():
|
| 265 |
n_main = gr.Number(label="Râu BTC/ETH", value=0.13)
|
| 266 |
n_alt = gr.Number(label="Râu Alt Top", value=0.25)
|
| 267 |
n_new = gr.Number(label="Râu High Vol", value=0.4)
|
| 268 |
n_sub = gr.Number(label="Râu Phụ Max", value=0.05)
|
|
|
|
| 269 |
c_run = gr.Checkbox(label="KÍCH HOẠT BOT")
|
| 270 |
+
btn = gr.Button("LƯU & CHẠY", variant="primary")
|
| 271 |
+
out = gr.Textbox(label="Status")
|
| 272 |
+
btn.click(update_ui, [n_amt, lev_main, lev_alts, lev_highvol, n_main, n_alt, n_new, n_sub, n_rr, n_buf, c_run], out)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 273 |
|
| 274 |
if __name__ == "__main__":
|
| 275 |
demo.launch(server_name="0.0.0.0", server_port=7860)
|