fix: price_monitor HKD→CNY + Eastmoney 2s timeout + None-safe + summary fix
This commit is contained in:
+20
-8
@@ -131,21 +131,26 @@ def fetch_hk_eastmoney(codes):
|
|||||||
|
|
||||||
results = {}
|
results = {}
|
||||||
|
|
||||||
# 主通道:东方财富实时行情(逐股查询,港股仅~10只,<1秒完成。该API不支持批量)
|
# 主通道:东方财富实时行情(逐股查询,2秒超时。连续失败则立即切腾讯兜底)
|
||||||
|
consecutive_fail = 0
|
||||||
for code in hk_codes:
|
for code in hk_codes:
|
||||||
try:
|
try:
|
||||||
|
if consecutive_fail >= 3:
|
||||||
|
break # 前3只都失败,东财不可用,直接切腾讯
|
||||||
url = (f"https://push2.eastmoney.com/api/qt/stock/get"
|
url = (f"https://push2.eastmoney.com/api/qt/stock/get"
|
||||||
f"?secid=116.{code}"
|
f"?secid=116.{code}"
|
||||||
f"&fields=f43,f170,f60,f57,f58"
|
f"&fields=f43,f170,f60,f57,f58"
|
||||||
f"&fltt=2")
|
f"&fltt=2")
|
||||||
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||||
with urllib.request.urlopen(req, timeout=5) as r:
|
with urllib.request.urlopen(req, timeout=2) as r:
|
||||||
resp = json.loads(r.read().decode("utf-8"))
|
resp = json.loads(r.read().decode("utf-8"))
|
||||||
|
|
||||||
if resp.get("rc") != 0:
|
if resp.get("rc") != 0:
|
||||||
|
consecutive_fail += 1
|
||||||
continue
|
continue
|
||||||
item = resp.get("data", {})
|
item = resp.get("data", {})
|
||||||
if not item:
|
if not item:
|
||||||
|
consecutive_fail += 1
|
||||||
continue
|
continue
|
||||||
price = float(item.get("f43", 0)) if item.get("f43") else 0
|
price = float(item.get("f43", 0)) if item.get("f43") else 0
|
||||||
prev_close = float(item.get("f60", 0)) if item.get("f60") else 0
|
prev_close = float(item.get("f60", 0)) if item.get("f60") else 0
|
||||||
@@ -153,10 +158,12 @@ def fetch_hk_eastmoney(codes):
|
|||||||
change_pct = str(item.get("f170", "0"))
|
change_pct = str(item.get("f170", "0"))
|
||||||
if price > 0:
|
if price > 0:
|
||||||
results[code] = (price, change, change_pct)
|
results[code] = (price, change, change_pct)
|
||||||
time.sleep(0.1) # 防止触发东财反爬(逐股查询,不支持批量)
|
consecutive_fail = 0 # 成功后重置计数
|
||||||
|
time.sleep(0.1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
consecutive_fail += 1
|
||||||
|
if consecutive_fail <= 2:
|
||||||
print(f"⚠️ 东方财富 {code} 拉取失败: {e}", file=sys.stderr)
|
print(f"⚠️ 东方财富 {code} 拉取失败: {e}", file=sys.stderr)
|
||||||
continue
|
|
||||||
|
|
||||||
# Fallback: 腾讯 qt.gtimg.cn(15分钟延迟)
|
# Fallback: 腾讯 qt.gtimg.cn(15分钟延迟)
|
||||||
missing = [c for c in hk_codes if c not in results]
|
missing = [c for c in hk_codes if c not in results]
|
||||||
@@ -248,12 +255,14 @@ def refresh_data_prices():
|
|||||||
if s['code'] in prices:
|
if s['code'] in prices:
|
||||||
price, _, change_pct = prices[s['code']]
|
price, _, change_pct = prices[s['code']]
|
||||||
if price > 0:
|
if price > 0:
|
||||||
# 港股API返回HKD,直接存HKD原值。calc_total_mv统一做CNY折算
|
# 港股:API返回HKD,需转CNY。系统统一存CNY标价
|
||||||
old = s.get('price', 0)
|
if is_hk_stock(s['code']):
|
||||||
|
price = round(price * HK_RATE, 2)
|
||||||
|
old = s.get('price') or 0
|
||||||
if abs(old - price) > 0.001:
|
if abs(old - price) > 0.001:
|
||||||
s['price'] = round(price, 2)
|
s['price'] = round(price, 2)
|
||||||
s['change_pct'] = float(change_pct) if change_pct else 0
|
s['change_pct'] = float(change_pct) if change_pct else 0
|
||||||
s['currency'] = 'HKD' if is_hk_stock(s['code']) else 'CNY'
|
s['currency'] = 'CNY'
|
||||||
updated += 1
|
updated += 1
|
||||||
changed = True
|
changed = True
|
||||||
if changed:
|
if changed:
|
||||||
@@ -297,7 +306,10 @@ def refresh_data_prices():
|
|||||||
if s['code'] in prices:
|
if s['code'] in prices:
|
||||||
price, _, change_pct = prices[s['code']]
|
price, _, change_pct = prices[s['code']]
|
||||||
if price > 0:
|
if price > 0:
|
||||||
old = s.get('price', 0)
|
# 港股:API返回HKD,需转CNY
|
||||||
|
if is_hk_stock(s['code']):
|
||||||
|
price = round(price * HK_RATE, 2)
|
||||||
|
old = s.get('price') or 0
|
||||||
if abs(old - price) > 0.001:
|
if abs(old - price) > 0.001:
|
||||||
s['price'] = round(price, 2)
|
s['price'] = round(price, 2)
|
||||||
s['change_pct'] = float(change_pct) if change_pct else 0
|
s['change_pct'] = float(change_pct) if change_pct else 0
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"""Fix portfolio_summary directly from holdings table"""
|
||||||
|
import sqlite3, sys
|
||||||
|
sys.path.insert(0, '/home/hmo/MoFin')
|
||||||
|
from mo_models import calc_total_mv, calc_total_assets, calc_position_pct
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||||
|
|
||||||
|
# Get holdings
|
||||||
|
rows = db.execute("SELECT code, name, shares, cost, price, market_value, change_pct, currency, position_pct FROM holdings WHERE is_active=1").fetchall()
|
||||||
|
holdings = [dict(zip(['code','name','shares','cost','price','market_value','change_pct','currency','position_pct'], r)) for r in rows]
|
||||||
|
|
||||||
|
# Get cash/summary
|
||||||
|
sr = db.execute("SELECT cash, frozen_cash FROM portfolio_summary WHERE id=1").fetchone()
|
||||||
|
cash = sr[0] or 0
|
||||||
|
frozen = sr[1] or 0
|
||||||
|
|
||||||
|
pf = {'holdings': holdings, 'cash': cash, 'frozen_cash': frozen}
|
||||||
|
mv = calc_total_mv(holdings)
|
||||||
|
ta = calc_total_assets(pf)
|
||||||
|
pp = calc_position_pct(pf)
|
||||||
|
pnl = sum((h['price'] or 0) * (h['shares'] or 0) - (h['cost'] or 0) * (h['shares'] or 0) for h in holdings)
|
||||||
|
|
||||||
|
print(f"mv={mv} ta={ta} pnl={pnl} pp={pp}%")
|
||||||
|
|
||||||
|
db.execute("UPDATE portfolio_summary SET total_mv=?, total_assets=?, total_pnl=?, position_pct=?, updated_at=? WHERE id=1",
|
||||||
|
(mv, ta, pnl, pp, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
|
||||||
|
db.commit()
|
||||||
|
print("portfolio_summary updated")
|
||||||
|
db.close()
|
||||||
Reference in New Issue
Block a user