|
|
@@ -1,35 +1,61 @@
|
|
|
import time
|
|
|
import logging
|
|
|
+from web3_py_client import EthClient
|
|
|
+import mexc_client as mexc
|
|
|
+
|
|
|
+web3 = EthClient()
|
|
|
|
|
|
# 配置日志
|
|
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
|
|
+
|
|
|
+def truncate_float_string(number, digits):
|
|
|
+ """
|
|
|
+ 硬截取浮点数,保留指定小数位数 (使用字符串格式化辅助)
|
|
|
+ """
|
|
|
+ stepper = 10.0 ** digits
|
|
|
+ # 将数乘以 stepper,转换为整数进行截断,然后除回 stepper
|
|
|
+ truncated_number = int(number * stepper) / stepper
|
|
|
+ return truncated_number
|
|
|
+
|
|
|
+
|
|
|
class ArbitrageProcess:
|
|
|
- def __init__(self, blockchain_client, exchange_client, token_address, quote_token_address, min_arbitrage_amount, slippage_tolerance):
|
|
|
+ def __init__(self, tx, gas_limit_multiplier, gas_price_multiplier, from_token, to_token, from_token_amount_human):
|
|
|
"""
|
|
|
- 初始化套利系统
|
|
|
+ 初始化套利流程
|
|
|
|
|
|
Args:
|
|
|
- blockchain_client: 链上交互客户端(伪代码)
|
|
|
- exchange_client: 中心化交易所交互客户端(伪代码)
|
|
|
- token_address: 要套利的资产地址 (链上)
|
|
|
- quote_token_address: 计价资产地址 (链上)
|
|
|
- min_arbitrage_amount: 最小套利交易金额
|
|
|
- slippage_tolerance: 链上交易滑点容忍度
|
|
|
+ tx: 在链上要发送交易的tx
|
|
|
+ gas_limit_multiplier: gas limit倍数, 一般都不加倍
|
|
|
+ gas_price_multiplier: gas price倍数, 可以提高交易成功率
|
|
|
"""
|
|
|
- self.blockchain_client = blockchain_client
|
|
|
- self.exchange_client = exchange_client
|
|
|
- self.token_address = token_address
|
|
|
- self.quote_token_address = quote_token_address
|
|
|
- self.min_arbitrage_amount = min_arbitrage_amount
|
|
|
- self.slippage_tolerance = slippage_tolerance
|
|
|
- self.current_state = "IDLE" # 系统当前状态
|
|
|
- self.last_transaction_id = None # 记录最近一次链上交易哈希或交易所订单ID 便于状态确认
|
|
|
- self.arbitrage_details = {} # 存储当前套利交易的细节信息,例如买入数量、套保价格等
|
|
|
+ self.tx = tx
|
|
|
+ self.tx.pop('maxPriorityFeePerGas', None)
|
|
|
+ self.tx.pop('value', None)
|
|
|
+ self.tx.pop('minReceiveAmount', None)
|
|
|
+ self.tx.pop('slippage', None)
|
|
|
+ self.tx.pop('maxSpendAmount', None)
|
|
|
+ self.tx.pop('signatureData', None)
|
|
|
+
|
|
|
+ self.gas_limit_multiplier = gas_limit_multiplier
|
|
|
+ self.gas_price_multiplier = gas_price_multiplier
|
|
|
+ self.from_token_addr = from_token
|
|
|
+ self.to_token_addr = to_token
|
|
|
+
|
|
|
+ # 存储当前套利交易的细节信息,例如买入数量、价格等
|
|
|
+ self.arbitrage_details = {
|
|
|
+ "chain_buy_tx_hash": None, # 链上买入的tx hash
|
|
|
+ "chain_usdt_use": from_token_amount_human, # 链上usdt减少量(使用量), todo, 暂用固定值代替
|
|
|
+ "chain_buy_amount": None, # 链上币增加量(购入量), todo, 暂用即时余额代替
|
|
|
+ "chain_buy_price": None, # 链上购入价, todo
|
|
|
+ "chain_withdrawal_tx_hash": None, # 链上转入交易所的id
|
|
|
+ "exchange_sell_order_id": None, # 交易所卖出id
|
|
|
+ "exchange_withdraw_id": None, # 交易所提现id
|
|
|
+ "exchange_withdraw_amount": None, # 交易所提现数量
|
|
|
+ }
|
|
|
|
|
|
# 定义可能的状态
|
|
|
self.STATES = [
|
|
|
- "IDLE", # 空闲状态,等待价差监听触发
|
|
|
"BUYING_ON_CHAIN", # 正在链上买入
|
|
|
"WAITING_CHAIN_CONFIRM", # 等待链上交易确认
|
|
|
# "HEDGING_ON_EXCHANGE", # 正在中心化交易所套保
|
|
|
@@ -46,43 +72,19 @@ class ArbitrageProcess:
|
|
|
"FAILED" # 套利流程失败
|
|
|
]
|
|
|
|
|
|
- def trigger_arbitrage(self, arbitrage_amount, hedge_price, estimated_chain_price):
|
|
|
- """
|
|
|
- 触发套利流程
|
|
|
-
|
|
|
- Args:
|
|
|
- arbitrage_amount: 套利交易数量 (基于要搬砖的资产)
|
|
|
- hedge_price: 中心化交易所的套保价格 (预计卖出价格)
|
|
|
- estimated_chain_price: 链上预计买入价格
|
|
|
- """
|
|
|
- if self.current_state != "IDLE":
|
|
|
- logging.warning(f"系统当前状态不是IDLE ({self.current_state}),无法触发新的套利流程。")
|
|
|
- return False
|
|
|
+ self.STATE_IDLE = "IDLE"
|
|
|
+ self.STATE_BUYING_ON_CHAIN = "BUYING_ON_CHAIN"
|
|
|
+ self.STATE_WAITING_CHAIN_CONFIRM = "WAITING_CHAIN_CONFIRM"
|
|
|
+ self.STATE_TRANSFERRING_TO_EXCHANGE = "TRANSFERRING_TO_EXCHANGE"
|
|
|
+ self.STATE_WAITING_TRANSFER_ARRIVE = "WAITING_TRANSFER_ARRIVE"
|
|
|
+ self.STATE_SELLING_ON_EXCHANGE = "SELLING_ON_EXCHANGE"
|
|
|
+ self.STATE_WAITING_SELL_CONFIRM = "WAITING_SELL_CONFIRM"
|
|
|
+ self.STATE_TRANSFERRING_TO_CHAIN = "TRANSFERRING_TO_CHAIN"
|
|
|
+ self.STATE_WAITING_WITHDRAWAL_CONFIRM = "WAITING_WITHDRAWAL_CONFIRM"
|
|
|
+ self.STATE_COMPLETED = "COMPLETED"
|
|
|
+ self.STATE_FAILED = "FAILED"
|
|
|
|
|
|
- if arbitrage_amount < self.min_arbitrage_amount:
|
|
|
- logging.warning(f"套利数量 ({arbitrage_amount}) 小于最小套利数量 ({self.min_arbitrage_amount}),取消触发。")
|
|
|
- return False
|
|
|
-
|
|
|
- # 简要计算预计利润,确保有利可图 (实际中需要更精确的计算,考虑滑点、手续费等)
|
|
|
- estimated_profit = arbitrage_amount * (hedge_price - estimated_chain_price)
|
|
|
- if estimated_profit <= 0:
|
|
|
- logging.warning(f"预计利润非正 ({estimated_profit}),取消触发。")
|
|
|
- return False
|
|
|
-
|
|
|
- logging.info(f"触发套利流程,数量:{arbitrage_amount},预计套保价格:{hedge_price},预计链上买入价格:{estimated_chain_price}")
|
|
|
- self.arbitrage_details = {
|
|
|
- "amount": arbitrage_amount,
|
|
|
- "hedge_price": hedge_price,
|
|
|
- "estimated_chain_price": estimated_chain_price,
|
|
|
- "chain_buy_tx_hash": None,
|
|
|
- "exchange_hedge_order_id": None,
|
|
|
- "exchange_transfer_id": None, # 交易所内部转账ID 或 链上充值交易哈希
|
|
|
- "exchange_sell_order_id": None,
|
|
|
- "exchange_close_hedge_order_id": None,
|
|
|
- "chain_withdrawal_tx_hash": None
|
|
|
- }
|
|
|
- self._set_state("BUYING_ON_CHAIN")
|
|
|
- return True
|
|
|
+ self.current_state = "IDLE"
|
|
|
|
|
|
def _set_state(self, state):
|
|
|
"""
|
|
|
@@ -99,11 +101,7 @@ class ArbitrageProcess:
|
|
|
根据当前状态执行套利流程的下一步
|
|
|
这是一个周期性调用的函数,例如在主循环中调用
|
|
|
"""
|
|
|
- if self.current_state == "IDLE":
|
|
|
- # 在IDLE状态下不执行任何操作,等待 trigger_arbitrage 被调用
|
|
|
- pass
|
|
|
-
|
|
|
- elif self.current_state == "BUYING_ON_CHAIN":
|
|
|
+ if self.current_state == "BUYING_ON_CHAIN":
|
|
|
self._execute_buy_on_chain()
|
|
|
|
|
|
elif self.current_state == "WAITING_CHAIN_CONFIRM":
|
|
|
@@ -141,12 +139,9 @@ class ArbitrageProcess:
|
|
|
|
|
|
elif self.current_state == "COMPLETED":
|
|
|
logging.info("套利流程成功完成!")
|
|
|
- self._reset_system() # 成功后重置系统
|
|
|
|
|
|
elif self.current_state == "FAILED":
|
|
|
logging.error("套利流程失败!")
|
|
|
- # 在这里处理失败逻辑,例如发送警报,记录错误详情等
|
|
|
- self._reset_system() # 失败后也重置系统,准备下一次尝试或者手动介入
|
|
|
|
|
|
# 以下是每个状态对应的具体执行函数(伪代码)
|
|
|
|
|
|
@@ -156,20 +151,16 @@ class ArbitrageProcess:
|
|
|
"""
|
|
|
logging.info("执行:链上买入操作...")
|
|
|
try:
|
|
|
- # 伪代码:调用链上客户端执行买入交易
|
|
|
- # chain_buy_tx_hash = self.blockchain_client.swap(
|
|
|
- # self.quote_token_address, # 用计价资产买入
|
|
|
- # self.token_address,
|
|
|
- # self.arbitrage_details["amount"] * self.arbitrage_details["estimated_chain_price"], # 需要花费的计价资产数量
|
|
|
- # self.arbitrage_details["amount"], # 期望获得的要套利的资产数量
|
|
|
- # self.slippage_tolerance
|
|
|
- # )
|
|
|
- #
|
|
|
- # 模拟成功
|
|
|
- chain_buy_tx_hash = f"tx_chain_buy_{int(time.time())}"
|
|
|
+ # 调用链上客户端执行买入交易
|
|
|
+ chain_buy_tx_hash = web3._sign_and_send_transaction(
|
|
|
+ self.tx,
|
|
|
+ self.gas_limit_multiplier,
|
|
|
+ self.gas_price_multiplier
|
|
|
+ )
|
|
|
+
|
|
|
+ # 交易成功
|
|
|
logging.info(f"链上买入交易已发送,交易哈希:{chain_buy_tx_hash}")
|
|
|
self.arbitrage_details["chain_buy_tx_hash"] = chain_buy_tx_hash
|
|
|
- self.last_transaction_id = chain_buy_tx_hash
|
|
|
self._set_state("WAITING_CHAIN_CONFIRM")
|
|
|
|
|
|
except Exception as e:
|
|
|
@@ -180,32 +171,33 @@ class ArbitrageProcess:
|
|
|
"""
|
|
|
等待链上交易确认
|
|
|
"""
|
|
|
- logging.info(f"等待链上交易确认:{self.last_transaction_id}")
|
|
|
+
|
|
|
+ hash = self.arbitrage_details["chain_buy_tx_hash"]
|
|
|
+ logging.info(f"等待链上交易确认:{hash}")
|
|
|
try:
|
|
|
# 伪代码:查询链上交易确认状态
|
|
|
- # is_confirmed = self.blockchain_client.is_transaction_confirmed(self.last_transaction_id)
|
|
|
- #
|
|
|
- # 模拟确认成功或失败
|
|
|
- # 实际应用中,需要设置超时机制和多次查询
|
|
|
- if time.time() % 5 < 1: # 模拟每5秒尝试确认一次
|
|
|
- is_confirmed = True # 模拟成功确认
|
|
|
- # is_confirmed = False # 模拟未确认
|
|
|
- # is_confirmed = "FAILED" # 模拟交易失败
|
|
|
- else:
|
|
|
- is_confirmed = False # 继续等待
|
|
|
+ receipt = web3.wait_for_transaction_receipt(hash, timeout=300)
|
|
|
|
|
|
- if is_confirmed is True:
|
|
|
+ if receipt.status == 1:
|
|
|
logging.info("链上交易已确认。")
|
|
|
- # TODO: 在这里可以根据实际链上交易结果更新实际买入数量,用于后续流程
|
|
|
- # actual_buy_amount = self.blockchain_client.get_transaction_result(self.last_transaction_id).amount_out
|
|
|
- # self.arbitrage_details["actual_buy_amount"] = actual_buy_amount # 存储实际买入数量
|
|
|
+ # 在这里根据实际链上交易结果更新实际买入数量,用于后续流程
|
|
|
+ actual_buy_amount = web3.get_erc20_balance(self.to_token_addr)
|
|
|
+ self.arbitrage_details["chain_buy_amount"] = actual_buy_amount # 存储实际买入数量
|
|
|
+
|
|
|
+ buy_token_decimal = web3.get_erc20_decimals(self.to_token_addr)
|
|
|
+
|
|
|
+ buy_amount_human = actual_buy_amount / (10 ** buy_token_decimal)
|
|
|
+ buy_amount_human = truncate_float_string(buy_amount_human, 2)
|
|
|
+ sell_amount_human = self.arbitrage_details["chain_usdt_use"]
|
|
|
+
|
|
|
+ price_human = sell_amount_human / buy_amount_human
|
|
|
+ price_human = truncate_float_string(price_human, 10)
|
|
|
+
|
|
|
+ logging.info(f"用{sell_amount_human}买入{buy_amount_human},价格{price_human}。")
|
|
|
self._set_state("TRANSFERRING_TO_EXCHANGE")
|
|
|
- elif is_confirmed == "FAILED":
|
|
|
- logging.error(f"链上交易确认失败:{self.last_transaction_id}")
|
|
|
- self._set_state("FAILED")
|
|
|
else:
|
|
|
- # 继续等待确认
|
|
|
- pass
|
|
|
+ logging.error(f"链上交易确认失败:{hash}")
|
|
|
+ self._set_state("FAILED")
|
|
|
|
|
|
except Exception as e:
|
|
|
logging.error(f"查询链上确认状态时发生错误:{e}")
|
|
|
@@ -528,89 +520,42 @@ class ArbitrageProcess:
|
|
|
logging.error(f"查询交易所提现状态时发生错误:{e}")
|
|
|
self._set_state("FAILED")
|
|
|
|
|
|
- def _reset_system(self):
|
|
|
- """
|
|
|
- 重置系统状态和套利详情
|
|
|
- """
|
|
|
- logging.info("重置系统状态...")
|
|
|
- self.current_state = "IDLE"
|
|
|
- self.last_transaction_id = None
|
|
|
- self.arbitrage_details = {}
|
|
|
- # TODO: 根据实际需求,这里可能需要记录失败的套利详情以便事后排查或补偿
|
|
|
- # 例如:self.failed_arbitrages.append(self.arbitrage_details)
|
|
|
-
|
|
|
# 伪代码示例:如何使用这个类
|
|
|
if __name__ == "__main__":
|
|
|
- # 模拟链上客户端和交易所客户端
|
|
|
- class MockBlockchainClient:
|
|
|
- def swap(self, token_in, token_out, amount_in, amount_out_min, slippage):
|
|
|
- print(f"Mock Chain: Swapping {amount_in} of {token_in} for at least {amount_out_min} of {token_out}")
|
|
|
- return "mock_chain_tx_hash_12345" # 返回模拟交易哈希
|
|
|
-
|
|
|
- def is_transaction_confirmed(self, tx_hash):
|
|
|
- # 在实际应用中会查询链上节点
|
|
|
- # 这里简单模拟一个延迟确认
|
|
|
- print(f"Mock Chain: Checking confirmation for {tx_hash}...")
|
|
|
- # 实际中需要更复杂的逻辑,判断区块高度等
|
|
|
- return True # 总是返回 True 模拟确认
|
|
|
-
|
|
|
- def transfer(self, token, recipient, amount):
|
|
|
- print(f"Mock Chain: Transferring {amount} of {token} to {recipient}")
|
|
|
- return "mock_chain_tx_transfer_56789"
|
|
|
-
|
|
|
- class MockExchangeClient:
|
|
|
- def place_futures_sell_order(self, symbol, amount, price, order_type):
|
|
|
- print(f"Mock Exchange: Placing futures SELL {order_type} order for {amount} of {symbol} at price {price}")
|
|
|
- return "mock_exchange_hedge_order_id_abcde"
|
|
|
-
|
|
|
- def get_order_status(self, order_id):
|
|
|
- # 在实际应用中会查询交易所API
|
|
|
- # 这里简单模拟一个延迟成交
|
|
|
- print(f"Mock Exchange: Checking order status for {order_id}...")
|
|
|
- # 实际中需要查询交易所 API 获取 FILLed, PARTIALLY_FILLED, CANCELED 等状态
|
|
|
- return "FILLED" # 总是返回 FILLED 模拟成交
|
|
|
-
|
|
|
- def get_deposit_address(self, token):
|
|
|
- print(f"Mock Exchange: Getting deposit address for {token}")
|
|
|
- return "mock_exchange_deposit_address_xyz"
|
|
|
-
|
|
|
- def is_deposit_arrived_by_tx(self, tx_hash):
|
|
|
- print(f"Mock Exchange: Checking deposit arrival for chain tx {tx_hash}...")
|
|
|
- return True # 总是返回 True 模拟到账
|
|
|
-
|
|
|
- def place_spot_sell_order(self, symbol, amount, order_type):
|
|
|
- print(f"Mock Exchange: Placing spot SELL {order_type} order for {amount} of {symbol}")
|
|
|
- return "mock_exchange_spot_sell_order_fghij"
|
|
|
-
|
|
|
- def close_futures_position(self, symbol, amount, order_type):
|
|
|
- print(f"Mock Exchange: Closing futures position for {amount} of {symbol} with {order_type} order")
|
|
|
- return "mock_exchange_close_hedge_order_lmnop"
|
|
|
-
|
|
|
- def withdraw(self, token, amount, address):
|
|
|
- print(f"Mock Exchange: Withdrawing {amount} of {token} to {address}")
|
|
|
- return "mock_exchange_withdrawal_qrstu"
|
|
|
-
|
|
|
- def get_withdrawal_status(self, withdrawal_id):
|
|
|
- print(f"Mock Exchange: Checking withdrawal status for {withdrawal_id}")
|
|
|
- return "COMPLETED" # 总是返回 COMPLETED 模拟提现完成
|
|
|
-
|
|
|
- blockchain_client = MockBlockchainClient()
|
|
|
- exchange_client = MockExchangeClient()
|
|
|
- arbitrage_system = ArbitrageProcess(
|
|
|
- blockchain_client=blockchain_client,
|
|
|
- exchange_client=exchange_client,
|
|
|
- token_address="0x...TokenA", # 示例代币地址
|
|
|
- quote_token_address="0x...USDT", # 示例计价代币地址
|
|
|
- min_arbitrage_amount=100, # 最小套利数量
|
|
|
- slippage_tolerance=0.01 # 滑点容忍度 1%
|
|
|
- )
|
|
|
-
|
|
|
- # 模拟价差监听器触发套利
|
|
|
- arbitrage_system.trigger_arbitrage(
|
|
|
- arbitrage_amount=1000,
|
|
|
- hedge_price=1.05, # 例如在交易所按 1.05 卖出
|
|
|
- estimated_chain_price=1.0 # 预计在链上按 1.0 买入
|
|
|
- )
|
|
|
+ import ok_chain_client
|
|
|
+ import decimal
|
|
|
+ import pprint
|
|
|
+
|
|
|
+ CHAIN_ID = 1
|
|
|
+ FROM_TOKEN = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
|
|
|
+ FROM_TOKEN_AMOUNT_HUMAM = decimal.Decimal('1')
|
|
|
+ FROM_TOKEN_DECIMAL = 6
|
|
|
+ TO_TOKEN = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860'
|
|
|
+ USER_WALLET = '0xb1f33026Db86a86372493a3B124d7123e9045Bb4'
|
|
|
+
|
|
|
+ # 询价
|
|
|
+ data = ok_chain_client.swap(CHAIN_ID,
|
|
|
+ FROM_TOKEN_AMOUNT_HUMAM * (10 ** FROM_TOKEN_DECIMAL),
|
|
|
+ FROM_TOKEN,
|
|
|
+ TO_TOKEN,
|
|
|
+ 1,
|
|
|
+ USER_WALLET
|
|
|
+ )
|
|
|
+ if data.get('code') != '0' or not data.get('data'):
|
|
|
+ pprint.pprint(data)
|
|
|
+ pprint.pprint({
|
|
|
+ "error": f"OK API错误({1}) - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"})
|
|
|
+
|
|
|
+ raise Exception("")
|
|
|
+ d = data['data'][0]
|
|
|
+ tx = d['tx']
|
|
|
+ pprint.pprint(tx)
|
|
|
+
|
|
|
+ # 套利流程模拟
|
|
|
+ arbitrage_system = ArbitrageProcess(tx, 1, 1.2, FROM_TOKEN, TO_TOKEN, FROM_TOKEN_AMOUNT_HUMAM)
|
|
|
+
|
|
|
+ # 一般都是从这个流程开始,测试时可以稍作修改、测试后续流程
|
|
|
+ arbitrage_system._set_state(arbitrage_system.STATE_BUYING_ON_CHAIN)
|
|
|
|
|
|
# 在主循环中周期性调用 run_arbitrage_step
|
|
|
while arbitrage_system.current_state != "COMPLETED" and arbitrage_system.current_state != "FAILED":
|