|
|
@@ -12,22 +12,18 @@ import plotly.graph_objects as go
|
|
|
from plotly.utils import PlotlyJSONEncoder
|
|
|
|
|
|
# --- 配置部分 ---
|
|
|
-IN_TOKEN_ADDRESS_SOLANA = 'So11111111111111111111111111111111111111112' # SOL Mint Address
|
|
|
-OUT_TOKEN_ADDRESS_SOLANA = '38PgzpJYu2HkiYvV8qePFakB8tuobPdGm2FFEn7Dpump' #OutToken
|
|
|
+IN_TOKEN_ADDRESS_SOLANA = 'So11111111111111111111111111111111111111112'
|
|
|
+OUT_TOKEN_ADDRESS_SOLANA = 'ED5nyyWEzpPPiWimP8vYm7sD7TD3LAt3Q3gRTWHzPJBY'
|
|
|
AMOUNT_TO_QUERY_OPENOCEAN_IN_SOL = decimal.Decimal('10')
|
|
|
-
|
|
|
-GATEIO_SPOT_PAIR_RFC_USDT = 'GORK_USDT'
|
|
|
-# GATEIO_FUTURES_CONTRACT_RFC_USDT = 'GORK_USDT' # 注释掉期货配置
|
|
|
-
|
|
|
+GATEIO_SPOT_PAIR_RFC_USDT = 'MOODENG_USDT'
|
|
|
BINANCE_SOL_USDT_PAIR = 'SOLUSDT'
|
|
|
-
|
|
|
proxies = None
|
|
|
decimal.getcontext().prec = 36
|
|
|
|
|
|
|
|
|
-# --- 价格获取函数 ---
|
|
|
+# --- 价格获取函数 (与上一版相同,此处省略以保持简洁) ---
|
|
|
def get_openocean_price_solana(in_token_addr, out_token_addr, human_amount_in_decimal_for_request):
|
|
|
- chain = 'solana'
|
|
|
+ chain = 'solana';
|
|
|
url = f'https://open-api.openocean.finance/v4/{chain}/quote'
|
|
|
params = {'inTokenAddress': in_token_addr, 'outTokenAddress': out_token_addr,
|
|
|
'amount': str(human_amount_in_decimal_for_request), 'gasPrice': '0.000005'}
|
|
|
@@ -40,13 +36,12 @@ def get_openocean_price_solana(in_token_addr, out_token_addr, human_amount_in_de
|
|
|
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'])
|
|
|
+ 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"}
|
|
|
- price_rfc_per_sol = h_in_sol / h_out_rfc
|
|
|
- return {"price_sol_per_rfc": price_rfc_per_sol}
|
|
|
+ return {"price_sol_per_rfc": h_in_sol / h_out_rfc}
|
|
|
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 '格式错误'}"}
|
|
|
@@ -63,7 +58,7 @@ def get_gateio_spot_price_usdt(pair_symbol):
|
|
|
data = r.json()
|
|
|
if isinstance(data, list) and data:
|
|
|
td = data[0]
|
|
|
- if td.get('currency_pair') == pair_symbol and td.get('lowest_ask'): # 使用 lowest_ask 作为卖价
|
|
|
+ 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})数据不匹配或无价格"}
|
|
|
@@ -75,19 +70,6 @@ def get_gateio_spot_price_usdt(pair_symbol):
|
|
|
return {"error": f"Gate现货({pair_symbol})请求错误: {e}"}
|
|
|
|
|
|
|
|
|
-# def get_gateio_futures_price_usdt(contract_symbol, settle_currency='usdt'): # 注释掉期货函数
|
|
|
-# url = f'https://api.gateio.ws/api/v4/futures/{settle_currency}/tickers'; params = {'contract': contract_symbol}
|
|
|
-# 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('contract') == contract_symbol and td.get('highest_bid'): # 使用 highest_bid 作为买价
|
|
|
-# return {"price_rfc_usdt": decimal.Decimal(td['highest_bid'])}
|
|
|
-# else: return {"error": f"Gate期货({contract_symbol})数据不匹配或无价格"}
|
|
|
-# elif isinstance(data, dict) and data.get('label'): return {"error": f"Gate期货API错误: {data['label']}-{data.get('message','')}"}
|
|
|
-# else: return {"error": f"Gate期货API({contract_symbol})数据格式错误或未找到"}
|
|
|
-# except Exception as e: return {"error": f"Gate期货({contract_symbol})请求错误: {e}"}
|
|
|
-
|
|
|
def get_binance_spot_price_usdt(symbol):
|
|
|
url = "https://api.binance.com/api/v3/ticker/price";
|
|
|
params = {'symbol': symbol}
|
|
|
@@ -109,24 +91,15 @@ app = Flask(__name__)
|
|
|
log = logging.getLogger('werkzeug');
|
|
|
log.setLevel(logging.ERROR)
|
|
|
|
|
|
-MAX_HISTORY_POINTS_PLOTLY = 86400 # 你坚持要这个值,保留它,但请注意性能
|
|
|
+MAX_HISTORY_POINTS_PLOTLY = 86400
|
|
|
REFRESH_INTERVAL_SECONDS = 1
|
|
|
|
|
|
historical_data_points = deque(maxlen=MAX_HISTORY_POINTS_PLOTLY)
|
|
|
latest_values_for_table = {
|
|
|
- "oo_rfc_sol_price": "N/A",
|
|
|
- "gate_spot_rfc_sol_price": "N/A",
|
|
|
- # "gate_futures_rfc_sol_price": "N/A", # 注释
|
|
|
- "sol_usdt_price_binance": "N/A",
|
|
|
+ "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",
|
|
|
- # "diff_oo_vs_futures_rfc_sol_percentage": "N/A", # 注释
|
|
|
- # "diff_spot_vs_futures_rfc_sol_percentage": "N/A", # 注释
|
|
|
- "oo_error": None, "gate_spot_error": None,
|
|
|
- # "gate_futures_error": None, # 注释
|
|
|
- "binance_sol_error": None,
|
|
|
- "last_updated": "N/A",
|
|
|
- "gate_spot_pair_name_usdt": GATEIO_SPOT_PAIR_RFC_USDT,
|
|
|
- # "gate_futures_contract_name_usdt": GATEIO_FUTURES_CONTRACT_RFC_USDT, # 注释
|
|
|
+ "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]
|
|
|
}
|
|
|
data_lock = threading.Lock()
|
|
|
@@ -149,62 +122,38 @@ def update_data_for_plotly_and_table():
|
|
|
global historical_data_points, latest_values_for_table
|
|
|
print("数据更新线程启动 (仅现货)...")
|
|
|
while True:
|
|
|
- fetch_time_full = time.strftime("%Y-%m-%d %H:%M:%S")
|
|
|
+ fetch_time_full = time.strftime("%Y-%m-%d %H:%M:%S");
|
|
|
fetch_time_chart = time.strftime("%H:%M:%S")
|
|
|
-
|
|
|
oo_data = get_openocean_price_solana(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)
|
|
|
- # futures_usdt_data = get_gateio_futures_price_usdt(GATEIO_FUTURES_CONTRACT_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")
|
|
|
+ oo_rfc_sol_price = oo_data.get("price_sol_per_rfc");
|
|
|
spot_rfc_usdt_price = spot_usdt_data.get("price_rfc_usdt")
|
|
|
- # futures_rfc_usdt_price = futures_usdt_data.get("price_rfc_usdt") # 注释
|
|
|
sol_usdt_price = binance_sol_usdt_data.get("price_sol_usdt")
|
|
|
-
|
|
|
- oo_err = oo_data.get("error")
|
|
|
- spot_err = spot_usdt_data.get("error")
|
|
|
- # futures_err = futures_usdt_data.get("error") # 注释
|
|
|
+ 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)
|
|
|
- # futures_rfc_sol_price = convert_usdt_price_to_sol_price(futures_rfc_usdt_price, sol_usdt_price) # 注释
|
|
|
-
|
|
|
diff_oo_spot_pct = calculate_percentage_diff(oo_rfc_sol_price, spot_rfc_sol_price)
|
|
|
- # diff_oo_futures_pct = calculate_percentage_diff(oo_rfc_sol_price, futures_rfc_sol_price) # 注释
|
|
|
- # diff_spot_futures_pct = calculate_percentage_diff(spot_rfc_sol_price, futures_rfc_sol_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,
|
|
|
- # "futures_rfc_sol": float(futures_rfc_sol_price) if futures_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,
|
|
|
- # "diff_oo_futures_rfc_sol": float(diff_oo_futures_pct) if diff_oo_futures_pct is not None else None, # 注释
|
|
|
- # "diff_spot_futures_rfc_sol": float(diff_spot_futures_pct) if diff_spot_futures_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["gate_futures_rfc_sol_price"] = str(futures_rfc_sol_price) if futures_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[
|
|
|
"diff_oo_vs_spot_rfc_sol_percentage"] = f"{diff_oo_spot_pct:+.4f}%" if diff_oo_spot_pct is not None else "N/A"
|
|
|
- # latest_values_for_table["diff_oo_vs_futures_rfc_sol_percentage"] = f"{diff_oo_futures_pct:+.4f}%" if diff_oo_futures_pct is not None else "N/A" # 注释
|
|
|
- # latest_values_for_table["diff_spot_vs_futures_rfc_sol_percentage"] = f"{diff_spot_futures_pct:+.4f}%" if diff_spot_futures_pct is not None else "N/A" # 注释
|
|
|
-
|
|
|
- latest_values_for_table["oo_error"] = oo_err
|
|
|
+ latest_values_for_table["oo_error"] = oo_err;
|
|
|
latest_values_for_table["gate_spot_error"] = spot_err
|
|
|
- # latest_values_for_table["gate_futures_error"] = futures_err # 注释
|
|
|
- latest_values_for_table["binance_sol_error"] = binance_err
|
|
|
+ latest_values_for_table["binance_sol_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'}")
|
|
|
time.sleep(REFRESH_INTERVAL_SECONDS)
|
|
|
@@ -213,17 +162,14 @@ def update_data_for_plotly_and_table():
|
|
|
@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_sol_spot_only.html', target_asset=target_asset,
|
|
|
+ spot_pair_usdt=GATEIO_SPOT_PAIR_RFC_USDT, sol_usdt_pair_binance=BINANCE_SOL_USDT_PAIR,
|
|
|
refresh_interval_ms=REFRESH_INTERVAL_SECONDS * 1000)
|
|
|
|
|
|
|
|
|
@app.route('/table-data')
|
|
|
def get_table_data():
|
|
|
- with data_lock:
|
|
|
- return jsonify(latest_values_for_table)
|
|
|
+ with data_lock: return jsonify(latest_values_for_table)
|
|
|
|
|
|
|
|
|
@app.route('/plotly-chart-data')
|
|
|
@@ -233,12 +179,20 @@ def get_plotly_chart_data():
|
|
|
if not points:
|
|
|
fig = go.Figure();
|
|
|
fig.update_layout(title_text="暂无数据")
|
|
|
- return jsonify({"price_chart": json.loads(json.dumps(fig, cls=PlotlyJSONEncoder)),
|
|
|
- "diff_chart": json.loads(json.dumps(fig, cls=PlotlyJSONEncoder))})
|
|
|
+ empty_json = json.loads(json.dumps(fig, cls=PlotlyJSONEncoder))
|
|
|
+ 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"]
|
|
|
|
|
|
+ common_xaxis_config = dict(title='时间')
|
|
|
+ if len(times) > 1:
|
|
|
+ common_xaxis_config['range'] = [times[0], times[-1]]
|
|
|
+ else:
|
|
|
+ common_xaxis_config['autorange'] = True
|
|
|
+
|
|
|
+ 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)',
|
|
|
@@ -248,27 +202,25 @@ def get_plotly_chart_data():
|
|
|
name=f'Gate Spot ({GATEIO_SPOT_PAIR_RFC_USDT} → RFC/SOL)',
|
|
|
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.add_trace(go.Scatter(x=times, y=[p['futures_rfc_sol'] for p in points], mode='lines', name=f'Gate Futures ({GATEIO_FUTURES_CONTRACT_RFC_USDT} → RFC/SOL)', line=dict(color='rgb(54, 162, 235)'), hovertemplate=f'<b>Gate Futures (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_title='时间',
|
|
|
+ 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='平台',
|
|
|
- hovermode='x unified')
|
|
|
+ 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.add_trace(go.Scatter(x=times, y=[p['diff_oo_futures_rfc_sol'] for p in points], mode='lines', name=f'OO vs Futures (RFC/SOL)', line=dict(color='rgb(153, 102, 255)'), hovertemplate=f'<b>OO vs Futures (RFC/SOL)</b><br>Time: %{{x}}<br>Diff: %{{y:+.4f}}%<extra></extra>')) # 注释
|
|
|
- # fig_diffs.add_trace(go.Scatter(x=times, y=[p['diff_spot_futures_rfc_sol'] for p in points], mode='lines', name=f'Spot vs Futures (RFC/SOL)', line=dict(color='rgb(75, 192, 75)'), hovertemplate=f'<b>Spot vs Futures (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)', xaxis_title='时间',
|
|
|
- yaxis_title='价差 (%)', legend_title_text='对比', yaxis_zeroline=True,
|
|
|
- hovermode='x unified')
|
|
|
-
|
|
|
- combined_figure_data = {
|
|
|
- "price_chart": json.loads(json.dumps(fig_prices, cls=PlotlyJSONEncoder)),
|
|
|
- "diff_chart": json.loads(json.dumps(fig_diffs, cls=PlotlyJSONEncoder))
|
|
|
- }
|
|
|
+ fig_diffs.update_layout(title_text=f'{target_asset_symbol}/SOL 价差百分比历史 (OO vs Spot)',
|
|
|
+ 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_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)),
|
|
|
+ "diff_chart": json.loads(json.dumps(fig_diffs, cls=PlotlyJSONEncoder))}
|
|
|
return jsonify(combined_figure_data)
|
|
|
|
|
|
|
|
|
@@ -279,10 +231,8 @@ if __name__ == "__main__":
|
|
|
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"Gate.io 期货: {GATEIO_FUTURES_CONTRACT_RFC_USDT} (将转换为 {asset_symbol}/SOL)") # 注释
|
|
|
print(f"币安转换汇率: {BINANCE_SOL_USDT_PAIR}")
|
|
|
-
|
|
|
- data_thread = threading.Thread(target=update_data_for_plotly_and_table, daemon=True)
|
|
|
+ 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)
|