fix: currency+PnL+is_hk_stock+gate+zone+total_pnl
This commit is contained in:
+7
-2
@@ -521,8 +521,13 @@ def run_once(round_label=""):
|
|||||||
if get_trigger_zones(d):
|
if get_trigger_zones(d):
|
||||||
check_codes.add(d["code"])
|
check_codes.add(d["code"])
|
||||||
|
|
||||||
# 批量拉取这些股票的价格
|
# 批量拉取这些股票的价格(A股走腾讯 + 港股走东方财富)
|
||||||
prices = fetch_all_prices(list(check_codes))
|
all_codes = list(check_codes)
|
||||||
|
prices = fetch_all_prices(all_codes)
|
||||||
|
hk_codes = [c for c in all_codes if is_hk_stock(str(c))]
|
||||||
|
if hk_codes:
|
||||||
|
hk_prices = fetch_hk_eastmoney(hk_codes)
|
||||||
|
prices.update(hk_prices)
|
||||||
|
|
||||||
for d in active:
|
for d in active:
|
||||||
code = d["code"]
|
code = d["code"]
|
||||||
|
|||||||
@@ -67,20 +67,29 @@ def main():
|
|||||||
currency = 'HKD' if '港币' in price_raw or '港' in r[10] else 'CNY'
|
currency = 'HKD' if '港币' in price_raw or '港' in r[10] else 'CNY'
|
||||||
price_str = price_raw.replace('港币', '').replace('港元', '').replace('港', '').strip()
|
price_str = price_raw.replace('港币', '').replace('港元', '').replace('港', '').strip()
|
||||||
price = float(price_str)
|
price = float(price_str)
|
||||||
cost_price = float(clean_cell(r[5]))
|
cost_price_raw = float(clean_cell(r[5]))
|
||||||
pl = float(clean_cell(r[6])) if r[6].strip() else 0
|
pl = float(clean_cell(r[6])) if r[6].strip() else 0
|
||||||
mkt_val = float(clean_cell(r[11]))
|
mkt_val_raw = float(clean_cell(r[11]))
|
||||||
cost_amount = float(clean_cell(r[15])) if r[15].strip() and r[15].strip() != '--' else 0
|
cost_amount_raw = float(clean_cell(r[15])) if r[15].strip() and r[15].strip() != '--' else 0
|
||||||
rate_str = clean_cell(r[16])
|
rate_str = clean_cell(r[16])
|
||||||
rate = float(rate_str) if rate_str and rate_str != '--' else 0.8664
|
rate = float(rate_str) if rate_str and rate_str != '--' else 0.8664
|
||||||
mv_cny = mkt_val
|
|
||||||
|
# 港股:所有金额必须转为 CNY 再存储(mo_models 设计规范:portfolio 应全部存 CNY)
|
||||||
|
if currency == 'HKD':
|
||||||
|
cost_price = round(cost_price_raw * rate, 2)
|
||||||
|
mv_cny = round(mkt_val_raw * rate, 2)
|
||||||
|
price_cny = round(price * rate, 2)
|
||||||
|
else:
|
||||||
|
cost_price = round(cost_price_raw, 2)
|
||||||
|
mv_cny = mkt_val_raw
|
||||||
|
price_cny = price
|
||||||
total_mv_cny += mv_cny
|
total_mv_cny += mv_cny
|
||||||
|
|
||||||
holdings.append({
|
holdings.append({
|
||||||
'code': code, 'name': name, 'shares': shares,
|
'code': code, 'name': name, 'shares': shares,
|
||||||
'price': price, 'cost_price': round(cost_price, 2),
|
'price': price_cny, 'cost_price': cost_price,
|
||||||
'currency': currency, 'market_val': mkt_val,
|
'currency': 'CNY', 'market_val': mv_cny,
|
||||||
'cost_amount': cost_amount, 'exchange_rate': rate,
|
'cost_amount_raw': cost_amount_raw, 'exchange_rate': rate,
|
||||||
})
|
})
|
||||||
|
|
||||||
if cash <= 0:
|
if cash <= 0:
|
||||||
@@ -105,9 +114,9 @@ def main():
|
|||||||
c.execute('DELETE FROM portfolio_summary')
|
c.execute('DELETE FROM portfolio_summary')
|
||||||
for h in holdings:
|
for h in holdings:
|
||||||
c.execute('''
|
c.execute('''
|
||||||
INSERT INTO holdings (code, name, shares, cost, position_pct, added_at, is_active)
|
INSERT INTO holdings (code, name, shares, cost, currency, position_pct, added_at, is_active)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, 1)
|
VALUES (?, ?, ?, ?, ?, ?, ?, 1)
|
||||||
''', (h['code'], h['name'], h['shares'], h['cost_price'],
|
''', (h['code'], h['name'], h['shares'], h['cost_price'], 'CNY',
|
||||||
round(h['market_val'] / total_assets * 100, 2),
|
round(h['market_val'] / total_assets * 100, 2),
|
||||||
datetime.now().strftime('%Y-%m-%d')))
|
datetime.now().strftime('%Y-%m-%d')))
|
||||||
c.execute('''
|
c.execute('''
|
||||||
|
|||||||
@@ -18,11 +18,9 @@ from datetime import datetime
|
|||||||
import technical_analysis as ta
|
import technical_analysis as ta
|
||||||
import multi_timeframe as mtf
|
import multi_timeframe as mtf
|
||||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
|
from mo_models import is_hk_stock, to_cny, get_hk_rate
|
||||||
|
|
||||||
|
# is_hk_stock 已从 mo_models 导入,不再在此复写。
|
||||||
def is_hk_stock(code):
|
|
||||||
"""判断是否港股(港股代码5位,A股6位带前导零)"""
|
|
||||||
return len(str(code)) <= 5
|
|
||||||
|
|
||||||
|
|
||||||
def calc_atr(code, period=14):
|
def calc_atr(code, period=14):
|
||||||
|
|||||||
+20
-11
@@ -18,6 +18,7 @@ from datetime import datetime
|
|||||||
import technical_analysis as ta
|
import technical_analysis as ta
|
||||||
import multi_timeframe as mtf
|
import multi_timeframe as mtf
|
||||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
|
from mo_models import is_hk_stock, to_cny, get_hk_rate
|
||||||
from strategy_tree import detect_scenario
|
from strategy_tree import detect_scenario
|
||||||
|
|
||||||
# ─── 策略准入门禁 — 硬性质量红线 ───────────────────────────────
|
# ─── 策略准入门禁 — 硬性质量红线 ───────────────────────────────
|
||||||
@@ -84,10 +85,10 @@ STRATEGY_QUALITY_GATES = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "GATE_CURRENCY_SET",
|
"id": "GATE_CURRENCY_SET",
|
||||||
"desc": "港股必须标 currency=HKD",
|
"desc": "港股决策金额应为人民币标价(currency=CNY)",
|
||||||
"check": lambda d: not (len(str(d.get("code",""))) == 5 and str(d.get("code",""))[0] in "01") or d.get("currency") == "HKD",
|
"check": lambda d: not is_hk_stock(d.get("code","")) or d.get("currency") in ("CNY", None),
|
||||||
"severity": "HIGH",
|
"severity": "MEDIUM",
|
||||||
"fix": "设置 d['currency']='HKD'"
|
"fix": "设置 d['currency']='CNY'(系统统一存CNY)"
|
||||||
},
|
},
|
||||||
# --- 第4条 CRITICAL 红线:9维交叉验证 (2026-07-02 Dad要求) ---
|
# --- 第4条 CRITICAL 红线:9维交叉验证 (2026-07-02 Dad要求) ---
|
||||||
# 策略不能只有价格数字,必须有证据经过了多维分析:
|
# 策略不能只有价格数字,必须有证据经过了多维分析:
|
||||||
@@ -352,9 +353,7 @@ def enforce_strategy_quality(code, name, result):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_hk_stock(code):
|
# is_hk_stock 已从 mo_models 导入(见文件头部 import),不再在此复写。
|
||||||
"""判断是否港股(港股代码5位,A股6位带前导零)"""
|
|
||||||
return len(str(code)) <= 5
|
|
||||||
|
|
||||||
|
|
||||||
def calc_atr(code, period=14):
|
def calc_atr(code, period=14):
|
||||||
@@ -823,8 +822,12 @@ def batch_fetch_prices(codes):
|
|||||||
return float(fields[i]) if fields[i].strip() else 0.0
|
return float(fields[i]) if fields[i].strip() else 0.0
|
||||||
except:
|
except:
|
||||||
return 0.0
|
return 0.0
|
||||||
|
price_raw = f(3)
|
||||||
|
# 港股:腾讯 API 返回 HKD,需转 CNY
|
||||||
|
if is_hk_stock(orig_code) and price_raw > 0:
|
||||||
|
price_raw = to_cny(price_raw)
|
||||||
all_results[orig_code] = {
|
all_results[orig_code] = {
|
||||||
"price": f(3), "close": f(4), "high": f(33), "low": f(34),
|
"price": price_raw, "close": f(4), "high": f(33), "low": f(34),
|
||||||
"code": orig_code,
|
"code": orig_code,
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -2294,8 +2297,9 @@ def regenerate_all(stdout=True):
|
|||||||
new_entry = {
|
new_entry = {
|
||||||
"code": code, "name": name, "price": price,
|
"code": code, "name": name, "price": price,
|
||||||
"cost": old_entry.get("cost", cost) if old_entry else cost, # 优先保留旧成本(holding.xls权威)
|
"cost": old_entry.get("cost", cost) if old_entry else cost, # 优先保留旧成本(holding.xls权威)
|
||||||
"shares": old_entry.get("shares", 0), # 保留持仓股数
|
"shares": shares, # 当前实际持仓股数(不继承旧决策的可能为0的值)
|
||||||
"avg_price": old_entry.get("avg_price", 0), # 保留持仓均价
|
"avg_price": old_entry.get("avg_price", 0), # 保留持仓均价
|
||||||
|
"currency": "CNY", # 系统统一存人民币标价(mo_models规范)
|
||||||
"action": result["action"],
|
"action": result["action"],
|
||||||
"stop_loss": result.get("stop_loss"),
|
"stop_loss": result.get("stop_loss"),
|
||||||
"entry_low": result["entry_low"],
|
"entry_low": result["entry_low"],
|
||||||
@@ -2499,17 +2503,22 @@ def regenerate_all(stdout=True):
|
|||||||
# 重新计算 portfolio 汇总(保留已存在的 cash,用最新价格算市值)
|
# 重新计算 portfolio 汇总(保留已存在的 cash,用最新价格算市值)
|
||||||
try:
|
try:
|
||||||
total_mv = 0.0
|
total_mv = 0.0
|
||||||
|
total_cost = 0.0
|
||||||
for h in existing_pf.get('holdings', []):
|
for h in existing_pf.get('holdings', []):
|
||||||
p = h.get('price') or 0
|
p = h.get('price') or 0
|
||||||
s = h.get('shares') or 0
|
s = h.get('shares') or 0
|
||||||
|
c = h.get('cost') or 0
|
||||||
total_mv += p * s
|
total_mv += p * s
|
||||||
|
total_cost += c * s
|
||||||
if p and s and total_mv > 0:
|
if p and s and total_mv > 0:
|
||||||
h['market_value'] = round(p * s, 2)
|
h['market_value'] = round(p * s, 2)
|
||||||
old_cash = existing_pf.get('cash') or 80476 # fallback 6/23 backup
|
old_cash = existing_pf.get('cash') or 80476 # fallback 6/23 backup
|
||||||
|
frozen_cash = existing_pf.get('frozen_cash') or 0
|
||||||
existing_pf['cash'] = old_cash
|
existing_pf['cash'] = old_cash
|
||||||
existing_pf['total_mv'] = round(total_mv, 2)
|
existing_pf['total_mv'] = round(total_mv, 2)
|
||||||
existing_pf['total_assets'] = round(total_mv + old_cash, 2)
|
existing_pf['total_assets'] = round(total_mv + old_cash + frozen_cash, 2)
|
||||||
existing_pf['position_pct'] = round(total_mv / (total_mv + old_cash) * 100, 2) if (total_mv + old_cash) > 0 else 0
|
existing_pf['total_pnl'] = round(total_mv - total_cost, 2)
|
||||||
|
existing_pf['position_pct'] = round(total_mv / (total_mv + old_cash + frozen_cash) * 100, 2) if (total_mv + old_cash + frozen_cash) > 0 else 0
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" [汇总计算失败] {e}", flush=True)
|
print(f" [汇总计算失败] {e}", flush=True)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user