|
|
@@ -12,21 +12,39 @@ import plotly.graph_objects as go
|
|
|
from plotly.utils import PlotlyJSONEncoder
|
|
|
|
|
|
# --- 配置部分 ---
|
|
|
-IN_TOKEN_ADDRESS_SOLANA = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'
|
|
|
-OUT_TOKEN_ADDRESS_SOLANA = '0x28561B8A2360F463011c16b6Cc0B0cbEF8dbBcad'
|
|
|
-AMOUNT_TO_QUERY_OPENOCEAN_IN_SOL = decimal.Decimal('1')
|
|
|
-GATEIO_SPOT_PAIR_RFC_USDT = 'MOODENGETH_USDT'
|
|
|
-BINANCE_SOL_USDT_PAIR = 'ETHUSDT'
|
|
|
+# OpenOcean (输入 ETH, 输出目标代币, 得到 目标代币/ETH 价格)
|
|
|
+# Token addresses are for Ethereum Mainnet
|
|
|
+IN_TOKEN_ADDRESS_ETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2' # WETH Ethereum Mainnet
|
|
|
+OUT_TOKEN_ADDRESS_TARGET_ETH = '0xf816507E690f5Aa4E29d164885EB5fa7a5627860' # MOODEN on Ethereum
|
|
|
+AMOUNT_TO_QUERY_OPENOCEAN_IN_ETH = decimal.Decimal('1') # 例如用 1 ETH 去查询能买多少目标代币
|
|
|
+
|
|
|
+# MEXC (抹茶) 现货 (目标代币/USDT)
|
|
|
+# 重要: 确保这个交易对在MEXC上存在并且是 TARGET_ASSET/USDT 的形式
|
|
|
+MEXC_TARGET_SPOT_PAIR_USDT = 'RATO_USDT' # 举例,你需要替换成实际的目标代币在MEXC上的交易对
|
|
|
+
|
|
|
+# Binance (ETH/USDT - 用于转换)
|
|
|
+BINANCE_BASE_CURRENCY_PAIR_USDT = 'ETHUSDT' # 基础货币现在是ETH
|
|
|
+
|
|
|
proxies = None
|
|
|
decimal.getcontext().prec = 36
|
|
|
|
|
|
|
|
|
-# --- 价格获取函数 (与上一版相同,此处省略以保持简洁) ---
|
|
|
-def get_openocean_price_eth(in_token_addr, out_token_addr, human_amount_in_decimal_for_request):
|
|
|
- chain = 'eth';
|
|
|
- url = f'https://open-api.openocean.finance/v4/{chain}/quote'
|
|
|
+# --- 价格获取函数 ---
|
|
|
+# OpenOcean (获取 目标代币/ETH 价格)
|
|
|
+def get_openocean_price_vs_base_currency(chain_name, in_token_addr, out_token_addr, human_amount_in_base_currency):
|
|
|
+ # chain_name: 'eth', 'solana', etc.
|
|
|
+ # in_token_addr: 基础货币的地址 (如 WETH, Wrapped SOL)
|
|
|
+ # out_token_addr: 目标代币的地址
|
|
|
+ # human_amount_in_base_currency: 用多少基础货币去查询
|
|
|
+ # 返回: price_base_currency_per_target_token (即 1 目标代币 = X 基础货币)
|
|
|
+ url = f'https://open-api.openocean.finance/v4/{chain_name}/quote'
|
|
|
+ # OpenOcean ETH chain gasPrice might need adjustment. For now using a placeholder.
|
|
|
+ # Check OpenOcean docs for recommended gasPrice values or if it's auto-detected for ETH.
|
|
|
+ # Using a higher gas price for ETH as an example. You might need to fetch current gas prices.
|
|
|
+ gas_price_value = '50000000000' if chain_name == 'eth' else '0.000005' # Gwei for ETH, lamports for SOL
|
|
|
+
|
|
|
params = {'inTokenAddress': in_token_addr, 'outTokenAddress': out_token_addr,
|
|
|
- 'amount': str(human_amount_in_decimal_for_request), 'gasPrice': '5000000000'}
|
|
|
+ 'amount': str(human_amount_in_base_currency), 'gasPrice': gas_price_value}
|
|
|
try:
|
|
|
r = requests.get(url, params=params, proxies=proxies, timeout=10);
|
|
|
r.raise_for_status();
|
|
|
@@ -36,55 +54,58 @@ def get_openocean_price_eth(in_token_addr, out_token_addr, human_amount_in_decim
|
|
|
req = ['inToken', 'outToken', 'inAmount', 'outAmount']
|
|
|
if not all(k in d for k in req) or d['inToken'].get('decimals') is None or d['outToken'].get(
|
|
|
'decimals') is None: return {"error": "OO API缺少数据"}
|
|
|
- in_dec, out_dec = int(d['inToken']['decimals']), int(d['outToken']['decimals']);
|
|
|
- atomic_in, atomic_out = decimal.Decimal(d['inAmount']), decimal.Decimal(d['outAmount'])
|
|
|
- h_in_sol = atomic_in / (10 ** in_dec);
|
|
|
- h_out_rfc = atomic_out / (10 ** out_dec)
|
|
|
- if h_out_rfc == 0: return {"error": "OO输出RFC为0"}
|
|
|
- return {"price_sol_per_rfc": h_in_sol / h_out_rfc}
|
|
|
+ in_dec, out_dec = int(d['inToken']['decimals']), int(d['outToken']['decimals'])
|
|
|
+ atomic_in_base, atomic_out_target = decimal.Decimal(d['inAmount']), decimal.Decimal(d['outAmount'])
|
|
|
+ human_in_base = atomic_in_base / (10 ** in_dec) # 花费的基础货币数量 (e.g., ETH)
|
|
|
+ human_out_target = atomic_out_target / (10 ** out_dec) # 得到的目标代币数量
|
|
|
+ if human_out_target == 0: return {"error": f"OO输出目标代币为0 ({chain_name})"}
|
|
|
+ # 价格:1 目标代币 = X 基础货币 => 基础货币数量 / 目标代币数量
|
|
|
+ price_val = human_in_base / human_out_target
|
|
|
+ return {f"price_base_per_target": price_val} # e.g. price_eth_per_mooden
|
|
|
else:
|
|
|
return {
|
|
|
- "error": f"OO API错误 - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"}
|
|
|
+ "error": f"OO API错误({chain_name}) - Code:{data.get('code', 'N/A')}, Msg:{data.get('msg', data.get('message', 'N/A')) if isinstance(data, dict) else '格式错误'}"}
|
|
|
except Exception as e:
|
|
|
- return {"error": f"OO(Solana)请求错误: {e}"}
|
|
|
+ return {"error": f"OO({chain_name})请求错误: {e}"}
|
|
|
|
|
|
|
|
|
-def get_gateio_spot_price_usdt(pair_symbol):
|
|
|
- url = f'https://api.gateio.ws/api/v4/spot/tickers';
|
|
|
- params = {'currency_pair': pair_symbol}
|
|
|
+# MEXC 现货 (获取 目标代币/USDT 价格)
|
|
|
+def get_mexc_spot_price_usdt(pair_symbol):
|
|
|
+ # MEXC API v3: https://mxcdevelop.github.io/apidocs/spot_v3_en/#ticker-price
|
|
|
+ # pair_symbol should be like 'BTCUSDT', 'ETHUSDT', 'MOODENUSDT'
|
|
|
+ url = "https://api.mexc.com/api/v3/ticker/price"
|
|
|
+ params = {'symbol': pair_symbol.replace('_', '')} # MEXC uses 'BTCUSDT' not 'BTC_USDT'
|
|
|
try:
|
|
|
r = requests.get(url, params=params, proxies=proxies, timeout=10);
|
|
|
r.raise_for_status();
|
|
|
data = r.json()
|
|
|
- if isinstance(data, list) and data:
|
|
|
- td = data[0]
|
|
|
- if td.get('currency_pair') == pair_symbol and td.get('lowest_ask'):
|
|
|
- return {"price_rfc_usdt": decimal.Decimal(td['lowest_ask'])}
|
|
|
- else:
|
|
|
- return {"error": f"Gate现货({pair_symbol})数据不匹配或无价格"}
|
|
|
- elif isinstance(data, dict) and data.get('label'):
|
|
|
- return {"error": f"Gate现货API错误: {data['label']}-{data.get('message', '')}"}
|
|
|
+ # MEXC an array of tickers if no symbol is provided, or a single object if symbol is specific
|
|
|
+ if isinstance(data, dict) and data.get('symbol') == params['symbol'] and 'price' in data:
|
|
|
+ return {"price_target_usdt": decimal.Decimal(data['price'])}
|
|
|
+ elif isinstance(data, list) and len(data) == 1 and data[0].get('symbol') == params['symbol'] and 'price' in \
|
|
|
+ data[0]: # Sometimes it returns a list with one item
|
|
|
+ return {"price_target_usdt": decimal.Decimal(data[0]['price'])}
|
|
|
else:
|
|
|
- return {"error": f"Gate现货API({pair_symbol})数据格式错误或未找到"}
|
|
|
+ error_msg = data.get('msg', '未知MEXC错误或交易对不存在') if isinstance(data, dict) else 'MEXC响应格式不正确'
|
|
|
+ return {"error": f"MEXC现货({pair_symbol}) API错误: {error_msg}"}
|
|
|
except Exception as e:
|
|
|
- return {"error": f"Gate现货({pair_symbol})请求错误: {e}"}
|
|
|
+ return {"error": f"MEXC现货({pair_symbol})请求错误: {e}"}
|
|
|
|
|
|
|
|
|
-def get_binance_spot_price_usdt(symbol):
|
|
|
+# Binance (获取 基础货币/USDT 价格, e.g., ETH/USDT)
|
|
|
+def get_binance_base_currency_price_usdt(symbol):
|
|
|
url = "https://api.binance.com/api/v3/ticker/price";
|
|
|
- params = {'symbol': symbol}
|
|
|
+ params = {'symbol': symbol.replace('_', '')}
|
|
|
try:
|
|
|
response = requests.get(url, params=params, proxies=proxies, timeout=10);
|
|
|
response.raise_for_status();
|
|
|
data = response.json()
|
|
|
- if 'price' in data and data.get('symbol') == symbol:
|
|
|
- return {"price_sol_usdt": decimal.Decimal(data['price'])}
|
|
|
+ if 'price' in data and data.get('symbol') == params['symbol']:
|
|
|
+ return {"price_base_usdt": decimal.Decimal(data['price'])} # e.g. price_eth_usdt
|
|
|
else:
|
|
|
msg = data.get('msg', '未知错误'); return {"error": f"Binance API错误 ({symbol}): {msg}"}
|
|
|
- except requests.exceptions.RequestException as e:
|
|
|
- return {"error": f"Binance API ({symbol}) 请求失败: {e}"}
|
|
|
except Exception as e:
|
|
|
- return {"error": f"Binance API ({symbol}) 意外错误: {e}"}
|
|
|
+ return {"error": f"Binance API ({symbol}) 错误: {e}"}
|
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
@@ -95,12 +116,21 @@ MAX_HISTORY_POINTS_PLOTLY = 86400
|
|
|
REFRESH_INTERVAL_SECONDS = 1
|
|
|
|
|
|
historical_data_points = deque(maxlen=MAX_HISTORY_POINTS_PLOTLY)
|
|
|
+
|
|
|
+# Extract target asset and base currency symbols from config
|
|
|
+TARGET_ASSET_SYMBOL = MEXC_TARGET_SPOT_PAIR_USDT.split('_')[0] # e.g., MOODEN
|
|
|
+BASE_CURRENCY_SYMBOL = BINANCE_BASE_CURRENCY_PAIR_USDT.replace('USDT', '') # e.g., ETH
|
|
|
+
|
|
|
latest_values_for_table = {
|
|
|
- "oo_rfc_sol_price": "N/A", "gate_spot_rfc_sol_price": "N/A", "sol_usdt_price_binance": "N/A",
|
|
|
- "diff_oo_vs_spot_rfc_sol_percentage": "N/A",
|
|
|
- "oo_error": None, "gate_spot_error": None, "binance_sol_error": None,
|
|
|
- "last_updated": "N/A", "gate_spot_pair_name_usdt": GATEIO_SPOT_PAIR_RFC_USDT,
|
|
|
- "target_asset_symbol": GATEIO_SPOT_PAIR_RFC_USDT.split('_')[0]
|
|
|
+ f"oo_target_vs_base_price": "N/A", # e.g., oo_mooden_vs_eth_price
|
|
|
+ f"mexc_target_vs_base_price": "N/A", # e.g., mexc_mooden_vs_eth_price (converted)
|
|
|
+ f"binance_base_vs_usdt_price": "N/A", # e.g., binance_eth_vs_usdt_price (reference)
|
|
|
+ f"diff_oo_vs_mexc_target_base_percentage": "N/A",
|
|
|
+ "oo_error": None, "mexc_error": None, "binance_error": None,
|
|
|
+ "last_updated": "N/A",
|
|
|
+ "mexc_spot_pair_usdt": MEXC_TARGET_SPOT_PAIR_USDT, # Original MEXC pair
|
|
|
+ "target_asset_symbol_for_display": TARGET_ASSET_SYMBOL,
|
|
|
+ "base_currency_symbol_for_display": BASE_CURRENCY_SYMBOL
|
|
|
}
|
|
|
data_lock = threading.Lock()
|
|
|
|
|
|
@@ -112,58 +142,78 @@ def calculate_percentage_diff(price_a, price_b):
|
|
|
return None
|
|
|
|
|
|
|
|
|
-def convert_usdt_price_to_sol_price(price_asset_usdt, price_sol_usdt):
|
|
|
- if price_asset_usdt is not None and price_sol_usdt is not None and price_sol_usdt > 0:
|
|
|
- return price_asset_usdt / price_sol_usdt
|
|
|
+def convert_usdt_price_to_base_currency_price(price_target_usdt, price_base_usdt):
|
|
|
+ # Converts Price(Target/USDT) to Price(Target/BaseCurrency) using Price(BaseCurrency/USDT)
|
|
|
+ # e.g., MOODEN/ETH = MOODEN/USDT / ETH/USDT
|
|
|
+ if price_target_usdt is not None and price_base_usdt is not None and price_base_usdt > 0:
|
|
|
+ return price_target_usdt / price_base_usdt
|
|
|
return None
|
|
|
|
|
|
|
|
|
def update_data_for_plotly_and_table():
|
|
|
global historical_data_points, latest_values_for_table
|
|
|
- print("数据更新线程启动 (仅现货)...")
|
|
|
+ print(f"数据更新线程启动 ({TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL})...")
|
|
|
while True:
|
|
|
fetch_time_full = time.strftime("%Y-%m-%d %H:%M:%S");
|
|
|
fetch_time_chart = time.strftime("%H:%M:%S")
|
|
|
- oo_data = get_openocean_price_eth(IN_TOKEN_ADDRESS_SOLANA, OUT_TOKEN_ADDRESS_SOLANA,
|
|
|
- AMOUNT_TO_QUERY_OPENOCEAN_IN_SOL)
|
|
|
- spot_usdt_data = get_gateio_spot_price_usdt(GATEIO_SPOT_PAIR_RFC_USDT)
|
|
|
- binance_sol_usdt_data = get_binance_spot_price_usdt(BINANCE_SOL_USDT_PAIR)
|
|
|
- oo_rfc_sol_price = oo_data.get("price_sol_per_rfc");
|
|
|
- spot_rfc_usdt_price = spot_usdt_data.get("price_rfc_usdt")
|
|
|
- sol_usdt_price = binance_sol_usdt_data.get("price_sol_usdt")
|
|
|
+
|
|
|
+ # OpenOcean: TARGET_ASSET / BASE_CURRENCY (e.g., MOODEN/ETH)
|
|
|
+ oo_data = get_openocean_price_vs_base_currency('eth', IN_TOKEN_ADDRESS_ETH, OUT_TOKEN_ADDRESS_TARGET_ETH,
|
|
|
+ AMOUNT_TO_QUERY_OPENOCEAN_IN_ETH)
|
|
|
+
|
|
|
+ # MEXC: TARGET_ASSET / USDT (e.g., MOODEN/USDT)
|
|
|
+ mexc_target_usdt_data = get_mexc_spot_price_usdt(MEXC_TARGET_SPOT_PAIR_USDT)
|
|
|
+
|
|
|
+ # Binance: BASE_CURRENCY / USDT (e.g., ETH/USDT)
|
|
|
+ binance_base_usdt_data = get_binance_base_currency_price_usdt(BINANCE_BASE_CURRENCY_PAIR_USDT)
|
|
|
+
|
|
|
+ oo_target_vs_base_price = oo_data.get("price_base_per_target") # 1 TARGET = X BASE
|
|
|
+ mexc_target_usdt_price = mexc_target_usdt_data.get("price_target_usdt") # 1 TARGET = X USDT
|
|
|
+ base_usdt_price = binance_base_usdt_data.get("price_base_usdt") # 1 BASE = X USDT
|
|
|
+
|
|
|
oo_err = oo_data.get("error");
|
|
|
- spot_err = spot_usdt_data.get("error");
|
|
|
- binance_err = binance_sol_usdt_data.get("error")
|
|
|
- spot_rfc_sol_price = convert_usdt_price_to_sol_price(spot_rfc_usdt_price, sol_usdt_price)
|
|
|
- diff_oo_spot_pct = calculate_percentage_diff(oo_rfc_sol_price, spot_rfc_sol_price)
|
|
|
+ mexc_err = mexc_target_usdt_data.get("error");
|
|
|
+ binance_err = binance_base_usdt_data.get("error")
|
|
|
+
|
|
|
+ # Convert MEXC price to TARGET_ASSET/BASE_CURRENCY
|
|
|
+ mexc_target_vs_base_price = convert_usdt_price_to_base_currency_price(mexc_target_usdt_price, base_usdt_price)
|
|
|
+
|
|
|
+ diff_oo_vs_mexc_pct = calculate_percentage_diff(oo_target_vs_base_price, mexc_target_vs_base_price)
|
|
|
+
|
|
|
current_point = {
|
|
|
"time": fetch_time_chart,
|
|
|
- "oo_rfc_sol": float(oo_rfc_sol_price) if oo_rfc_sol_price else None,
|
|
|
- "spot_rfc_sol": float(spot_rfc_sol_price) if spot_rfc_sol_price else None,
|
|
|
- "diff_oo_spot_rfc_sol": float(diff_oo_spot_pct) if diff_oo_spot_pct is not None else None,
|
|
|
+ "oo_target_vs_base": float(oo_target_vs_base_price) if oo_target_vs_base_price else None,
|
|
|
+ "mexc_target_vs_base": float(mexc_target_vs_base_price) if mexc_target_vs_base_price else None,
|
|
|
+ "diff_oo_vs_mexc_target_base": float(diff_oo_vs_mexc_pct) if diff_oo_vs_mexc_pct is not None else None,
|
|
|
}
|
|
|
+
|
|
|
with data_lock:
|
|
|
historical_data_points.append(current_point)
|
|
|
- latest_values_for_table["oo_rfc_sol_price"] = str(oo_rfc_sol_price) if oo_rfc_sol_price else "N/A"
|
|
|
- latest_values_for_table["gate_spot_rfc_sol_price"] = str(
|
|
|
- spot_rfc_sol_price) if spot_rfc_sol_price else "N/A"
|
|
|
- latest_values_for_table["sol_usdt_price_binance"] = str(sol_usdt_price) if sol_usdt_price else "N/A"
|
|
|
+ latest_values_for_table[f"oo_target_vs_base_price"] = str(
|
|
|
+ oo_target_vs_base_price) if oo_target_vs_base_price else "N/A"
|
|
|
+ latest_values_for_table[f"mexc_target_vs_base_price"] = str(
|
|
|
+ mexc_target_vs_base_price) if mexc_target_vs_base_price else "N/A"
|
|
|
+ latest_values_for_table[f"binance_base_vs_usdt_price"] = str(base_usdt_price) if base_usdt_price else "N/A"
|
|
|
latest_values_for_table[
|
|
|
- "diff_oo_vs_spot_rfc_sol_percentage"] = f"{diff_oo_spot_pct:+.4f}%" if diff_oo_spot_pct is not None else "N/A"
|
|
|
+ f"diff_oo_vs_mexc_target_base_percentage"] = f"{diff_oo_vs_mexc_pct:+.4f}%" if diff_oo_vs_mexc_pct is not None else "N/A"
|
|
|
latest_values_for_table["oo_error"] = oo_err;
|
|
|
- latest_values_for_table["gate_spot_error"] = spot_err
|
|
|
- latest_values_for_table["binance_sol_error"] = binance_err;
|
|
|
+ latest_values_for_table["mexc_error"] = mexc_err
|
|
|
+ latest_values_for_table["binance_error"] = binance_err;
|
|
|
latest_values_for_table["last_updated"] = fetch_time_full
|
|
|
+
|
|
|
print(
|
|
|
- f"{fetch_time_chart} Fetch | OO_RFC/SOL:{'OK' if oo_rfc_sol_price else 'F'} | Spot_RFC/SOL:{'OK' if spot_rfc_sol_price else 'F'} | SOL/USDT:{'OK' if sol_usdt_price else 'F'}")
|
|
|
+ f"{fetch_time_chart} Fetch | OO_{TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL}:{'OK' if oo_target_vs_base_price else 'F'} | MEXC_{TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL}:{'OK' if mexc_target_vs_base_price else 'F'} | {BASE_CURRENCY_SYMBOL}/USDT:{'OK' if base_usdt_price else 'F'}")
|
|
|
time.sleep(REFRESH_INTERVAL_SECONDS)
|
|
|
|
|
|
|
|
|
@app.route('/')
|
|
|
def index_plotly():
|
|
|
- target_asset = latest_values_for_table["target_asset_symbol"]
|
|
|
- return render_template('index_plotly_sol_spot_only.html', target_asset=target_asset,
|
|
|
- spot_pair_usdt=GATEIO_SPOT_PAIR_RFC_USDT, sol_usdt_pair_binance=BINANCE_SOL_USDT_PAIR,
|
|
|
+ # 这些符号将传递给模板用于动态显示
|
|
|
+ return render_template('index_plotly_dynamic.html',
|
|
|
+ target_asset=TARGET_ASSET_SYMBOL,
|
|
|
+ base_asset=BASE_CURRENCY_SYMBOL,
|
|
|
+ mexc_spot_pair_usdt=MEXC_TARGET_SPOT_PAIR_USDT,
|
|
|
+ binance_bridge_pair=BINANCE_BASE_CURRENCY_PAIR_USDT,
|
|
|
refresh_interval_ms=REFRESH_INTERVAL_SECONDS * 1000)
|
|
|
|
|
|
|
|
|
@@ -183,7 +233,11 @@ def get_plotly_chart_data():
|
|
|
return jsonify({"price_chart": empty_json, "diff_chart": empty_json})
|
|
|
|
|
|
times = [p['time'] for p in points]
|
|
|
- target_asset_symbol = latest_values_for_table["target_asset_symbol"]
|
|
|
+
|
|
|
+ # 使用从配置中提取的动态符号
|
|
|
+ display_target_asset = latest_values_for_table["target_asset_symbol_for_display"]
|
|
|
+ display_base_asset = latest_values_for_table["base_currency_symbol_for_display"]
|
|
|
+ display_mexc_pair_usdt = latest_values_for_table["mexc_spot_pair_usdt"]
|
|
|
|
|
|
common_xaxis_config = dict(title='时间')
|
|
|
if len(times) > 1:
|
|
|
@@ -194,29 +248,29 @@ def get_plotly_chart_data():
|
|
|
common_legend_config = dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1)
|
|
|
|
|
|
fig_prices = go.Figure()
|
|
|
- fig_prices.add_trace(
|
|
|
- go.Scatter(x=times, y=[p['oo_rfc_sol'] for p in points], mode='lines', name='OpenOcean (RFC/SOL)',
|
|
|
- line=dict(color='rgb(75, 192, 192)'),
|
|
|
- hovertemplate=f'<b>OpenOcean (RFC/SOL)</b><br>Time: %{{x}}<br>Price: %{{y:.8f}} SOL<extra></extra>'))
|
|
|
- fig_prices.add_trace(go.Scatter(x=times, y=[p['spot_rfc_sol'] for p in points], mode='lines',
|
|
|
- name=f'Gate Spot ({GATEIO_SPOT_PAIR_RFC_USDT} → RFC/SOL)',
|
|
|
+ fig_prices.add_trace(go.Scatter(x=times, y=[p['oo_target_vs_base'] for p in points], mode='lines',
|
|
|
+ name=f'OpenOcean ({display_target_asset}/{display_base_asset})',
|
|
|
+ line=dict(color='rgb(75, 192, 192)'),
|
|
|
+ hovertemplate=f'<b>OpenOcean ({display_target_asset}/{display_base_asset})</b><br>Time: %{{x}}<br>Price: %{{y:.8f}} {display_base_asset}<extra></extra>'))
|
|
|
+ fig_prices.add_trace(go.Scatter(x=times, y=[p['mexc_target_vs_base'] for p in points], mode='lines',
|
|
|
+ name=f'MEXC ({display_mexc_pair_usdt} → {display_target_asset}/{display_base_asset})',
|
|
|
line=dict(color='rgb(255, 99, 132)'),
|
|
|
- hovertemplate=f'<b>Gate Spot (RFC/SOL)</b><br>Time: %{{x}}<br>Price: %{{y:.8f}} SOL<extra></extra>'))
|
|
|
- fig_prices.update_layout(title_text=f'{target_asset_symbol}/SOL 价格历史', xaxis=common_xaxis_config.copy(),
|
|
|
- yaxis_title=f'价格 (1 {target_asset_symbol} = X SOL)', legend_title_text='平台',
|
|
|
+ hovertemplate=f'<b>MEXC ({display_target_asset}/{display_base_asset})</b><br>Time: %{{x}}<br>Price: %{{y:.8f}} {display_base_asset}<extra></extra>'))
|
|
|
+ fig_prices.update_layout(title_text=f'{display_target_asset}/{display_base_asset} 价格历史',
|
|
|
+ xaxis=common_xaxis_config.copy(),
|
|
|
+ yaxis_title=f'价格 (1 {display_target_asset} = X {display_base_asset})',
|
|
|
+ legend_title_text='平台',
|
|
|
legend=common_legend_config.copy(), hovermode='x unified',
|
|
|
margin=dict(l=70, r=30, t=80, b=50))
|
|
|
|
|
|
fig_diffs = go.Figure()
|
|
|
- fig_diffs.add_trace(go.Scatter(x=times, y=[p['diff_oo_spot_rfc_sol'] for p in points], mode='lines',
|
|
|
- name=f'OO vs Spot (RFC/SOL)', line=dict(color='rgb(255, 159, 64)'),
|
|
|
- hovertemplate=f'<b>OO vs Spot (RFC/SOL)</b><br>Time: %{{x}}<br>Diff: %{{y:+.4f}}%<extra></extra>'))
|
|
|
- fig_diffs.update_layout(title_text=f'{target_asset_symbol}/SOL 价差百分比历史 (OO vs Spot)',
|
|
|
+ fig_diffs.add_trace(go.Scatter(x=times, y=[p['diff_oo_vs_mexc_target_base'] for p in points], mode='lines',
|
|
|
+ name=f'OO vs MEXC ({display_target_asset}/{display_base_asset})',
|
|
|
+ line=dict(color='rgb(255, 159, 64)'),
|
|
|
+ hovertemplate=f'<b>OO vs MEXC ({display_target_asset}/{display_base_asset})</b><br>Time: %{{x}}<br>Diff: %{{y:+.4f}}%<extra></extra>'))
|
|
|
+ fig_diffs.update_layout(title_text=f'{display_target_asset}/{display_base_asset} 价差百分比历史 (OO vs MEXC)',
|
|
|
xaxis=common_xaxis_config.copy(),
|
|
|
- yaxis_title='价差 (%)',
|
|
|
- legend=common_legend_config.copy(),
|
|
|
- # Applying to diff chart as well, or set showlegend=False
|
|
|
- # showlegend=False, # Uncomment this if you don't want legend on diff chart
|
|
|
+ yaxis_title='价差 (%)', legend_title_text='对比', legend=common_legend_config.copy(),
|
|
|
yaxis_zeroline=True, hovermode='x unified', margin=dict(l=70, r=30, t=80, b=50))
|
|
|
|
|
|
combined_figure_data = {"price_chart": json.loads(json.dumps(fig_prices, cls=PlotlyJSONEncoder)),
|
|
|
@@ -226,13 +280,13 @@ def get_plotly_chart_data():
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
print("应用启动...")
|
|
|
- asset_symbol = GATEIO_SPOT_PAIR_RFC_USDT.split('_')[0]
|
|
|
- print(f"目标资产: {asset_symbol}")
|
|
|
+ print(f"目标资产: {TARGET_ASSET_SYMBOL}")
|
|
|
+ print(f"基础货币 (用于计价): {BASE_CURRENCY_SYMBOL}")
|
|
|
print(
|
|
|
- f"OpenOcean: {asset_symbol}/SOL (通过 {IN_TOKEN_ADDRESS_SOLANA[-6:]}...SOL / {OUT_TOKEN_ADDRESS_SOLANA[-6:]}...{asset_symbol})")
|
|
|
- print(f"Gate.io 现货: {GATEIO_SPOT_PAIR_RFC_USDT} (将转换为 {asset_symbol}/SOL)")
|
|
|
- print(f"币安转换汇率: {BINANCE_SOL_USDT_PAIR}")
|
|
|
+ f"OpenOcean: {TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL} (通过 {IN_TOKEN_ADDRESS_ETH[-6:]}...{BASE_CURRENCY_SYMBOL} / {OUT_TOKEN_ADDRESS_TARGET_ETH[-6:]}...{TARGET_ASSET_SYMBOL} on ETH)")
|
|
|
+ print(f"MEXC 现货: {MEXC_TARGET_SPOT_PAIR_USDT} (将转换为 {TARGET_ASSET_SYMBOL}/{BASE_CURRENCY_SYMBOL})")
|
|
|
+ print(f"币安转换汇率: {BINANCE_BASE_CURRENCY_PAIR_USDT}")
|
|
|
data_thread = threading.Thread(target=update_data_for_plotly_and_table, daemon=True);
|
|
|
data_thread.start()
|
|
|
print(f"Flask 服务将在 http://0.0.0.0:5000 上运行 (刷新间隔: {REFRESH_INTERVAL_SECONDS}s)")
|
|
|
- app.run(debug=False, host='0.0.0.0', port=5000, use_reloader=False)
|
|
|
+ app.run(debug=False, host='0.0.0.0', port=5000, use_reloader=False)
|