|
|
@@ -29,11 +29,12 @@ ok_chain_client.api_config = okchain_api # 假设ok_chain_client有此配置方
|
|
|
ARB_EXECUTOR_URL = arb["ARB_EXECUTOR_URL"]
|
|
|
|
|
|
# --- 配置部分 ---
|
|
|
-# IN_AMOUNT_TO_QUERY 将在循环中动态确定
|
|
|
+# EXCHANGE_OUT_AMOUNT 将在循环中动态确定
|
|
|
EXCHANGE_OUT_AMOUNT = decimal.Decimal(str(arb["EXCHANGE_OUT_AMOUNT"])) # 确保是Decimal
|
|
|
PROFIT_LIMIT = decimal.Decimal(str(arb["PROFIT_LIMIT"])) # 确保是Decimal
|
|
|
IN_TOKEN_ADDRESS = arb["IN_TOKEN_ADDRESS"]
|
|
|
IN_TOKEN_DECIMALS = arb["IN_TOKEN_DECIMALS"]
|
|
|
+IN_AMOUNT_TO_QUERY = arb["IN_AMOUNT_TO_QUERY"]
|
|
|
OUT_TOKEN_ADDRESS = arb["OUT_TOKEN_ADDRESS"]
|
|
|
SLIPPAGE = arb["SLIPPAGE"]
|
|
|
MEXC_TARGET_PAIR_USDT = arb["MEXC_TARGET_PAIR_USDT"]
|
|
|
@@ -62,7 +63,7 @@ historical_data_points = deque(maxlen=MAX_HISTORY_POINTS_PLOTLY)
|
|
|
TARGET_ASSET_SYMBOL = MEXC_TARGET_PAIR_USDT.split('_')[0] # e.g., RATO
|
|
|
BASE_CURRENCY_SYMBOL = MEXC_TARGET_PAIR_USDT.split('_')[1] # e.g., USDT (assumed to be consistent with IN_TOKEN_ADDRESS)
|
|
|
|
|
|
-# --- 链上价格获取函数 (Okx) ---
|
|
|
+# --- 链上价格获取函数 (链上) ---
|
|
|
# 返回: price_base_per_target (例如 USDT per RATO)
|
|
|
def get_chain_price_vs_target_currency(chain_id, in_token_addr, out_token_addr, amount_in_base_human, in_token_decimals, slippage, user_wallet_addr, user_exchange_wallet_addr):
|
|
|
try:
|
|
|
@@ -76,72 +77,78 @@ def get_chain_price_vs_target_currency(chain_id, in_token_addr, out_token_addr,
|
|
|
in_dec, out_dec = int(router_result['fromToken']['decimal']), int(router_result['toToken']['decimal'])
|
|
|
atomic_in_base, atomic_out_target = decimal.Decimal(router_result['fromTokenAmount']), decimal.Decimal(router_result['toTokenAmount'])
|
|
|
|
|
|
- human_in_base = atomic_in_base / (10 ** in_dec)
|
|
|
- human_out_target = atomic_out_target / (10 ** out_dec)
|
|
|
- if human_out_target == 0: return {"error": f"OO输出目标代币为0 ({CHAIN_ID})"}, data # data 也返回
|
|
|
- return {"price_base_per_target": human_in_base / human_out_target}, data
|
|
|
+ human_in_target = atomic_in_base / (10 ** in_dec)
|
|
|
+ human_out_base = atomic_out_target / (10 ** out_dec)
|
|
|
+ if human_out_base == 0: return {"error": f"OO输出目标代币为0 ({CHAIN_ID})"}, data # data 也返回
|
|
|
+
|
|
|
+ # 1target = x usdt,这个价格
|
|
|
+ return {"price_base_per_target": human_out_base / human_in_target }, data, human_out_base
|
|
|
else:
|
|
|
pprint.pprint(data)
|
|
|
return {
|
|
|
- "error": f"Okx API错误({chain_id}) - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"}, None
|
|
|
+ "error": f"链上 API错误({chain_id}) - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"}, None, 0
|
|
|
except Exception as e:
|
|
|
- logger.error(f"Okx ({chain_id})请求错误详情: ", exc_info=True)
|
|
|
- return {"error": f"Okx ({chain_id})请求错误: {e}"}, None
|
|
|
+ logger.error(f"链上 ({chain_id})请求错误详情: ", exc_info=True)
|
|
|
+ return {"error": f"链上 ({chain_id})请求错误: {e}"}, None
|
|
|
|
|
|
# MEXC 现货 (获取 目标代币/USDT 的 bid 价格)
|
|
|
-# 返回: price_target_per_usdt_bid1 (例如 RATO per USDT)
|
|
|
-def get_mexc_spot_price_target_usdt_bid(pair_symbol):
|
|
|
+# human_out_base: 需要买入大概多少价值的target
|
|
|
+def get_mexc_spot_price_target_usdt_ask(human_out_base):
|
|
|
url = "https://api.mexc.com/api/v3/depth"
|
|
|
- params = {'symbol': pair_symbol.replace('_', ''), 'limit': 1000} # 减少limit,5000可能过大且非必要
|
|
|
+ params = {'symbol': MEXC_TARGET_PAIR_USDT.replace('_', ''), 'limit': 1000}
|
|
|
+
|
|
|
try:
|
|
|
r = requests.get(url, params=params, proxies=proxies, timeout=5) # 减少超时
|
|
|
r.raise_for_status()
|
|
|
data = r.json()
|
|
|
- if 'bids' in data and data['bids']: # 确保bids存在且不为空
|
|
|
- bids = data['bids']
|
|
|
- trade_volume_remaining = EXCHANGE_OUT_AMOUNT # 还需要卖出的数量 (Decimal)
|
|
|
+ if 'asks' in data and data['asks']: # 确保asks存在且不为空
|
|
|
+ asks = data['asks']
|
|
|
+ trade_value_remaining = human_out_base # 还需要买入的价值(USDT)
|
|
|
trade_value = decimal.Decimal('0') # 累计的总价值 (Decimal)
|
|
|
accumulated_volume = decimal.Decimal('0') # 累计吃单量
|
|
|
|
|
|
- for orderbook in bids:
|
|
|
+ for orderbook in asks:
|
|
|
price = decimal.Decimal(orderbook[0])
|
|
|
volume = decimal.Decimal(orderbook[1])
|
|
|
|
|
|
- if trade_volume_remaining <= decimal.Decimal('0'):
|
|
|
- break # 已经满足卖出量
|
|
|
+ if trade_value_remaining <= decimal.Decimal('0'):
|
|
|
+ break # 已经满足买入量
|
|
|
|
|
|
- can_fill = min(volume, trade_volume_remaining)
|
|
|
+ can_fill = min(volume, trade_value_remaining / price)
|
|
|
trade_value += price * can_fill
|
|
|
accumulated_volume += can_fill
|
|
|
- trade_volume_remaining -= can_fill
|
|
|
+ trade_value_remaining -= price * can_fill
|
|
|
+
|
|
|
+ # print(trade_value, accumulated_volume, trade_value_remaining)
|
|
|
|
|
|
- if accumulated_volume == decimal.Decimal('0'): # 如果一点都没卖出去
|
|
|
- # logger.warning(f"MEXC无法以EXCHANGE_OUT_AMOUNT={EXCHANGE_OUT_AMOUNT}获取任何 efectiva 卖出价格,累积量为0")
|
|
|
- return {"error": f"MEXC订单簿深度不足以卖出{EXCHANGE_OUT_AMOUNT} {TARGET_ASSET_SYMBOL}"}, decimal.Decimal('0')
|
|
|
+ # print('\n\n')
|
|
|
+
|
|
|
+ if accumulated_volume == decimal.Decimal('0'): # 如果一点都没买入去
|
|
|
+ return {"error": f"MEXC订单簿深度不足以买入{human_out_base} {BASE_CURRENCY_SYMBOL}"}, decimal.Decimal('0')
|
|
|
|
|
|
- # 计算平均卖出价格
|
|
|
- # sell_price 代表 1 TARGET_ASSET = X USDT
|
|
|
- sell_price = trade_value / accumulated_volume
|
|
|
- sell_price = sell_price.quantize(decimal.Decimal('1e-10'), rounding=decimal.ROUND_DOWN)
|
|
|
+ # 计算平均买入价格
|
|
|
+ # buy_price 代表 1 TARGET_ASSET = X USDT
|
|
|
+ buy_price = trade_value / accumulated_volume
|
|
|
+ buy_price = buy_price.quantize(decimal.Decimal('1e-10'), rounding=decimal.ROUND_DOWN)
|
|
|
|
|
|
- # trade_value 代表卖出 accumulated_volume 个 TARGET_ASSET 能得到的 USDT 总量
|
|
|
+ # trade_value 代表买入 accumulated_volume 个 TARGET_ASSET 能得到的 TARGET 总量
|
|
|
return {
|
|
|
- "price_target_per_usdt_bid1": sell_price # 这个名字其实是 RATO/USDT,所以可以叫 price_target_per_base
|
|
|
- }, trade_value # 返回的是实际能卖出 EXCHANGE_OUT_AMOUNT (或更少,如果深度不足) 所得的 USDT 总额
|
|
|
+ "price_target_per_base": buy_price
|
|
|
+ }, trade_value # 返回的是实际能买入 EXCHANGE_OUT_AMOUNT (或更少,如果深度不足) 所得的 USDT 总额
|
|
|
else:
|
|
|
- # logger.warning(f"MEXC现货({pair_symbol}) bids 数据不存在或为空: {data}")
|
|
|
- return {"error": f"MEXC现货({pair_symbol}) bids 数据不存在或为空"}, decimal.Decimal('0')
|
|
|
+ # logger.warning(f"MEXC现货({MEXC_TARGET_PAIR_USDT}) asks 数据不存在或为空: {data}")
|
|
|
+ return {"error": f"MEXC现货({MEXC_TARGET_PAIR_USDT}) asks 数据不存在或为空"}, decimal.Decimal('0')
|
|
|
except requests.exceptions.RequestException as e:
|
|
|
- # logger.error(f"MEXC现货({pair_symbol})请求错误: {e}")
|
|
|
- return {"error": f"MEXC现货({pair_symbol})请求错误: {e}"}, decimal.Decimal('0')
|
|
|
+ # logger.error(f"MEXC现货({MEXC_TARGET_PAIR_USDT})请求错误: {e}")
|
|
|
+ return {"error": f"MEXC现货({MEXC_TARGET_PAIR_USDT})请求错误: {e}"}, decimal.Decimal('0')
|
|
|
except Exception as e:
|
|
|
- # logger.error(f"MEXC现货({pair_symbol})处理错误: {e}", exc_info=True)
|
|
|
- return {"error": f"MEXC现货({pair_symbol})处理错误: {e}"}, decimal.Decimal('0')
|
|
|
+ # logger.error(f"MEXC现货({MEXC_TARGET_PAIR_USDT})处理错误: {e}", exc_info=True)
|
|
|
+ return {"error": f"MEXC现货({MEXC_TARGET_PAIR_USDT})处理错误: {e}"}, decimal.Decimal('0')
|
|
|
|
|
|
latest_values_for_table = {
|
|
|
f"oo_price_usdt_per_target": "N/A",
|
|
|
- f"mexc_price_usdt_per_target_bid1": "N/A", # MEXC Bid1 (converted to USDT/TARGET)
|
|
|
- f"diff_oo_vs_mexc_bid1_percentage": "N/A",
|
|
|
+ f"mexc_price_target_per_base": "N/A", # MEXC price (converted to USDT/TARGET)
|
|
|
+ f"diff_oo_vs_mexc_percentage": "N/A",
|
|
|
"profit_value_for_table": "N/A", # 新增:用于表格的利润值
|
|
|
"oo_error": None, "mexc_error": None,
|
|
|
"last_updated": "N/A",
|
|
|
@@ -151,20 +158,18 @@ latest_values_for_table = {
|
|
|
}
|
|
|
data_lock = threading.Lock()
|
|
|
|
|
|
-def calculate_percentage_diff(price_a_base_per_target, price_b_base_per_target):
|
|
|
- # price_a: MEXC卖价 (USDT/TARGET) - 链上买的目标币,拿到CEX卖掉
|
|
|
- # price_b: 链上买价 (USDT/TARGET) - 链上用USDT买目标币
|
|
|
- # 期望 price_a > price_b
|
|
|
- if price_a_base_per_target is not None and price_b_base_per_target is not None and \
|
|
|
- isinstance(price_a_base_per_target, decimal.Decimal) and \
|
|
|
- isinstance(price_b_base_per_target, decimal.Decimal) and price_b_base_per_target != 0:
|
|
|
+def calculate_percentage_diff(sell_price, buy_price):
|
|
|
+ # 期望 sell_price > buy_price
|
|
|
+ if sell_price is not None and buy_price is not None and \
|
|
|
+ isinstance(sell_price, decimal.Decimal) and \
|
|
|
+ isinstance(buy_price, decimal.Decimal) and buy_price != 0:
|
|
|
# (卖价 - 买价) / 买价
|
|
|
- rst = (price_a_base_per_target - price_b_base_per_target) / price_b_base_per_target
|
|
|
+ rst = (sell_price - buy_price) / buy_price
|
|
|
rst = rst.quantize(decimal.Decimal('1e-6'), rounding=decimal.ROUND_DOWN) # 提高精度
|
|
|
return rst
|
|
|
return None
|
|
|
|
|
|
-def send_arb_msg(profit_amount, chain_swap_data, mexc_price_usdt_per_target, in_amount_to_query_human):
|
|
|
+def send_arb_msg(profit_amount, chain_swap_data, mexc_price_usdt_per_target, human_out_base):
|
|
|
# chain_swap_data 是从 get_chain_price_vs_target_currency 返回的第二个值
|
|
|
if not (chain_swap_data and chain_swap_data.get('data') and chain_swap_data['data']):
|
|
|
logger.error(f"套利消息发送失败:链上交易数据不完整 {chain_swap_data}")
|
|
|
@@ -177,10 +182,10 @@ def send_arb_msg(profit_amount, chain_swap_data, mexc_price_usdt_per_target, in_
|
|
|
to_token_info = router_result['toToken']
|
|
|
|
|
|
in_dec, out_dec = int(from_token_info['decimal']), int(to_token_info['decimal'])
|
|
|
- # human_in_base 根据实际传入的 IN_AMOUNT_TO_QUERY (trade_value) 确定
|
|
|
- # human_out_target 是链上swap的实际输出
|
|
|
+ # human_in_target 根据实际传入的 IN_AMOUNT_TO_QUERY (trade_value) 确定
|
|
|
+ # human_out_base 是链上swap的实际输出
|
|
|
atomic_out_target = decimal.Decimal(router_result['toTokenAmount'])
|
|
|
- human_out_target = atomic_out_target / (10 ** out_dec)
|
|
|
+ human_out_base = atomic_out_target / (10 ** out_dec)
|
|
|
|
|
|
arbitrage_data = {
|
|
|
"tx": tx, # 预签名交易
|
|
|
@@ -189,10 +194,10 @@ def send_arb_msg(profit_amount, chain_swap_data, mexc_price_usdt_per_target, in_
|
|
|
# "mexcPriceUsdtPerTarget": str(mexc_price_usdt_per_target.quantize(decimal.Decimal('1e-8'))),
|
|
|
"symbol": MEXC_TARGET_PAIR_USDT,
|
|
|
"fromToken": IN_TOKEN_ADDRESS,
|
|
|
- "fromTokenAmountHuman": str(in_amount_to_query_human.quantize(decimal.Decimal(f'1e-{in_dec}'))),
|
|
|
+ "fromTokenAmountHuman": str(human_out_base.quantize(decimal.Decimal(f'1e-{in_dec}'))),
|
|
|
"fromTokenDecimal": str(in_dec),
|
|
|
"toToken": OUT_TOKEN_ADDRESS,
|
|
|
- "toTokenAmountHuman": str(human_out_target.quantize(decimal.Decimal(f'1e-{out_dec}'))),
|
|
|
+ "toTokenAmountHuman": str(human_out_base.quantize(decimal.Decimal(f'1e-{out_dec}'))),
|
|
|
"toTokenDecimal": str(out_dec),
|
|
|
"exchangeOutAmount": str(EXCHANGE_OUT_AMOUNT.quantize(decimal.Decimal(f'1e-{out_dec}'))), # CEX上期望卖出的目标币数量
|
|
|
"strategy": STRATEGY,
|
|
|
@@ -223,45 +228,13 @@ def update_data_for_plotly_and_table():
|
|
|
fetch_time_full = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
fetch_time_chart = time.strftime("%H:%M:%S")
|
|
|
|
|
|
- # 1. MEXC: 获取 price_target_per_usdt_bid1 (例如 RATO/USDT) 和相应的 trade_value_usdt
|
|
|
- # trade_value_usdt 是指如果以 EXCHANGE_OUT_AMOUNT 的目标代币在MEXC上砸盘卖出,能获得的USDT估值
|
|
|
- mexc_data, trade_value_usdt = get_mexc_spot_price_target_usdt_bid(MEXC_TARGET_PAIR_USDT)
|
|
|
- mexc_price_target_per_usdt_bid1 = mexc_data.get("price_target_per_usdt_bid1") # TARGET/USDT
|
|
|
- mexc_err = mexc_data.get("error")
|
|
|
-
|
|
|
- # price_target_per_usdt_bid1: 这是1个目标币能卖多少USDT, 即 USDT/TARGET
|
|
|
- # 所以可以直接用,不需要转换,变量名应为 mexc_price_usdt_per_target_bid1
|
|
|
- mexc_price_usdt_per_target_bid1_for_calc = None
|
|
|
- if mexc_price_target_per_usdt_bid1 is not None and mexc_price_target_per_usdt_bid1 > 0:
|
|
|
- mexc_price_usdt_per_target_bid1_for_calc = mexc_price_target_per_usdt_bid1 # RATO/USDT => USDT/TARGET (命名约定)
|
|
|
- elif not mexc_err and mexc_price_target_per_usdt_bid1 is not None:
|
|
|
- mexc_err = mexc_err or "MEXC价格为0或无效"
|
|
|
-
|
|
|
- if mexc_err or trade_value_usdt == decimal.Decimal('0'): # 如果MEXC有问题或无法确定砸盘价值,则跳过本次循环
|
|
|
- logger.warning(f"MEXC数据获取问题: {mexc_err}, trade_value_usdt: {trade_value_usdt}. 跳过本次循环。")
|
|
|
- with data_lock: # 依然更新错误信息
|
|
|
- latest_values_for_table["mexc_error"] = mexc_err
|
|
|
- latest_values_for_table["oo_error"] = latest_values_for_table.get("oo_error") # 保持上次的oo_error
|
|
|
- latest_values_for_table["last_updated"] = fetch_time_full
|
|
|
- time.sleep(REFRESH_INTERVAL_SECONDS)
|
|
|
- continue
|
|
|
-
|
|
|
- # 2. 确定链上查询的输入金额 (USDT)
|
|
|
- # 使用 MEXC 卖出 EXCHANGE_OUT_AMOUNT 个目标币能得到的USDT数量 (trade_value_usdt)
|
|
|
- # 作为链上购买目标币时花费的USDT数量 (in_amount_to_query_human)
|
|
|
- in_amount_to_query_human = trade_value_usdt.quantize(decimal.Decimal('1.00'), rounding=decimal.ROUND_DOWN) # 保留两位小数,向下取整
|
|
|
- if in_amount_to_query_human <= decimal.Decimal('0'):
|
|
|
- logger.warning(f"计算出的链上查询金额为0或负数 ({in_amount_to_query_human} USDT),跳过。trade_value_usdt: {trade_value_usdt}")
|
|
|
- time.sleep(REFRESH_INTERVAL_SECONDS)
|
|
|
- continue
|
|
|
-
|
|
|
- # 3. 获取链上价格:用 in_amount_to_query_human 这么多的USDT去买目标币,能买到多少,以及价格 (USDT/TARGET)
|
|
|
- oo_data, chain_swap_full_response = get_chain_price_vs_target_currency(
|
|
|
+ # 1. 获取链上价格:卖出 IN_AMOUNT_TO_QUERY 这么多的TARGET去卖成USDT,能卖到多少,以及价格 (USDT/TARGET)
|
|
|
+ oo_data, chain_swap_full_response, human_out_base = get_chain_price_vs_target_currency(
|
|
|
CHAIN_ID,
|
|
|
- IN_TOKEN_ADDRESS, # USDT
|
|
|
- OUT_TOKEN_ADDRESS, # TARGET
|
|
|
- in_amount_to_query_human, # 花费的USDT数量
|
|
|
- IN_TOKEN_DECIMALS, # USDT的精度
|
|
|
+ IN_TOKEN_ADDRESS, # TARGET
|
|
|
+ OUT_TOKEN_ADDRESS, # USDT
|
|
|
+ IN_AMOUNT_TO_QUERY, # 链上卖出代币量
|
|
|
+ IN_TOKEN_DECIMALS, # 链上卖出代币的精度
|
|
|
SLIPPAGE,
|
|
|
USER_WALLET,
|
|
|
USER_EXCHANGE_WALLET
|
|
|
@@ -269,64 +242,54 @@ def update_data_for_plotly_and_table():
|
|
|
oo_price_usdt_per_target = oo_data.get("price_base_per_target") # USDT/TARGET
|
|
|
oo_err = oo_data.get("error")
|
|
|
|
|
|
- # 4. 计算百分比差异
|
|
|
- # diff = (MEXC卖价 - 链上买价) / 链上买价
|
|
|
- diff_oo_vs_mexc_bid1_pct = calculate_percentage_diff(
|
|
|
- mexc_price_usdt_per_target_bid1_for_calc, # MEXC卖价 (USDT/TARGET)
|
|
|
- oo_price_usdt_per_target # 链上买价 (USDT/TARGET)
|
|
|
+ # 2. MEXC: 获取 price_target_per_base (例如 RATO/USDT)
|
|
|
+ # trade_value_usdt 是指如果以 EXCHANGE_OUT_AMOUNT 的目标代币在MEXC上砸盘卖出,能获得的USDT估值
|
|
|
+ mexc_data, trade_value_usdt = get_mexc_spot_price_target_usdt_ask(human_out_base)
|
|
|
+ mexc_price_target_per_base = mexc_data.get("price_target_per_base") # TARGET/USDT
|
|
|
+ mexc_err = mexc_data.get("error")
|
|
|
+
|
|
|
+ # 3. 计算百分比差异
|
|
|
+ # diff = (链上卖价 - MEXC买价) / MEXC买价
|
|
|
+ diff_oo_vs_mexc_pct = calculate_percentage_diff(
|
|
|
+ oo_price_usdt_per_target, # 链上卖价 (USDT/TARGET)
|
|
|
+ mexc_price_target_per_base, # MEXC买价 (USDT/TARGET)
|
|
|
)
|
|
|
|
|
|
- # 5. 计算实际利润额 (以USDT计价)
|
|
|
- # 利润 = (MEXC每目标币卖价 - 链上每目标币买价) * 链上买入的目标币数量
|
|
|
- # 链上买入的目标币数量 = in_amount_to_query_human / oo_price_usdt_per_target
|
|
|
+ # 4. 计算实际利润额 (以USDT计价)
|
|
|
# 简化:利润百分比 * 投入的USDT金额
|
|
|
actual_profit_usdt = None
|
|
|
- if diff_oo_vs_mexc_bid1_pct is not None and oo_price_usdt_per_target is not None and oo_price_usdt_per_target > 0:
|
|
|
- # 方案A: 基于百分比和投入金额
|
|
|
- actual_profit_usdt = diff_oo_vs_mexc_bid1_pct * in_amount_to_query_human
|
|
|
- # # 方案B: 基于单价差和数量 (更精确,如果chain_swap_full_response可用)
|
|
|
- # if chain_swap_full_response and chain_swap_full_response.get('data'):
|
|
|
- # router_result = chain_swap_full_response['data'][0]['routerResult']
|
|
|
- # atomic_out_target = decimal.Decimal(router_result['toTokenAmount'])
|
|
|
- # out_dec = int(router_result['toToken']['decimal'])
|
|
|
- # human_out_target_onchain = atomic_out_target / (10 ** out_dec) # 链上实际换到的目标币
|
|
|
-
|
|
|
- # # 能在CEX卖出的目标币数量,取链上换到的数量和CEX预设卖出量的较小值,因为CEX订单深度是按EXCHANGE_OUT_AMOUNT查的
|
|
|
- # effective_target_to_sell_on_mexc = min(human_out_target_onchain, EXCHANGE_OUT_AMOUNT)
|
|
|
-
|
|
|
- # revenue_on_mexc = effective_target_to_sell_on_mexc * mexc_price_usdt_per_target_bid1_for_calc
|
|
|
- # cost_on_chain = in_amount_to_query_human # 这是我们实际在链上花的USDT
|
|
|
- # actual_profit_usdt_v2 = revenue_on_mexc - cost_on_chain
|
|
|
- # actual_profit_usdt = actual_profit_usdt_v2 # 使用更精确的V2版本
|
|
|
-
|
|
|
- # 6. 满足利润条件,发送套利消息, PROFIT_LIMIT + 3的3是提前計算的成本,否則一直提交
|
|
|
+ if diff_oo_vs_mexc_pct is not None and oo_price_usdt_per_target is not None and oo_price_usdt_per_target > 0:
|
|
|
+ # 基于百分比和投入金额
|
|
|
+ actual_profit_usdt = diff_oo_vs_mexc_pct * human_out_base
|
|
|
+
|
|
|
+ # 5. 满足利润条件,发送套利消息, PROFIT_LIMIT + 3的3是提前計算的成本,否則一直提交
|
|
|
global mode
|
|
|
if actual_profit_usdt is not None and actual_profit_usdt > PROFIT_LIMIT + 3 and mode == 'trade':
|
|
|
if chain_swap_full_response: # 确保有完整的链上数据
|
|
|
- send_arb_msg(actual_profit_usdt, chain_swap_full_response, mexc_price_usdt_per_target_bid1_for_calc, in_amount_to_query_human)
|
|
|
+ send_arb_msg(actual_profit_usdt, chain_swap_full_response, mexc_price_target_per_base, human_out_base)
|
|
|
else:
|
|
|
logger.warning("利润满足但链上数据不完整,无法发送套利消息。")
|
|
|
|
|
|
current_point = {
|
|
|
"time": fetch_time_chart,
|
|
|
"oo_price_usdt_per_target": float(oo_price_usdt_per_target) if oo_price_usdt_per_target else None,
|
|
|
- "mexc_price_usdt_per_target_bid1": float(mexc_price_usdt_per_target_bid1_for_calc) if mexc_price_usdt_per_target_bid1_for_calc else None,
|
|
|
- "diff_oo_vs_mexc_bid1": float(diff_oo_vs_mexc_bid1_pct) if diff_oo_vs_mexc_bid1_pct is not None else None,
|
|
|
+ "mexc_price_target_per_base": float(mexc_price_target_per_base) if mexc_price_target_per_base else None,
|
|
|
+ "diff_oo_vs_mexc": float(diff_oo_vs_mexc_pct) if diff_oo_vs_mexc_pct is not None else None,
|
|
|
"profit_value": float(actual_profit_usdt) if actual_profit_usdt is not None else None, # 新增:用于图表的实际利润额
|
|
|
}
|
|
|
|
|
|
with data_lock:
|
|
|
historical_data_points.append(current_point)
|
|
|
latest_values_for_table["oo_price_usdt_per_target"] = f"{oo_price_usdt_per_target:.8f}" if oo_price_usdt_per_target else "N/A"
|
|
|
- latest_values_for_table["mexc_price_usdt_per_target_bid1"] = f"{mexc_price_usdt_per_target_bid1_for_calc:.8f}" if mexc_price_usdt_per_target_bid1_for_calc else "N/A"
|
|
|
- latest_values_for_table["diff_oo_vs_mexc_bid1_percentage"] = f"{diff_oo_vs_mexc_bid1_pct:+.4%}" if diff_oo_vs_mexc_bid1_pct is not None else "N/A" # 显示为百分比
|
|
|
+ latest_values_for_table["mexc_price_target_per_base"] = f"{mexc_price_target_per_base:.8f}" if mexc_price_target_per_base else "N/A"
|
|
|
+ latest_values_for_table["diff_oo_vs_mexc_percentage"] = f"{diff_oo_vs_mexc_pct:+.4%}" if diff_oo_vs_mexc_pct is not None else "N/A" # 显示为百分比
|
|
|
latest_values_for_table["profit_value_for_table"] = f"{actual_profit_usdt:.2f} {BASE_CURRENCY_SYMBOL}" if actual_profit_usdt is not None else "N/A" # 新增
|
|
|
latest_values_for_table["oo_error"] = oo_err
|
|
|
latest_values_for_table["mexc_error"] = mexc_err
|
|
|
latest_values_for_table["last_updated"] = fetch_time_full
|
|
|
- latest_values_for_table["in_amount_for_query_display"] = f"{in_amount_to_query_human:.2f} {BASE_CURRENCY_SYMBOL}" if in_amount_to_query_human > 0 else "N/A"
|
|
|
+ latest_values_for_table["in_amount_for_query_display"] = f"{human_out_base:.2f} {BASE_CURRENCY_SYMBOL}" if human_out_base > 0 else "N/A"
|
|
|
|
|
|
- # logger.info(f"{fetch_time_chart} Price Query: Chain Input {in_amount_to_query_human:.2f} {BASE_CURRENCY_SYMBOL} | OKX Price: {oo_price_usdt_per_target_display} | MEXC Price: {mexc_price_usdt_per_target_bid1_display} | Diff: {diff_display} | Profit: {profit_display}")
|
|
|
+ # logger.info(f"{fetch_time_chart} Price Query: Chain Input {human_out_base:.2f} {BASE_CURRENCY_SYMBOL} | OKX Price: {oo_price_usdt_per_target_display} | MEXC Price: {mexc_price_target_per_base_display} | Diff: {diff_display} | Profit: {profit_display}")
|
|
|
if oo_err or mexc_err :
|
|
|
logger.warning(f"{fetch_time_chart} Errors: OO:{oo_err}, MEXC:{mexc_err}")
|
|
|
|
|
|
@@ -375,14 +338,14 @@ def get_plotly_chart_data():
|
|
|
# Price Chart
|
|
|
fig_prices = go.Figure()
|
|
|
fig_prices.add_trace(go.Scatter(x=times, y=[p['oo_price_usdt_per_target'] for p in points], mode='lines',
|
|
|
- name=f'Okx ({display_base_asset}/{display_target_asset})',
|
|
|
+ name=f'链上({display_base_asset}/{display_target_asset})',
|
|
|
line=dict(color='rgb(75, 192, 192)'),
|
|
|
- hovertemplate=f'<b>Okx链上价</b><br>价格: %{{y:.8f}} {display_base_asset}<extra></extra>',
|
|
|
+ hovertemplate=f'<b>链上价</b><br>价格: %{{y:.8f}} {display_base_asset}<extra></extra>',
|
|
|
connectgaps=True)) # 处理None值不画线
|
|
|
- fig_prices.add_trace(go.Scatter(x=times, y=[p['mexc_price_usdt_per_target_bid1'] for p in points], mode='lines',
|
|
|
- name=f'MEXC卖1价 ({display_base_asset}/{display_target_asset})',
|
|
|
+ fig_prices.add_trace(go.Scatter(x=times, y=[p['mexc_price_target_per_base'] for p in points], mode='lines',
|
|
|
+ name=f'MEXC价 ({display_base_asset}/{display_target_asset})',
|
|
|
line=dict(color='rgb(255, 99, 132)', dash='dash'),
|
|
|
- hovertemplate=f'<b>MEXC卖出价</b><br>价格: %{{y:.8f}} {display_base_asset}<extra></extra>',
|
|
|
+ hovertemplate=f'<b>MEXC价</b><br>价格: %{{y:.8f}} {display_base_asset}<extra></extra>',
|
|
|
connectgaps=True))
|
|
|
fig_prices.update_layout(title_text=f'{display_base_asset}/{display_target_asset} 价格历史',
|
|
|
xaxis=common_xaxis_config.copy(),
|
|
|
@@ -394,9 +357,9 @@ def get_plotly_chart_data():
|
|
|
# Percentage Difference Chart
|
|
|
fig_diffs = go.Figure()
|
|
|
fig_diffs.add_trace(
|
|
|
- go.Scatter(x=times, y=[p['diff_oo_vs_mexc_bid1'] for p in points], mode='lines', name=f'价差百分比 (MEXC卖价 vs Okx买价)',
|
|
|
+ go.Scatter(x=times, y=[p['diff_oo_vs_mexc'] for p in points], mode='lines', name=f'价差百分比 (MEXC卖价 vs 链上买价)',
|
|
|
line=dict(color='rgb(255, 159, 64)'),
|
|
|
- hovertemplate=f'<b>(MEXC卖价-Okx买价)/Okx买价</b><br>百分比: %{{y:+.4%}}<extra></extra>', # 显示为百分比
|
|
|
+ hovertemplate=f'<b>(MEXC卖价-链上买价)/链上买价</b><br>百分比: %{{y:+.4%}}<extra></extra>', # 显示为百分比
|
|
|
connectgaps=True))
|
|
|
fig_diffs.update_layout(title_text=f'价差百分比历史曲线',
|
|
|
xaxis=common_xaxis_config.copy(),
|
|
|
@@ -434,7 +397,7 @@ if __name__ == "__main__":
|
|
|
choices=['trade', 'view'], # 限制可选值
|
|
|
help='运行模式: "trade" (执行交易) 或 "view" (仅观察)')
|
|
|
|
|
|
- except_strategy = 'erc20_to_mexc'
|
|
|
+ except_strategy = 'mexc_to_erc20'
|
|
|
if STRATEGY != except_strategy:
|
|
|
raise Exception(f"策略不匹配! 期待{except_strategy}, 实际{STRATEGY}")
|
|
|
|