|
|
@@ -21,13 +21,12 @@ from flask import Flask, request, jsonify
|
|
|
from flask_cors import CORS # 导入
|
|
|
from as_utils import get_formatted_timestamp
|
|
|
from as_utils import add_state_flow_entry
|
|
|
-from config import wallet
|
|
|
-from config import withdraw
|
|
|
from binance.client import Client # 用于获取ETH价格
|
|
|
from checker import ok_chain_client
|
|
|
from mexc_client import MexcClient
|
|
|
from pprint import pprint
|
|
|
from pprint import pformat
|
|
|
+from config import rpc_url
|
|
|
|
|
|
ok_chain_client.api_config = {
|
|
|
"api_key": 'a05643ab-fb17-402b-94a8-a886bd343301', # 请替换为您的真实 API Key
|
|
|
@@ -39,49 +38,30 @@ ok_chain_client.api_config = {
|
|
|
log = logging.getLogger('werkzeug')
|
|
|
log.setLevel(logging.ERROR)
|
|
|
|
|
|
-web3 = web3_py_client.EthClient()
|
|
|
+web3 = web3_py_client.EthClient(rpc_url)
|
|
|
w3 = web3.w3
|
|
|
mexc = MexcClient()
|
|
|
|
|
|
-USER_WALLET = wallet["user_wallet"]
|
|
|
-USER_EXCHANGE_WALLET = wallet["user_exchange_wallet"]
|
|
|
-
|
|
|
# 该代币最后一次执行套利的区块信息 (如果需要防止过于频繁的同类套利,不然变成砸盘、拉盘的了)
|
|
|
last_process_info = {} # 示例: {"RATO_USDT": 0}
|
|
|
MIN_BLOCKS_BETWEEN_ARB = Decimal(5) # 在重试相同交易对之前等待几个区块
|
|
|
|
|
|
# --- 全局状态和锁 ---
|
|
|
-processing_list = [] # 正在处理的任务列表
|
|
|
-history_process_list = [] # 已完成的任务历史列表
|
|
|
-list_lock = threading.Lock() # 用于修改 processing_list 和 history_process_list 结构的锁
|
|
|
+processing_list = [] # 正在处理的任务列表
|
|
|
+history_process_list = [] # 已完成的任务历史列表
|
|
|
+list_lock = threading.Lock() # 用于修改 processing_list 和 history_process_list 结构的锁
|
|
|
|
|
|
# --- 一些核心數據和鎖 ---
|
|
|
core_data = {
|
|
|
- "nonce": 0, # 全局 Nonce
|
|
|
- "eth_balance": Decimal(0), # 全局 eth餘額
|
|
|
"eth_price": 0, # 全局 eth價格
|
|
|
"block_number": 0, # 全局 區塊號
|
|
|
"block": None, # 全局 最后一個區塊的信息
|
|
|
}
|
|
|
core_lock = threading.Lock() # 核心數據的锁
|
|
|
|
|
|
-# --- pending數據和鎖 ---
|
|
|
-pending_data = {
|
|
|
- # # 數據結構的demo
|
|
|
- # "0xaf181bbbf5bf56d9204bd18cd25abd90e51890e848525e7788b410689c0c26a4": {
|
|
|
- # "block_number": 22570370, # 提交pending時的區塊,隔幾個區塊去獲取數據會更準確
|
|
|
- # "tx_details": None, # okapi解析的數據, None就是還沒有獲取到
|
|
|
- # "reponse": None, # okapi最後一次獲取到的數據
|
|
|
- # },
|
|
|
-}
|
|
|
-pending_lock = threading.Lock()
|
|
|
-PENDING_CONFIRM_BLOCK = 3 # 需要幾個區塊才進行確認
|
|
|
-
|
|
|
# --- mexc相關數據和鎖 ---
|
|
|
mexc_data = {
|
|
|
"account_info": {},
|
|
|
- "deposit_list": [],
|
|
|
- "withdraw_list": [],
|
|
|
"coin_info_map": {}, # 處理過的幣種信息,coin_info_map[coin][network]
|
|
|
}
|
|
|
mexc_lock = threading.Lock()
|
|
|
@@ -155,18 +135,8 @@ def strategy_builder(process_item):
|
|
|
profitLimit = Decimal(process_item['profitLimit'])
|
|
|
strategy = process_item['strategy']
|
|
|
|
|
|
- # 對於高利潤交易,進行適當加速
|
|
|
- gas_limit_multiplier = 1
|
|
|
- gas_price_multiplier = 1
|
|
|
- # if profit > Decimal(5) * profitLimit:
|
|
|
- # gas_price_multiplier = 5
|
|
|
- # elif profit > Decimal(10) * profitLimit:
|
|
|
- # gas_price_multiplier = 10
|
|
|
-
|
|
|
global core_data
|
|
|
global core_lock
|
|
|
- global pending_data
|
|
|
- global pending_lock
|
|
|
global mexc_data
|
|
|
global mexc_lock
|
|
|
|
|
|
@@ -174,15 +144,13 @@ def strategy_builder(process_item):
|
|
|
logger.info(f'策略原始参数:\n{process_item_formated}')
|
|
|
|
|
|
if strategy == 'erc20_to_mexc':
|
|
|
- return s_erc20_to_mexc.ArbitrageProcess(gas_limit_multiplier, gas_price_multiplier, process_item,
|
|
|
+ return s_erc20_to_mexc.ArbitrageProcess(process_item,
|
|
|
core_data, core_lock,
|
|
|
- pending_data, pending_lock,
|
|
|
mexc_data, mexc_lock
|
|
|
)
|
|
|
elif strategy == 'mexc_to_erc20':
|
|
|
- return s_mexc_to_erc20.ArbitrageProcess(gas_limit_multiplier, gas_price_multiplier, process_item,
|
|
|
+ return s_mexc_to_erc20.ArbitrageProcess(process_item,
|
|
|
core_data, core_lock,
|
|
|
- pending_data, pending_lock,
|
|
|
mexc_data, mexc_lock
|
|
|
)
|
|
|
else:
|
|
|
@@ -216,11 +184,6 @@ def update_core_data_periodically():
|
|
|
"""
|
|
|
global core_data # 明确表示我们要修改全局的 core_data
|
|
|
|
|
|
- if not USER_WALLET or USER_WALLET == "你的钱包地址":
|
|
|
- logger.error("USER_WALLET 未正确配置。nonce 更新将无法进行。")
|
|
|
- # 如果 USER_WALLET 未配置,可以考虑让线程不执行 nonce 更新,或者直接退出
|
|
|
- # 这里我们选择继续运行,但 nonce 不会被更新
|
|
|
-
|
|
|
while True:
|
|
|
try:
|
|
|
new_eth_price = None
|
|
|
@@ -239,22 +202,16 @@ def update_core_data_periodically():
|
|
|
else:
|
|
|
logger.warning("Binance client 未初始化, 无法获取 ETH 价格。")
|
|
|
|
|
|
- # 2. 获取最新的 Nonce 和 最新的block_number 以及 最新的賬戶eth餘額
|
|
|
+ # 2. 获取最新的block_number
|
|
|
# 确保 w3 已初始化且 USER_WALLET 已配置
|
|
|
- if w3 and w3.is_connected() and USER_WALLET and USER_WALLET != "你的钱包地址":
|
|
|
+ if w3 and w3.is_connected():
|
|
|
try:
|
|
|
new_block = w3.eth.get_block('latest')
|
|
|
new_block_number = new_block['number']
|
|
|
- new_nonce = w3.eth.get_transaction_count(USER_WALLET, 'latest')
|
|
|
- eth_balance_origin = w3.eth.get_balance(USER_WALLET)
|
|
|
- new_eth_balance = Decimal(eth_balance_origin / (10 ** 18))
|
|
|
- new_eth_balance = new_eth_balance.quantize(Decimal('1e-6'), rounding=ROUND_DOWN)
|
|
|
except Exception as e:
|
|
|
- logger.error(f"为 {USER_WALLET} 获取 Nonce、BlockNumber、EthBalances 失败: {e}")
|
|
|
+ logger.error(f"获取 BlockNumber 失败: {e}")
|
|
|
elif not (w3 and w3.is_connected()):
|
|
|
- logger.warning("Web3 未连接, 无法获取 nonce。")
|
|
|
- elif not (USER_WALLET and USER_WALLET != "你的钱包地址"):
|
|
|
- logger.warning("USER_WALLET 配置不正确, 无法获取 nonce。")
|
|
|
+ logger.warning("Web3 未连接, 无法获取 BlockNumber。")
|
|
|
|
|
|
# 3. 更新共享数据 core_data (使用锁)
|
|
|
# 只有当获取到新数据时才更新,避免不必要的写操作和日志
|
|
|
@@ -300,8 +257,6 @@ def update_mexc_data_periodically():
|
|
|
while True:
|
|
|
try:
|
|
|
new_account_info = None
|
|
|
- new_withdraw_list = None
|
|
|
- new_deposit_list = None
|
|
|
new_coin_info_list = None
|
|
|
|
|
|
# 1. new_account_info
|
|
|
@@ -318,35 +273,7 @@ def update_mexc_data_periodically():
|
|
|
except Exception as e:
|
|
|
logger.error(f"从 Mexc 获取 Balance 失败: {e}, {new_account_info}")
|
|
|
|
|
|
- # 2. new_deposit_list
|
|
|
- try:
|
|
|
- new_deposit_list = mexc.wallet.get_deposit_list()
|
|
|
-
|
|
|
- if not isinstance(new_deposit_list, list):
|
|
|
- raise Exception("充值信息獲取錯誤")
|
|
|
-
|
|
|
- with mexc_lock:
|
|
|
- mexc_data['deposit_list'] = new_deposit_list
|
|
|
-
|
|
|
- # logger.info(f'deposit_list: {new_deposit_list[0]}')
|
|
|
- except Exception as e:
|
|
|
- logger.error(f"从 Mexc 获取 deposit_list 失败: {e}, {new_deposit_list}")
|
|
|
-
|
|
|
- # 3. new_withdraw_list
|
|
|
- try:
|
|
|
- new_withdraw_list = mexc.wallet.get_withdraw_list()
|
|
|
-
|
|
|
- if not isinstance(new_withdraw_list, list):
|
|
|
- raise Exception("提現信息獲取錯誤")
|
|
|
-
|
|
|
- with mexc_lock:
|
|
|
- mexc_data['withdraw_list'] = new_withdraw_list
|
|
|
-
|
|
|
- # logger.info(f'withdraw_list: {new_withdraw_list[0]}')
|
|
|
- except Exception as e:
|
|
|
- logger.error(f"从 Mexc 获取 withdraw_list 失败: {e}, {new_withdraw_list}")
|
|
|
-
|
|
|
- # 4. new_coin_info list
|
|
|
+ # 2. new_coin_info list
|
|
|
try:
|
|
|
if coin_info_get_delay >= 60:
|
|
|
coin_info_get_delay = 0
|
|
|
@@ -355,7 +282,6 @@ def update_mexc_data_periodically():
|
|
|
if not isinstance(new_coin_info_list, list):
|
|
|
raise Exception("幣種信息獲取錯誤")
|
|
|
|
|
|
-
|
|
|
# 處理幣種信息
|
|
|
new_coin_info_map = {}
|
|
|
for coin_info in new_coin_info_list:
|
|
|
@@ -369,7 +295,7 @@ def update_mexc_data_periodically():
|
|
|
|
|
|
# logger.info(f'coin_info_map: {new_coin_info_map['USDT']}')
|
|
|
except Exception as e:
|
|
|
- logger.error(f"从 Mexc 获取 withdraw_list 失败: {e}, {new_withdraw_list}")
|
|
|
+ logger.error(f"从 Mexc 获取 coinlist 失败: {e}, {new_coin_info_list}")
|
|
|
|
|
|
except Exception as e:
|
|
|
# 捕获线程循环中的其他潜在错误
|
|
|
@@ -383,209 +309,6 @@ def update_mexc_data_periodically():
|
|
|
# 等待 1s
|
|
|
time.sleep(1)
|
|
|
|
|
|
-# --- tx pending數據獲取綫程函數 ---
|
|
|
-def update_tx_data_periodically():
|
|
|
- """
|
|
|
- 每一秒獲取一條tx數據
|
|
|
- """
|
|
|
- global pending_data # 明确表示我们要修改全局的 pending_data
|
|
|
-
|
|
|
- while True:
|
|
|
- # 等待1s
|
|
|
- time.sleep(1)
|
|
|
-
|
|
|
- try:
|
|
|
- # 使用拷貝后的數據,否則可能會出現綫程問題
|
|
|
- with pending_lock:
|
|
|
- pending_data_copy = copy.deepcopy(pending_data)
|
|
|
-
|
|
|
- # 核心數據同理
|
|
|
- with core_lock:
|
|
|
- core_data_copy = copy.deepcopy(core_data)
|
|
|
-
|
|
|
- block_number = core_data_copy['block_number']
|
|
|
- for tx in pending_data_copy:
|
|
|
- try:
|
|
|
- # 已獲取的就不要再獲取了
|
|
|
- if pending_data_copy[tx]['tx_details'] is not None:
|
|
|
- continue
|
|
|
-
|
|
|
- # PENDING_CONFIRM_BLOCK個區塊之後的才進行確認,防止回滾頻繁觸發
|
|
|
- if block_number < pending_data_copy[tx]['block_number'] + PENDING_CONFIRM_BLOCK:
|
|
|
- continue
|
|
|
-
|
|
|
- # 調用ok的api,直接獲取詳細交易
|
|
|
- ok_rst = ok_chain_client.history(CHAIN_ID, tx)
|
|
|
- # 存儲最後一次獲取的細節
|
|
|
- with pending_lock:
|
|
|
- pending_data[tx]['response'] = ok_rst
|
|
|
-
|
|
|
- # 錯誤響應
|
|
|
- if ok_rst['code'] != '0':
|
|
|
- raise RuntimeError("API 返回错误响应", ok_rst)
|
|
|
-
|
|
|
- # ok不一定那麽快獲取到
|
|
|
- if ok_rst['data'] is None:
|
|
|
- # 每一個之間等待1s
|
|
|
- time.sleep(1)
|
|
|
-
|
|
|
- continue
|
|
|
-
|
|
|
- details = ok_rst['data']
|
|
|
- status = details['status']
|
|
|
-
|
|
|
- if status != 'fail':
|
|
|
- # 有時候不會馬上識別出成交數量
|
|
|
- if 'fromTokenDetails' not in details or 'toTokenDetails' not in details:
|
|
|
- # 每一個之間等待1s
|
|
|
- time.sleep(1)
|
|
|
-
|
|
|
- continue
|
|
|
-
|
|
|
- # 有時候不會馬上識別出成交數量 判斷2
|
|
|
- if details['fromTokenDetails'] is None or details['toTokenDetails'] is None:
|
|
|
- # 每一個之間等待1s
|
|
|
- time.sleep(1)
|
|
|
-
|
|
|
- continue
|
|
|
-
|
|
|
- # 有時候不會馬上識別出gas信息之類的
|
|
|
- fileds = ['gasLimit', 'gasPrice', 'gasUsed', 'height']
|
|
|
- insufficient = False
|
|
|
- for filed in fileds:
|
|
|
- if details[filed] == '':
|
|
|
- insufficient = True
|
|
|
- break
|
|
|
- if insufficient:
|
|
|
- # 每一個之間等待1s
|
|
|
- time.sleep(1)
|
|
|
-
|
|
|
- continue
|
|
|
-
|
|
|
- # 成功獲取之後直接調用更新
|
|
|
- with pending_lock:
|
|
|
- pending_data[tx]['tx_details'] = details
|
|
|
-
|
|
|
- formated_data = pformat(ok_rst['data'], indent=2) # indent=2 让格式更整齐
|
|
|
- logger.info(f"獲取成功: \n{formated_data}")
|
|
|
- except Exception as e:
|
|
|
- exc_traceback = traceback.format_exc()
|
|
|
- logger.error(f"tx數據獲取失敗\n{exc_traceback}")
|
|
|
- # traceback.print_exc()
|
|
|
-
|
|
|
- # 每一個之間等待1s
|
|
|
- time.sleep(1)
|
|
|
- except Exception as e:
|
|
|
- exc_traceback = traceback.format_exc()
|
|
|
- logger.error(f"pending更新线程发生未知错误\n{exc_traceback}")
|
|
|
- # traceback.print_exc()
|
|
|
-
|
|
|
-# --- 餘額平衡綫程 ---
|
|
|
-def balance_available_funds_periodically():
|
|
|
- """
|
|
|
- 每10秒嘗試平衡一次餘額
|
|
|
- """
|
|
|
- PROPORTION_LIMIT = Decimal(withdraw['proportion_limit']) # 鏈上資金比例低於這個值就會觸發平衡
|
|
|
- PROPORTION_TARGET = Decimal(withdraw['proportion_target']) # 鏈上資金占比目標,1表示100%是鏈上資金
|
|
|
- BASE_COIN = 'USDT'
|
|
|
- BASE_COIN_ADDR = '0xdAC17F958D2ee523a2206206994597C13D831ec7'
|
|
|
-
|
|
|
- CANT_WITHDRAW_STATE_LIST = ['IDLE',
|
|
|
- 'CHECK',
|
|
|
- 'SELLING_ON_EXCHANGE',
|
|
|
- 'WAITING_SELL_CONFIRM',
|
|
|
- "BUYING_ON_CHAIN",
|
|
|
- "WAITING_CHAIN_CONFIRM",
|
|
|
- "WAITING_EXCHANGE_ROLLBACK"
|
|
|
- ]
|
|
|
-
|
|
|
- global processing_list
|
|
|
-
|
|
|
- while True:
|
|
|
- time.sleep(10)
|
|
|
-
|
|
|
- try:
|
|
|
- mexc_available = Decimal(0)
|
|
|
-
|
|
|
- # 交易所餘額讀取
|
|
|
- with mexc_lock:
|
|
|
- balances = mexc_data['account_info']['balances']
|
|
|
-
|
|
|
- for balance in balances:
|
|
|
- if balance['asset'].upper() == BASE_COIN:
|
|
|
- mexc_available = Decimal(balance['free'])
|
|
|
- mexc_available = mexc_available.quantize(Decimal('1e-2'), rounding=ROUND_DOWN)
|
|
|
-
|
|
|
- # 鏈上餘額讀取
|
|
|
- chain_available = web3.get_erc20_balance(BASE_COIN_ADDR)
|
|
|
- chain_available = chain_available.quantize(Decimal('1e-2'), rounding=ROUND_DOWN)
|
|
|
-
|
|
|
- # 縂可用餘額(不包括lock的)
|
|
|
- total_available = mexc_available + chain_available
|
|
|
-
|
|
|
- # 小於20都懶得做平衡,手續費都不夠
|
|
|
- if total_available < Decimal(20):
|
|
|
- continue
|
|
|
-
|
|
|
- # 抹茶餘額也要大於20
|
|
|
- if mexc_available < Decimal(20):
|
|
|
- continue
|
|
|
-
|
|
|
- # 計算鏈上資金佔總體的比例
|
|
|
- proportion = chain_available / total_available
|
|
|
- proportion = proportion.quantize(Decimal('1e-4'), rounding=ROUND_DOWN)
|
|
|
-
|
|
|
- # 判斷比例是否滿足limit,不滿足則先不提現
|
|
|
- if proportion > PROPORTION_LIMIT:
|
|
|
- continue
|
|
|
-
|
|
|
- # 鏈上應該具備的資金量
|
|
|
- chain_available_target = total_available * PROPORTION_TARGET
|
|
|
- mexc_should_be_withdrawal_founds = chain_available_target - chain_available
|
|
|
- mexc_should_be_withdrawal_founds = mexc_should_be_withdrawal_founds.quantize(Decimal(1), rounding=ROUND_DOWN)
|
|
|
-
|
|
|
- # 如若當前綫程中有未執行完的,先不執行提現
|
|
|
- with list_lock:
|
|
|
- cant_withdraw = False
|
|
|
-
|
|
|
- for processing in processing_list:
|
|
|
- if processing['currentState'] in CANT_WITHDRAW_STATE_LIST:
|
|
|
- cant_withdraw = True
|
|
|
- break
|
|
|
-
|
|
|
- # 不執行提現判斷
|
|
|
- if cant_withdraw:
|
|
|
- # formated_processing = pformat(processing_list, indent=2)
|
|
|
- # logger.info(f"不執行提現, 因爲: \n{formated_processing}")
|
|
|
- continue
|
|
|
-
|
|
|
- if mexc_should_be_withdrawal_founds > 0:
|
|
|
- withdrawal_params = {
|
|
|
- 'coin': 'USDT',
|
|
|
- 'netWork': 'ETH',
|
|
|
- 'address': USER_WALLET,
|
|
|
- 'amount': mexc_should_be_withdrawal_founds,
|
|
|
- }
|
|
|
- withdrawal_params_formated = pformat(withdrawal_params, indent=2)
|
|
|
- withdrawal_rst = mexc.wallet.post_withdraw(withdrawal_params)
|
|
|
- withdrawal_rst_formated = pformat(withdrawal_rst, indent=2)
|
|
|
-
|
|
|
- logger.info(f"[withdrawal]mexc_available={mexc_available}, chain_available={chain_available},proportion={proportion}, mexc_withdrawal={mexc_should_be_withdrawal_founds}")
|
|
|
-
|
|
|
- if "id" not in withdrawal_rst:
|
|
|
- msg = f"[withdrawal]交易所提现失败\n參數: {withdrawal_params_formated}\n響應: {withdrawal_rst_formated}"
|
|
|
- logger.error(msg)
|
|
|
- else:
|
|
|
- msg = f"[withdrawal]交易所提现已发送\n參數: {withdrawal_params_formated}\n響應: {withdrawal_rst_formated}"
|
|
|
- logger.info(msg)
|
|
|
- else:
|
|
|
- # TODO 這是另一個方向,需要從鏈上往交易所劃轉
|
|
|
- pass
|
|
|
- except Exception as e:
|
|
|
- exc_traceback = traceback.format_exc()
|
|
|
- logger.error(f"可用資金平衡綫程发生未知错误\n{exc_traceback}")
|
|
|
- # traceback.print_exc()
|
|
|
-
|
|
|
@app.route('/submit_process', methods=['POST'])
|
|
|
def handle_submit_process():
|
|
|
data = request.get_json()
|
|
|
@@ -618,8 +341,6 @@ def handle_submit_process():
|
|
|
process_item = copy.deepcopy(data)
|
|
|
process_item['id'] = process_id
|
|
|
process_item['creationTime'] = get_formatted_timestamp(), # 创建时间
|
|
|
- process_item['userWallet'] = USER_WALLET
|
|
|
- process_item['userExchangeWallet'] = USER_EXCHANGE_WALLET
|
|
|
process_item['stateFlow'] = [] # 状态流转记录
|
|
|
process_item['currentState'] = "PENDING_START"
|
|
|
|
|
|
@@ -673,14 +394,6 @@ if __name__ == "__main__":
|
|
|
updater_thread = threading.Thread(target=update_mexc_data_periodically, daemon=True)
|
|
|
updater_thread.start()
|
|
|
|
|
|
- logger.info("启动pending信息獲取线程...")
|
|
|
- pending_thread = threading.Thread(target=update_tx_data_periodically, daemon=True)
|
|
|
- pending_thread.start()
|
|
|
-
|
|
|
- logger.info("启动餘額平衡线程...")
|
|
|
- pending_thread = threading.Thread(target=balance_available_funds_periodically, daemon=True)
|
|
|
- pending_thread.start()
|
|
|
-
|
|
|
logger.info("主线程继续执行,可以执行其他任务或保持运行以观察数据更新。")
|
|
|
|
|
|
logger.info("启动 Flask 套利执行服务器...")
|