495 lines
18 KiB
Python
495 lines
18 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
XTZH AI定价引擎服务
|
||
NAC公链 - XTZH资产稳定币当日铸造价格计算
|
||
基于NAC宪法权重模型:货币层40% + 黄金层10% + 商品层50%
|
||
黄金信任锚覆盖率:125%
|
||
"""
|
||
|
||
import asyncio
|
||
import json
|
||
import logging
|
||
import time
|
||
import math
|
||
from datetime import datetime, timezone
|
||
from typing import Optional
|
||
import urllib.request
|
||
import urllib.error
|
||
from http.server import HTTPServer, BaseHTTPRequestHandler
|
||
import threading
|
||
|
||
# ============================================================================
|
||
# 日志配置
|
||
# ============================================================================
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s [XTZH-AI] %(levelname)s %(message)s',
|
||
handlers=[
|
||
logging.FileHandler('/var/log/xtzh-pricing.log'),
|
||
logging.StreamHandler()
|
||
]
|
||
)
|
||
logger = logging.getLogger('xtzh-pricing')
|
||
|
||
# ============================================================================
|
||
# NAC宪法权重常量(来自constants.rs)
|
||
# ============================================================================
|
||
W_FX_BASE = 4000 # 货币层权重基准 40.00%
|
||
W_AU_BASE = 1000 # 黄金层权重基准 10.00%
|
||
W_COM_BASE = 5000 # 商品层权重基准 50.00%
|
||
W_AU_MIN = 500 # 黄金层权重最小值 5.00%
|
||
W_AU_MAX = 2000 # 黄金层权重最大值 20.00%
|
||
WEIGHT_SUM = 10000 # 权重和 100.00%
|
||
GOLD_TRUST_ANCHOR_COVERAGE = 12500 # 黄金信任锚覆盖率 125.00%
|
||
|
||
# SDR货币篮子权重(IMF 2022-2026)
|
||
SDR_BASKET = {
|
||
'USD': 0.4332, # 43.32%
|
||
'EUR': 0.2923, # 29.23%
|
||
'CNY': 0.1059, # 10.59%
|
||
'JPY': 0.0784, # 7.84%
|
||
'GBP': 0.0802, # 8.02%
|
||
# 其余0.1%忽略
|
||
}
|
||
|
||
# ============================================================================
|
||
# 数据缓存
|
||
# ============================================================================
|
||
_cache = {
|
||
'data': None,
|
||
'timestamp': 0,
|
||
'ttl': 300 # 5分钟缓存
|
||
}
|
||
|
||
# ============================================================================
|
||
# 数据获取函数
|
||
# ============================================================================
|
||
|
||
def fetch_url(url, timeout=10):
|
||
"""获取URL内容"""
|
||
try:
|
||
req = urllib.request.Request(url, headers={
|
||
'User-Agent': 'Mozilla/5.0 (NAC-XTZH-Pricing/1.0)',
|
||
'Accept': 'application/json'
|
||
})
|
||
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
||
return json.loads(resp.read().decode('utf-8'))
|
||
except Exception as e:
|
||
logger.warning(f"获取 {url} 失败: {e}")
|
||
return None
|
||
|
||
def fetch_yahoo_price(symbol):
|
||
"""从Yahoo Finance获取价格"""
|
||
import urllib.parse
|
||
encoded = urllib.parse.quote(symbol)
|
||
url = f"https://query1.finance.yahoo.com/v8/finance/chart/{encoded}?interval=1d&range=1d"
|
||
data = fetch_url(url)
|
||
if data and data.get('chart', {}).get('result'):
|
||
return data['chart']['result'][0]['meta'].get('regularMarketPrice')
|
||
return None
|
||
|
||
def fetch_sdr_rates():
|
||
"""获取SDR汇率(1 XDR = ? 各货币)"""
|
||
data = fetch_url("https://open.er-api.com/v6/latest/XDR")
|
||
if data and data.get('rates'):
|
||
rates = data['rates']
|
||
return {
|
||
'USD': rates.get('USD', 1.374498),
|
||
'EUR': rates.get('EUR', 1.166327),
|
||
'GBP': rates.get('GBP', 1.017137),
|
||
'JPY': rates.get('JPY', 215.268845),
|
||
'CNY': rates.get('CNY', 9.444808),
|
||
'updated': data.get('time_last_update_utc', '')
|
||
}
|
||
# 备用:使用最近已知值
|
||
logger.warning("SDR汇率获取失败,使用备用数据")
|
||
return {
|
||
'USD': 1.374498, 'EUR': 1.166327, 'GBP': 1.017137,
|
||
'JPY': 215.268845, 'CNY': 9.444808, 'updated': 'fallback'
|
||
}
|
||
|
||
def fetch_gold_price_usd():
|
||
"""获取黄金价格(USD/盎司)"""
|
||
# 主源:Yahoo Finance GC=F
|
||
price = fetch_yahoo_price("GC=F")
|
||
if price and price > 0:
|
||
logger.info(f"黄金价格(Yahoo GC=F): ${price}")
|
||
return price, "Yahoo Finance GC=F"
|
||
|
||
# 备源:Binance PAXG(黄金代币)
|
||
data = fetch_url("https://api.binance.com/api/v3/ticker/price?symbol=PAXGUSDT")
|
||
if data and data.get('price'):
|
||
price = float(data['price'])
|
||
logger.info(f"黄金价格(Binance PAXG): ${price}")
|
||
return price, "Binance PAXG"
|
||
|
||
# 备源:CoinGecko
|
||
data = fetch_url("https://api.coingecko.com/api/v3/simple/price?ids=pax-gold&vs_currencies=usd")
|
||
if data and data.get('pax-gold', {}).get('usd'):
|
||
price = float(data['pax-gold']['usd'])
|
||
logger.info(f"黄金价格(CoinGecko PAXG): ${price}")
|
||
return price, "CoinGecko PAXG"
|
||
|
||
# 最终备用
|
||
logger.warning("黄金价格获取失败,使用备用值")
|
||
return 2650.0, "fallback"
|
||
|
||
def fetch_market_indicators():
|
||
"""获取市场指标(VIX、DXY、WTI等)"""
|
||
indicators = {}
|
||
|
||
# VIX波动率指数
|
||
vix = fetch_yahoo_price("^VIX")
|
||
indicators['vix'] = vix if vix else 20.0
|
||
|
||
# DXY美元指数
|
||
dxy = fetch_yahoo_price("DX-Y.NYB")
|
||
if not dxy:
|
||
dxy = fetch_yahoo_price("DX=F")
|
||
indicators['dxy'] = dxy if dxy else 104.0
|
||
|
||
# WTI原油
|
||
wti = fetch_yahoo_price("CL=F")
|
||
indicators['wti'] = wti if wti else 75.0
|
||
|
||
# 铜期货
|
||
copper = fetch_yahoo_price("HG=F")
|
||
indicators['copper'] = copper if copper else 4.2
|
||
|
||
logger.info(f"市场指标: VIX={indicators['vix']}, DXY={indicators['dxy']}, WTI={indicators['wti']}, 铜={indicators['copper']}")
|
||
return indicators
|
||
|
||
# ============================================================================
|
||
# XTZH AI定价引擎(核心计算)
|
||
# ============================================================================
|
||
|
||
def calculate_fx_layer(sdr_rates):
|
||
"""
|
||
计算货币层价值(FX Layer)
|
||
1 XTZH 对应的 SDR 价值的货币层部分
|
||
使用SDR货币篮子权重计算
|
||
"""
|
||
# 1 SDR = 1 XTZH(基础锚定)
|
||
# 货币层价值 = SDR在各货币中的加权价值
|
||
# 以USD计价:1 SDR = 1.374 USD
|
||
sdr_in_usd = sdr_rates['USD']
|
||
|
||
# 货币篮子验证:各货币权重之和应接近1
|
||
# USD 43.32% + EUR 29.23% + CNY 10.59% + JPY 7.84% + GBP 8.02% = 99%
|
||
fx_value_usd = sdr_in_usd # 货币层直接等于SDR的USD价值
|
||
|
||
logger.info(f"货币层: 1 SDR = ${sdr_in_usd:.4f} USD")
|
||
return fx_value_usd
|
||
|
||
def calculate_gold_layer(gold_price_usd, sdr_rates):
|
||
"""
|
||
计算黄金层价值(Gold Layer)
|
||
黄金信任锚覆盖率125%,黄金层权重10%(可在5%-20%动态调整)
|
||
|
||
黄金层价值 = 黄金价格 / 标准黄金单位 * 黄金覆盖率
|
||
标准:1 SDR ≈ 0.0005 盎司黄金(历史锚定)
|
||
"""
|
||
sdr_in_usd = sdr_rates['USD']
|
||
|
||
# 黄金层价值:以黄金价格调整SDR价值
|
||
# 黄金覆盖率125%意味着每1 XTZH背后有1.25倍的黄金价值支撑
|
||
# 黄金调整因子 = 当前黄金价格 / 基准黄金价格(2020年基准 $1800/oz)
|
||
gold_base_price = 1800.0 # 2020年基准价格
|
||
gold_adjustment = gold_price_usd / gold_base_price
|
||
|
||
# 黄金层贡献值(以USD计)
|
||
# 黄金层权重10%,覆盖率125%
|
||
gold_coverage_ratio = GOLD_TRUST_ANCHOR_COVERAGE / 10000.0 # 1.25
|
||
gold_layer_value = sdr_in_usd * gold_adjustment * gold_coverage_ratio
|
||
|
||
logger.info(f"黄金层: 价格=${gold_price_usd:.2f}, 调整因子={gold_adjustment:.4f}, 层价值=${gold_layer_value:.4f}")
|
||
return gold_layer_value
|
||
|
||
def calculate_commodity_layer(indicators, sdr_rates):
|
||
"""
|
||
计算商品层价值(Commodity Layer)
|
||
商品层权重50%,包含原油、铜、农产品等大宗商品
|
||
|
||
商品层价值 = SDR价值 * 商品综合调整因子
|
||
"""
|
||
sdr_in_usd = sdr_rates['USD']
|
||
|
||
# 商品调整因子计算
|
||
# WTI原油:基准$75,权重30%
|
||
wti = indicators.get('wti', 75.0)
|
||
wti_base = 75.0
|
||
wti_factor = wti / wti_base
|
||
|
||
# 铜价:基准$4.2/磅,权重25%
|
||
copper = indicators.get('copper', 4.2)
|
||
copper_base = 4.2
|
||
copper_factor = copper / copper_base
|
||
|
||
# DXY美元指数:基准104,反向关系(美元强则商品弱)
|
||
dxy = indicators.get('dxy', 104.0)
|
||
dxy_base = 104.0
|
||
dxy_factor = dxy_base / dxy # 反向
|
||
|
||
# VIX风险调整:基准20,高VIX降低商品层价值
|
||
vix = indicators.get('vix', 20.0)
|
||
vix_base = 20.0
|
||
vix_factor = vix_base / max(vix, 10.0) # 防止除零
|
||
|
||
# 综合商品调整因子(加权平均)
|
||
commodity_adjustment = (
|
||
wti_factor * 0.30 +
|
||
copper_factor * 0.25 +
|
||
dxy_factor * 0.30 +
|
||
vix_factor * 0.15
|
||
)
|
||
|
||
commodity_layer_value = sdr_in_usd * commodity_adjustment
|
||
|
||
logger.info(f"商品层: WTI因子={wti_factor:.4f}, 铜因子={copper_factor:.4f}, DXY因子={dxy_factor:.4f}, VIX因子={vix_factor:.4f}")
|
||
logger.info(f"商品层综合调整={commodity_adjustment:.4f}, 层价值=${commodity_layer_value:.4f}")
|
||
return commodity_layer_value
|
||
|
||
def calculate_dynamic_weights(indicators, gold_price_usd):
|
||
"""
|
||
计算动态权重(AI调整)
|
||
基于市场状态动态调整三层权重
|
||
约束:w_fx + w_au + w_com = 10000(基点)
|
||
黄金层:500 <= w_au <= 2000
|
||
"""
|
||
vix = indicators.get('vix', 20.0)
|
||
dxy = indicators.get('dxy', 104.0)
|
||
|
||
# 风险情绪调整:高VIX时增加黄金权重
|
||
# VIX > 30: 高风险,黄金权重上调
|
||
# VIX < 15: 低风险,黄金权重下调
|
||
if vix > 30:
|
||
w_au = min(W_AU_MAX, W_AU_BASE + int((vix - 30) * 30))
|
||
elif vix < 15:
|
||
w_au = max(W_AU_MIN, W_AU_BASE - int((15 - vix) * 25))
|
||
else:
|
||
w_au = W_AU_BASE
|
||
|
||
# 美元强弱调整:DXY高时货币层权重下调
|
||
if dxy > 110:
|
||
fx_adjustment = -200
|
||
elif dxy < 95:
|
||
fx_adjustment = 200
|
||
else:
|
||
fx_adjustment = int((104 - dxy) * 15)
|
||
|
||
w_fx = W_FX_BASE + fx_adjustment
|
||
w_fx = max(2000, min(6000, w_fx)) # 限制在20%-60%
|
||
|
||
# 商品层补足剩余权重
|
||
w_com = WEIGHT_SUM - w_fx - w_au
|
||
w_com = max(2000, min(7000, w_com)) # 限制在20%-70%
|
||
|
||
# 重新归一化确保总和=10000
|
||
total = w_fx + w_au + w_com
|
||
if total != WEIGHT_SUM:
|
||
diff = WEIGHT_SUM - total
|
||
w_com += diff
|
||
|
||
logger.info(f"动态权重: FX={w_fx/100:.1f}%, AU={w_au/100:.1f}%, COM={w_com/100:.1f}%")
|
||
return w_fx, w_au, w_com
|
||
|
||
def calculate_xtzh_mint_price():
|
||
"""
|
||
计算XTZH当日铸造价格(核心函数)
|
||
|
||
价格公式:
|
||
P_XTZH = (w_fx * V_fx + w_au * V_au + w_com * V_com) / WEIGHT_SUM
|
||
|
||
其中:
|
||
- V_fx: 货币层价值(SDR USD等价)
|
||
- V_au: 黄金层价值(黄金调整后)
|
||
- V_com: 商品层价值(大宗商品调整后)
|
||
- w_fx, w_au, w_com: 动态权重(基点,总和10000)
|
||
"""
|
||
logger.info("=== 开始计算XTZH当日铸造价格 ===")
|
||
|
||
# 1. 获取实时数据
|
||
sdr_rates = fetch_sdr_rates()
|
||
gold_price_usd, gold_source = fetch_gold_price_usd()
|
||
indicators = fetch_market_indicators()
|
||
|
||
# 2. 计算三层价值
|
||
v_fx = calculate_fx_layer(sdr_rates)
|
||
v_au = calculate_gold_layer(gold_price_usd, sdr_rates)
|
||
v_com = calculate_commodity_layer(indicators, sdr_rates)
|
||
|
||
# 3. 计算动态权重
|
||
w_fx, w_au, w_com = calculate_dynamic_weights(indicators, gold_price_usd)
|
||
|
||
# 4. 加权计算最终价格(USD)
|
||
price_usd = (w_fx * v_fx + w_au * v_au + w_com * v_com) / WEIGHT_SUM
|
||
|
||
# 5. 计算各货币价格
|
||
sdr_in_usd = sdr_rates['USD']
|
||
price_sdr = price_usd / sdr_in_usd # XTZH/SDR
|
||
price_cny = price_usd / sdr_rates['USD'] * sdr_rates['CNY'] # 通过SDR换算
|
||
price_eur = price_usd / sdr_rates['USD'] * sdr_rates['EUR']
|
||
|
||
# 6. 计算铸造比例(1 USD = ? XTZH)
|
||
mint_ratio = 1.0 / price_usd # 1 USD可以铸造多少XTZH
|
||
|
||
# 7. 计算黄金储备支撑(每1 XTZH背后的黄金价值)
|
||
gold_backing_per_xtzh = (w_au / WEIGHT_SUM) * v_au * (GOLD_TRUST_ANCHOR_COVERAGE / 10000.0)
|
||
|
||
result = {
|
||
"price_usd": round(price_usd, 6),
|
||
"price_sdr": round(price_sdr, 6),
|
||
"price_cny": round(price_cny, 6),
|
||
"price_eur": round(price_eur, 6),
|
||
"mint_ratio": round(mint_ratio, 6),
|
||
"gold_backing_usd": round(gold_backing_per_xtzh, 6),
|
||
"weights": {
|
||
"fx_layer": round(w_fx / 100, 2),
|
||
"gold_layer": round(w_au / 100, 2),
|
||
"commodity_layer": round(w_com / 100, 2)
|
||
},
|
||
"components": {
|
||
"fx_value_usd": round(v_fx, 6),
|
||
"gold_value_usd": round(v_au, 6),
|
||
"commodity_value_usd": round(v_com, 6)
|
||
},
|
||
"market_data": {
|
||
"gold_price_usd": round(gold_price_usd, 2),
|
||
"gold_source": gold_source,
|
||
"sdr_rates": {
|
||
"usd": round(sdr_rates['USD'], 6),
|
||
"eur": round(sdr_rates['EUR'], 6),
|
||
"gbp": round(sdr_rates['GBP'], 6),
|
||
"jpy": round(sdr_rates['JPY'], 6),
|
||
"cny": round(sdr_rates['CNY'], 6)
|
||
},
|
||
"vix": round(indicators.get('vix', 0), 2),
|
||
"dxy": round(indicators.get('dxy', 0), 3),
|
||
"wti_oil": round(indicators.get('wti', 0), 2),
|
||
"copper": round(indicators.get('copper', 0), 4)
|
||
},
|
||
"model_info": {
|
||
"version": "1.0.0",
|
||
"algorithm": "NAC Constitutional Weighted Pricing",
|
||
"gold_trust_anchor_coverage": "125%",
|
||
"weight_constraint": "w_fx + w_au + w_com = 10000bp",
|
||
"gold_weight_range": "5.00% - 20.00%"
|
||
},
|
||
"timestamp": datetime.now(timezone.utc).isoformat(),
|
||
"next_update": "每5分钟更新一次"
|
||
}
|
||
|
||
logger.info(f"=== XTZH铸造价格计算完成 ===")
|
||
logger.info(f" 价格: ${price_usd:.6f} USD / {price_sdr:.6f} SDR")
|
||
logger.info(f" 权重: FX={w_fx/100:.1f}% AU={w_au/100:.1f}% COM={w_com/100:.1f}%")
|
||
logger.info(f" 黄金支撑: ${gold_backing_per_xtzh:.6f} USD/XTZH")
|
||
|
||
return result
|
||
|
||
# ============================================================================
|
||
# HTTP服务器
|
||
# ============================================================================
|
||
|
||
def get_cached_price():
|
||
"""获取缓存的价格数据"""
|
||
now = time.time()
|
||
if _cache['data'] is None or (now - _cache['timestamp']) > _cache['ttl']:
|
||
logger.info("缓存过期,重新计算XTZH价格...")
|
||
try:
|
||
_cache['data'] = calculate_xtzh_mint_price()
|
||
_cache['timestamp'] = now
|
||
except Exception as e:
|
||
logger.error(f"价格计算失败: {e}")
|
||
if _cache['data'] is None:
|
||
# 返回基础值
|
||
_cache['data'] = {
|
||
"price_usd": 1.374498,
|
||
"price_sdr": 1.0,
|
||
"price_cny": 9.444808,
|
||
"price_eur": 1.166327,
|
||
"error": str(e),
|
||
"timestamp": datetime.now(timezone.utc).isoformat()
|
||
}
|
||
return _cache['data']
|
||
|
||
class XTZHPricingHandler(BaseHTTPRequestHandler):
|
||
def log_message(self, format, *args):
|
||
logger.info(f"HTTP {args[0]} {args[1]} {args[2]}")
|
||
|
||
def send_cors_headers(self):
|
||
self.send_header('Access-Control-Allow-Origin', '*')
|
||
self.send_header('Access-Control-Allow-Methods', 'GET, OPTIONS')
|
||
self.send_header('Access-Control-Allow-Headers', 'Content-Type, Authorization')
|
||
self.send_header('Content-Type', 'application/json; charset=utf-8')
|
||
|
||
def do_OPTIONS(self):
|
||
self.send_response(200)
|
||
self.send_cors_headers()
|
||
self.end_headers()
|
||
|
||
def do_GET(self):
|
||
path = self.path.split('?')[0]
|
||
|
||
if path == '/health':
|
||
self.send_response(200)
|
||
self.send_cors_headers()
|
||
self.end_headers()
|
||
resp = json.dumps({"status": "ok", "service": "xtzh-pricing", "version": "1.0.0"})
|
||
self.wfile.write(resp.encode())
|
||
|
||
elif path in ['/price', '/api/xtzh/price', '/api/v1/xtzh/price']:
|
||
data = get_cached_price()
|
||
self.send_response(200)
|
||
self.send_cors_headers()
|
||
self.end_headers()
|
||
resp = json.dumps({"success": True, "data": data}, ensure_ascii=False)
|
||
self.wfile.write(resp.encode('utf-8'))
|
||
|
||
elif path in ['/price/simple', '/api/xtzh/price/simple']:
|
||
data = get_cached_price()
|
||
simple = {
|
||
"price_usd": data.get("price_usd"),
|
||
"price_sdr": data.get("price_sdr"),
|
||
"price_cny": data.get("price_cny"),
|
||
"price_eur": data.get("price_eur"),
|
||
"gold_price_usd": data.get("market_data", {}).get("gold_price_usd"),
|
||
"timestamp": data.get("timestamp")
|
||
}
|
||
self.send_response(200)
|
||
self.send_cors_headers()
|
||
self.end_headers()
|
||
resp = json.dumps({"success": True, "data": simple}, ensure_ascii=False)
|
||
self.wfile.write(resp.encode('utf-8'))
|
||
|
||
elif path in ['/price/refresh', '/api/xtzh/price/refresh']:
|
||
# 强制刷新缓存
|
||
_cache['timestamp'] = 0
|
||
data = get_cached_price()
|
||
self.send_response(200)
|
||
self.send_cors_headers()
|
||
self.end_headers()
|
||
resp = json.dumps({"success": True, "data": data, "refreshed": True}, ensure_ascii=False)
|
||
self.wfile.write(resp.encode('utf-8'))
|
||
|
||
else:
|
||
self.send_response(404)
|
||
self.send_cors_headers()
|
||
self.end_headers()
|
||
resp = json.dumps({"error": "Not found", "endpoints": ["/health", "/price", "/price/simple", "/price/refresh"]})
|
||
self.wfile.write(resp.encode())
|
||
|
||
def run_server(port=8709):
|
||
"""启动HTTP服务器"""
|
||
server = HTTPServer(('0.0.0.0', port), XTZHPricingHandler)
|
||
logger.info(f"XTZH AI定价服务启动,端口: {port}")
|
||
logger.info(f"API端点: http://0.0.0.0:{port}/price")
|
||
|
||
# 启动时预热缓存
|
||
logger.info("预热缓存,首次计算XTZH价格...")
|
||
get_cached_price()
|
||
|
||
server.serve_forever()
|
||
|
||
if __name__ == '__main__':
|
||
run_server(8709)
|