migrate: last 4 JSON files — live_prices, market, mtf_cache, capital_flow → DB
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,35 @@
|
|||||||
|
import sys; sys.path.insert(0, '/home/hmo/MoFin')
|
||||||
|
from mo_data import *
|
||||||
|
|
||||||
|
# Portfolio
|
||||||
|
pf = read_portfolio()
|
||||||
|
ta = pf.get('total_assets',0)
|
||||||
|
ca = pf.get('cash',0)
|
||||||
|
pp = pf.get('position_pct',0)
|
||||||
|
print(f"总资产: {ta:.0f} 现金: {ca:.0f} 仓位: {pp:.1f}%")
|
||||||
|
for h in pf.get('holdings', []):
|
||||||
|
c = h.get('currency','CNY')
|
||||||
|
price = h['price']
|
||||||
|
cost = h['cost']
|
||||||
|
profit_pct = (price/cost - 1)*100 if cost and cost else 0
|
||||||
|
ps = f"{price:.2f}{' HKD' if c=='HKD' else ''}"
|
||||||
|
pp_h = h.get('position_pct')
|
||||||
|
if pp_h is None: pp_h = 0
|
||||||
|
print(f" {h['code']} {h['name']} 价{ps} 仓{pp_h:.1f}% 盈{profit_pct:.1f}%")
|
||||||
|
|
||||||
|
# Watchlist
|
||||||
|
print()
|
||||||
|
wl = read_watchlist()
|
||||||
|
for s in wl.get('stocks',[]):
|
||||||
|
try:
|
||||||
|
c = s.get('currency','CNY')
|
||||||
|
price = s.get('price')
|
||||||
|
if price is None: price = 0
|
||||||
|
ps = f"{price:.2f}{' HKD' if c=='HKD' else ''}"
|
||||||
|
el = s.get('entry_low')
|
||||||
|
eh = s.get('entry_high')
|
||||||
|
enl = f"{el:.2f}" if el is not None else '?'
|
||||||
|
enh = f"{eh:.2f}" if eh is not None else '?'
|
||||||
|
print(f" 自选 {s['code']} {s.get('name','')} 价{ps} 入{enl}~{enh}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ERROR {s.get('code','?')}: {e}")
|
||||||
+194
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"last_updated": "2026-07-03 11:32",
|
"last_updated": "2026-07-03 13:42",
|
||||||
"total_candidates": 5,
|
"total_candidates": 11,
|
||||||
"sectors_analyzed_today": [
|
"sectors_analyzed_today": [
|
||||||
"半导体",
|
"半导体",
|
||||||
"金属新材料",
|
"金属新材料",
|
||||||
@@ -503,6 +503,198 @@
|
|||||||
"drop_reason": null,
|
"drop_reason": null,
|
||||||
"trend_warning": false,
|
"trend_warning": false,
|
||||||
"trend_note": ""
|
"trend_note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "300124",
|
||||||
|
"name": "汇川技术",
|
||||||
|
"sector": "自动化设备",
|
||||||
|
"xiaoguo_score": 8.5,
|
||||||
|
"xiaoguo_reason": "工控龙头,PLC/伺服/机器人核心部件全覆盖,受益设备更新与新能源自动化需求,业绩确定性强,机构持仓集中。",
|
||||||
|
"xiaoguo_strategy": {
|
||||||
|
"entry_range": "58.00-62.00元",
|
||||||
|
"stop_loss": "54.00元",
|
||||||
|
"target": "72.00-78.00元"
|
||||||
|
},
|
||||||
|
"verified_price": 72.94,
|
||||||
|
"verified_change": 6.64,
|
||||||
|
"added_at": "2026-07-03 13:37",
|
||||||
|
"last_updated": "2026-07-03 13:37",
|
||||||
|
"num_observations": 1,
|
||||||
|
"score_history": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-03 13:37",
|
||||||
|
"score": 8.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zhiwei_star": null,
|
||||||
|
"zhiwei_reviewed": false,
|
||||||
|
"zhiwei_reviewed_at": null,
|
||||||
|
"promoted": false,
|
||||||
|
"promoted_at": null,
|
||||||
|
"dropped": false,
|
||||||
|
"drop_reason": null,
|
||||||
|
"trend_warning": false,
|
||||||
|
"trend_note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "002747",
|
||||||
|
"name": "埃斯顿",
|
||||||
|
"sector": "自动化设备",
|
||||||
|
"xiaoguo_score": 7.5,
|
||||||
|
"xiaoguo_reason": "国产工业机器人第一梯队,核心零部件自研率高,政策扶持明确,但短期毛利率承压,适合波段操作。",
|
||||||
|
"xiaoguo_strategy": {
|
||||||
|
"entry_range": "18.00-20.00元",
|
||||||
|
"stop_loss": "16.50元",
|
||||||
|
"target": "24.00-26.00元"
|
||||||
|
},
|
||||||
|
"verified_price": 44.77,
|
||||||
|
"verified_change": 10.0,
|
||||||
|
"added_at": "2026-07-03 13:37",
|
||||||
|
"last_updated": "2026-07-03 13:37",
|
||||||
|
"num_observations": 1,
|
||||||
|
"score_history": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-03 13:37",
|
||||||
|
"score": 7.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zhiwei_star": null,
|
||||||
|
"zhiwei_reviewed": false,
|
||||||
|
"zhiwei_reviewed_at": null,
|
||||||
|
"promoted": false,
|
||||||
|
"promoted_at": null,
|
||||||
|
"dropped": false,
|
||||||
|
"drop_reason": null,
|
||||||
|
"trend_warning": false,
|
||||||
|
"trend_note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "300607",
|
||||||
|
"name": "拓斯达",
|
||||||
|
"sector": "自动化设备",
|
||||||
|
"xiaoguo_score": 7.0,
|
||||||
|
"xiaoguo_reason": "自动化产线集成+机器人双轮驱动,市值适中弹性大,AI视觉与协作机器人布局进展快,但业绩波动性较高。",
|
||||||
|
"xiaoguo_strategy": {
|
||||||
|
"entry_range": "12.00-13.50元",
|
||||||
|
"stop_loss": "11.00元",
|
||||||
|
"target": "16.00-18.00元"
|
||||||
|
},
|
||||||
|
"verified_price": 52.92,
|
||||||
|
"verified_change": 13.46,
|
||||||
|
"added_at": "2026-07-03 13:37",
|
||||||
|
"last_updated": "2026-07-03 13:37",
|
||||||
|
"num_observations": 1,
|
||||||
|
"score_history": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-03 13:37",
|
||||||
|
"score": 7.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zhiwei_star": null,
|
||||||
|
"zhiwei_reviewed": false,
|
||||||
|
"zhiwei_reviewed_at": null,
|
||||||
|
"promoted": false,
|
||||||
|
"promoted_at": null,
|
||||||
|
"dropped": false,
|
||||||
|
"drop_reason": null,
|
||||||
|
"trend_warning": false,
|
||||||
|
"trend_note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "601689",
|
||||||
|
"name": "拓普集团",
|
||||||
|
"sector": "汽车零部件",
|
||||||
|
"xiaoguo_score": 8.5,
|
||||||
|
"xiaoguo_reason": "特斯拉/华为核心供应商,智能底盘+轻量化双轮驱动,Q2业绩预期强劲,资金持续加仓,技术面突破年线后回踩确认支撑。",
|
||||||
|
"xiaoguo_strategy": {
|
||||||
|
"entry_range": "58.5-60.2",
|
||||||
|
"stop_loss": "56.0",
|
||||||
|
"target": "68.0-72.0"
|
||||||
|
},
|
||||||
|
"verified_price": 62.43,
|
||||||
|
"verified_change": 10.01,
|
||||||
|
"added_at": "2026-07-03 13:40",
|
||||||
|
"last_updated": "2026-07-03 13:40",
|
||||||
|
"num_observations": 1,
|
||||||
|
"score_history": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-03 13:40",
|
||||||
|
"score": 8.5
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zhiwei_star": null,
|
||||||
|
"zhiwei_reviewed": false,
|
||||||
|
"zhiwei_reviewed_at": null,
|
||||||
|
"promoted": false,
|
||||||
|
"promoted_at": null,
|
||||||
|
"dropped": false,
|
||||||
|
"drop_reason": null,
|
||||||
|
"trend_warning": false,
|
||||||
|
"trend_note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "002126",
|
||||||
|
"name": "银轮股份",
|
||||||
|
"sector": "汽车零部件",
|
||||||
|
"xiaoguo_score": 8.0,
|
||||||
|
"xiaoguo_reason": "热管理龙头,绑定比亚迪/理想/海外车企,订单饱满,估值合理,趋势稳健,机构持仓集中,适合中线波段。",
|
||||||
|
"xiaoguo_strategy": {
|
||||||
|
"entry_range": "24.8-25.5",
|
||||||
|
"stop_loss": "23.5",
|
||||||
|
"target": "29.0-31.0"
|
||||||
|
},
|
||||||
|
"verified_price": 53.39,
|
||||||
|
"verified_change": 5.76,
|
||||||
|
"added_at": "2026-07-03 13:40",
|
||||||
|
"last_updated": "2026-07-03 13:40",
|
||||||
|
"num_observations": 1,
|
||||||
|
"score_history": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-03 13:40",
|
||||||
|
"score": 8.0
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zhiwei_star": null,
|
||||||
|
"zhiwei_reviewed": false,
|
||||||
|
"zhiwei_reviewed_at": null,
|
||||||
|
"promoted": false,
|
||||||
|
"promoted_at": null,
|
||||||
|
"dropped": false,
|
||||||
|
"drop_reason": null,
|
||||||
|
"trend_warning": false,
|
||||||
|
"trend_note": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "603596",
|
||||||
|
"name": "伯特利",
|
||||||
|
"sector": "汽车零部件",
|
||||||
|
"xiaoguo_score": 8.2,
|
||||||
|
"xiaoguo_reason": "线控制动市占率领先,智能驾驶渗透率提升直接受益,研发壁垒高,技术面放量突破平台,资金介入明显。",
|
||||||
|
"xiaoguo_strategy": {
|
||||||
|
"entry_range": "42.0-43.5",
|
||||||
|
"stop_loss": "40.0",
|
||||||
|
"target": "48.0-52.0"
|
||||||
|
},
|
||||||
|
"verified_price": 27.65,
|
||||||
|
"verified_change": 9.98,
|
||||||
|
"added_at": "2026-07-03 13:40",
|
||||||
|
"last_updated": "2026-07-03 13:40",
|
||||||
|
"num_observations": 1,
|
||||||
|
"score_history": [
|
||||||
|
{
|
||||||
|
"date": "2026-07-03 13:40",
|
||||||
|
"score": 8.2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"zhiwei_star": null,
|
||||||
|
"zhiwei_reviewed": false,
|
||||||
|
"zhiwei_reviewed_at": null,
|
||||||
|
"promoted": false,
|
||||||
|
"promoted_at": null,
|
||||||
|
"dropped": false,
|
||||||
|
"drop_reason": null,
|
||||||
|
"trend_warning": false,
|
||||||
|
"trend_note": ""
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Binary file not shown.
Binary file not shown.
+270
-30
@@ -960,6 +960,14 @@
|
|||||||
"high": 151.69,
|
"high": 151.69,
|
||||||
"low": 141.26,
|
"low": 141.26,
|
||||||
"volume": 94902364.0
|
"volume": 94902364.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 144.89,
|
||||||
|
"close": 143.0,
|
||||||
|
"high": 146.45,
|
||||||
|
"low": 140.4,
|
||||||
|
"volume": 56102965.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -1254,7 +1262,7 @@
|
|||||||
"volume": 147766189.0
|
"volume": 147766189.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051747.536662
|
"updated_at": 1783056811.510267
|
||||||
},
|
},
|
||||||
"688795": {
|
"688795": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -2217,6 +2225,14 @@
|
|||||||
"high": 687.04,
|
"high": 687.04,
|
||||||
"low": 639.4,
|
"low": 639.4,
|
||||||
"volume": 3085878.0
|
"volume": 3085878.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 643.88,
|
||||||
|
"close": 649.03,
|
||||||
|
"high": 664.48,
|
||||||
|
"low": 633.01,
|
||||||
|
"volume": 1568495.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -2479,7 +2495,7 @@
|
|||||||
"volume": 4788252.0
|
"volume": 4788252.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051740.058768
|
"updated_at": 1783056712.9785304
|
||||||
},
|
},
|
||||||
"000657": {
|
"000657": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -3442,6 +3458,14 @@
|
|||||||
"high": 101.5,
|
"high": 101.5,
|
||||||
"low": 90.68,
|
"low": 90.68,
|
||||||
"volume": 1054773.0
|
"volume": 1054773.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 95.0,
|
||||||
|
"close": 92.06,
|
||||||
|
"high": 96.53,
|
||||||
|
"low": 87.88,
|
||||||
|
"volume": 721833.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -3736,7 +3760,7 @@
|
|||||||
"volume": 1051508.0
|
"volume": 1051508.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051278.4453125
|
"updated_at": 1783056502.3469434
|
||||||
},
|
},
|
||||||
"000700": {
|
"000700": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -4699,6 +4723,14 @@
|
|||||||
"high": 16.87,
|
"high": 16.87,
|
||||||
"low": 15.59,
|
"low": 15.59,
|
||||||
"volume": 1137587.0
|
"volume": 1137587.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 16.08,
|
||||||
|
"close": 17.7,
|
||||||
|
"high": 17.88,
|
||||||
|
"low": 16.08,
|
||||||
|
"volume": 1515920.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -4993,7 +5025,7 @@
|
|||||||
"volume": 1265397.0
|
"volume": 1265397.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051627.982171
|
"updated_at": 1783056719.8422928
|
||||||
},
|
},
|
||||||
"000711": {
|
"000711": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -5956,6 +5988,14 @@
|
|||||||
"high": 5.01,
|
"high": 5.01,
|
||||||
"low": 4.87,
|
"low": 4.87,
|
||||||
"volume": 443973.0
|
"volume": 443973.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 5.18,
|
||||||
|
"close": 5.26,
|
||||||
|
"high": 5.26,
|
||||||
|
"low": 5.1,
|
||||||
|
"volume": 1131235.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -6250,7 +6290,7 @@
|
|||||||
"volume": 496248.0
|
"volume": 496248.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051282.8847363
|
"updated_at": 1783056503.0905344
|
||||||
},
|
},
|
||||||
"001309": {
|
"001309": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -7213,6 +7253,14 @@
|
|||||||
"high": 872.83,
|
"high": 872.83,
|
||||||
"low": 806.0,
|
"low": 806.0,
|
||||||
"volume": 160378.0
|
"volume": 160378.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 804.75,
|
||||||
|
"close": 888.1,
|
||||||
|
"high": 892.1,
|
||||||
|
"low": 795.0,
|
||||||
|
"volume": 144171.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -7507,7 +7555,7 @@
|
|||||||
"volume": 216663.0
|
"volume": 216663.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051286.5401492
|
"updated_at": 1783056668.8085573
|
||||||
},
|
},
|
||||||
"002594": {
|
"002594": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -8470,6 +8518,14 @@
|
|||||||
"high": 85.67,
|
"high": 85.67,
|
||||||
"low": 81.9,
|
"low": 81.9,
|
||||||
"volume": 825046.0
|
"volume": 825046.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 83.73,
|
||||||
|
"close": 86.66,
|
||||||
|
"high": 87.28,
|
||||||
|
"low": 83.6,
|
||||||
|
"volume": 477402.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -8764,7 +8820,7 @@
|
|||||||
"volume": 934285.0
|
"volume": 934285.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051286.9981902
|
"updated_at": 1783056675.2141986
|
||||||
},
|
},
|
||||||
"00700": {
|
"00700": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -9727,6 +9783,14 @@
|
|||||||
"high": 447.0,
|
"high": 447.0,
|
||||||
"low": 429.4,
|
"low": 429.4,
|
||||||
"volume": 40905100.0
|
"volume": 40905100.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 433.0,
|
||||||
|
"close": 432.6,
|
||||||
|
"high": 445.8,
|
||||||
|
"low": 432.4,
|
||||||
|
"volume": 15135668.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -10029,7 +10093,7 @@
|
|||||||
"volume": 13032847.0
|
"volume": 13032847.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051633.4890287
|
"updated_at": 1783056724.989126
|
||||||
},
|
},
|
||||||
"00968": {
|
"00968": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -12257,6 +12321,14 @@
|
|||||||
"high": 84.2,
|
"high": 84.2,
|
||||||
"low": 78.55,
|
"low": 78.55,
|
||||||
"volume": 178219425.0
|
"volume": 178219425.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 80.0,
|
||||||
|
"close": 78.95,
|
||||||
|
"high": 81.45,
|
||||||
|
"low": 77.35,
|
||||||
|
"volume": 71251123.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -12559,7 +12631,7 @@
|
|||||||
"volume": 60114819.0
|
"volume": 60114819.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051638.5061805
|
"updated_at": 1783056730.0097694
|
||||||
},
|
},
|
||||||
"01070": {
|
"01070": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -14787,6 +14859,14 @@
|
|||||||
"high": 40.4,
|
"high": 40.4,
|
||||||
"low": 39.16,
|
"low": 39.16,
|
||||||
"volume": 16452660.0
|
"volume": 16452660.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 39.7,
|
||||||
|
"close": 39.9,
|
||||||
|
"high": 40.5,
|
||||||
|
"low": 39.62,
|
||||||
|
"volume": 6092471.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -15089,7 +15169,7 @@
|
|||||||
"volume": 2870057.0
|
"volume": 2870057.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051643.521601
|
"updated_at": 1783056735.0304337
|
||||||
},
|
},
|
||||||
"01211": {
|
"01211": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -16052,6 +16132,14 @@
|
|||||||
"high": 79.6,
|
"high": 79.6,
|
||||||
"low": 74.95,
|
"low": 74.95,
|
||||||
"volume": 54549471.0
|
"volume": 54549471.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 81.2,
|
||||||
|
"close": 82.85,
|
||||||
|
"high": 83.35,
|
||||||
|
"low": 80.0,
|
||||||
|
"volume": 25755799.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -16354,7 +16442,7 @@
|
|||||||
"volume": 13286402.0
|
"volume": 13286402.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051648.5394585
|
"updated_at": 1783056740.0491588
|
||||||
},
|
},
|
||||||
"01478": {
|
"01478": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -17317,6 +17405,14 @@
|
|||||||
"high": 7.08,
|
"high": 7.08,
|
||||||
"low": 6.72,
|
"low": 6.72,
|
||||||
"volume": 6257000.0
|
"volume": 6257000.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 6.72,
|
||||||
|
"close": 7.04,
|
||||||
|
"high": 7.1,
|
||||||
|
"low": 6.72,
|
||||||
|
"volume": 2981000.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -17619,7 +17715,7 @@
|
|||||||
"volume": 1618000.0
|
"volume": 1618000.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051653.5516405
|
"updated_at": 1783056745.0709894
|
||||||
},
|
},
|
||||||
"01888": {
|
"01888": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -18582,6 +18678,14 @@
|
|||||||
"high": 90.1,
|
"high": 90.1,
|
||||||
"low": 79.85,
|
"low": 79.85,
|
||||||
"volume": 92637274.0
|
"volume": 92637274.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 81.0,
|
||||||
|
"close": 85.6,
|
||||||
|
"high": 88.9,
|
||||||
|
"low": 80.6,
|
||||||
|
"volume": 57017840.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -18884,7 +18988,7 @@
|
|||||||
"volume": 29941901.0
|
"volume": 29941901.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051658.5684023
|
"updated_at": 1783056750.0898428
|
||||||
},
|
},
|
||||||
"02202": {
|
"02202": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -19847,6 +19951,14 @@
|
|||||||
"high": 2.27,
|
"high": 2.27,
|
||||||
"low": 2.18,
|
"low": 2.18,
|
||||||
"volume": 39512590.0
|
"volume": 39512590.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 2.25,
|
||||||
|
"close": 2.31,
|
||||||
|
"high": 2.35,
|
||||||
|
"low": 2.23,
|
||||||
|
"volume": 18235400.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -20149,7 +20261,7 @@
|
|||||||
"volume": 19786580.0
|
"volume": 19786580.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051663.581557
|
"updated_at": 1783056755.496929
|
||||||
},
|
},
|
||||||
"02318": {
|
"02318": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -27437,6 +27549,14 @@
|
|||||||
"high": 229.2,
|
"high": 229.2,
|
||||||
"low": 198.0,
|
"low": 198.0,
|
||||||
"volume": 40078361.0
|
"volume": 40078361.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 191.2,
|
||||||
|
"close": 207.2,
|
||||||
|
"high": 210.6,
|
||||||
|
"low": 190.0,
|
||||||
|
"volume": 14255822.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -27739,7 +27859,7 @@
|
|||||||
"volume": 15066251.0
|
"volume": 15066251.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051668.878092
|
"updated_at": 1783056760.5189984
|
||||||
},
|
},
|
||||||
"09868": {
|
"09868": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -31232,6 +31352,14 @@
|
|||||||
"high": 14.6,
|
"high": 14.6,
|
||||||
"low": 14.1,
|
"low": 14.1,
|
||||||
"volume": 155998.0
|
"volume": 155998.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 14.18,
|
||||||
|
"close": 14.4,
|
||||||
|
"high": 14.53,
|
||||||
|
"low": 14.16,
|
||||||
|
"volume": 102393.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -31526,7 +31654,7 @@
|
|||||||
"volume": 230937.0
|
"volume": 230937.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051674.5847087
|
"updated_at": 1783056765.5414958
|
||||||
},
|
},
|
||||||
"300124": {
|
"300124": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -32489,6 +32617,14 @@
|
|||||||
"high": 71.79,
|
"high": 71.79,
|
||||||
"low": 67.31,
|
"low": 67.31,
|
||||||
"volume": 703358.0
|
"volume": 703358.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 67.5,
|
||||||
|
"close": 74.14,
|
||||||
|
"high": 74.23,
|
||||||
|
"low": 67.4,
|
||||||
|
"volume": 691416.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -32783,7 +32919,7 @@
|
|||||||
"volume": 722493.0
|
"volume": 722493.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051344.573262
|
"updated_at": 1783056692.3502543
|
||||||
},
|
},
|
||||||
"300308": {
|
"300308": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -33746,6 +33882,14 @@
|
|||||||
"high": 1198.0,
|
"high": 1198.0,
|
||||||
"low": 1127.4,
|
"low": 1127.4,
|
||||||
"volume": 317620.0
|
"volume": 317620.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 1130.0,
|
||||||
|
"close": 1158.88,
|
||||||
|
"high": 1188.01,
|
||||||
|
"low": 1124.0,
|
||||||
|
"volume": 230014.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -34040,7 +34184,7 @@
|
|||||||
"volume": 389058.0
|
"volume": 389058.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051680.1109805
|
"updated_at": 1783056770.5614996
|
||||||
},
|
},
|
||||||
"300548": {
|
"300548": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -35003,6 +35147,14 @@
|
|||||||
"high": 245.0,
|
"high": 245.0,
|
||||||
"low": 220.0,
|
"low": 220.0,
|
||||||
"volume": 174041.0
|
"volume": 174041.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 220.0,
|
||||||
|
"close": 228.99,
|
||||||
|
"high": 231.55,
|
||||||
|
"low": 218.99,
|
||||||
|
"volume": 102806.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -35297,7 +35449,7 @@
|
|||||||
"volume": 242727.0
|
"volume": 242727.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051685.6375031
|
"updated_at": 1783056775.5768287
|
||||||
},
|
},
|
||||||
"300750": {
|
"300750": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -36260,6 +36412,14 @@
|
|||||||
"high": 390.99,
|
"high": 390.99,
|
||||||
"low": 380.39,
|
"low": 380.39,
|
||||||
"volume": 340754.0
|
"volume": 340754.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 381.96,
|
||||||
|
"close": 381.17,
|
||||||
|
"high": 387.95,
|
||||||
|
"low": 380.55,
|
||||||
|
"volume": 157731.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -36554,7 +36714,7 @@
|
|||||||
"volume": 551212.0
|
"volume": 551212.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051690.893728
|
"updated_at": 1783056780.5942247
|
||||||
},
|
},
|
||||||
"301308": {
|
"301308": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -37517,6 +37677,14 @@
|
|||||||
"high": 636.99,
|
"high": 636.99,
|
||||||
"low": 592.0,
|
"low": 592.0,
|
||||||
"volume": 208005.0
|
"volume": 208005.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 589.0,
|
||||||
|
"close": 634.0,
|
||||||
|
"high": 646.85,
|
||||||
|
"low": 574.1,
|
||||||
|
"volume": 153435.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -37811,7 +37979,7 @@
|
|||||||
"volume": 296230.0
|
"volume": 296230.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051380.6922731
|
"updated_at": 1783056696.8459098
|
||||||
},
|
},
|
||||||
"518880": {
|
"518880": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -38774,6 +38942,14 @@
|
|||||||
"high": 8.484,
|
"high": 8.484,
|
||||||
"low": 8.397,
|
"low": 8.397,
|
||||||
"volume": 5149790.0
|
"volume": 5149790.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 8.71,
|
||||||
|
"close": 8.68,
|
||||||
|
"high": 8.73,
|
||||||
|
"low": 8.68,
|
||||||
|
"volume": 2689807.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -39068,7 +39244,7 @@
|
|||||||
"volume": 3915247.0
|
"volume": 3915247.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051695.915503
|
"updated_at": 1783056785.6140218
|
||||||
},
|
},
|
||||||
"600519": {
|
"600519": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -40031,6 +40207,14 @@
|
|||||||
"high": 1215.52,
|
"high": 1215.52,
|
||||||
"low": 1190.51,
|
"low": 1190.51,
|
||||||
"volume": 50870.0
|
"volume": 50870.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 1205.24,
|
||||||
|
"close": 1189.52,
|
||||||
|
"high": 1210.14,
|
||||||
|
"low": 1188.0,
|
||||||
|
"volume": 23088.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -40325,7 +40509,7 @@
|
|||||||
"volume": 64803.0
|
"volume": 64803.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051561.7579694
|
"updated_at": 1783056699.260756
|
||||||
},
|
},
|
||||||
"600563": {
|
"600563": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -41288,6 +41472,14 @@
|
|||||||
"high": 173.3,
|
"high": 173.3,
|
||||||
"low": 164.3,
|
"low": 164.3,
|
||||||
"volume": 112859.0
|
"volume": 112859.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 160.27,
|
||||||
|
"close": 159.59,
|
||||||
|
"high": 163.68,
|
||||||
|
"low": 153.52,
|
||||||
|
"volume": 75944.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -41582,7 +41774,7 @@
|
|||||||
"volume": 180947.0
|
"volume": 180947.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051700.9725504
|
"updated_at": 1783056791.4344049
|
||||||
},
|
},
|
||||||
"601318": {
|
"601318": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -42545,6 +42737,14 @@
|
|||||||
"high": 50.2,
|
"high": 50.2,
|
||||||
"low": 48.8,
|
"low": 48.8,
|
||||||
"volume": 920130.0
|
"volume": 920130.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 49.5,
|
||||||
|
"close": 48.43,
|
||||||
|
"high": 49.78,
|
||||||
|
"low": 48.41,
|
||||||
|
"volume": 556535.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -42839,7 +43039,7 @@
|
|||||||
"volume": 1746202.0
|
"volume": 1746202.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051575.149798
|
"updated_at": 1783056700.660319
|
||||||
},
|
},
|
||||||
"601899": {
|
"601899": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -43802,6 +44002,14 @@
|
|||||||
"high": 26.96,
|
"high": 26.96,
|
||||||
"low": 25.52,
|
"low": 25.52,
|
||||||
"volume": 5067417.0
|
"volume": 5067417.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 27.5,
|
||||||
|
"close": 27.93,
|
||||||
|
"high": 28.3,
|
||||||
|
"low": 27.41,
|
||||||
|
"volume": 4183914.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -44096,7 +44304,7 @@
|
|||||||
"volume": 4780454.0
|
"volume": 4780454.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051705.9927075
|
"updated_at": 1783056796.4605315
|
||||||
},
|
},
|
||||||
"688411": {
|
"688411": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -45059,6 +45267,14 @@
|
|||||||
"high": 272.99,
|
"high": 272.99,
|
||||||
"low": 254.8,
|
"low": 254.8,
|
||||||
"volume": 6368531.0
|
"volume": 6368531.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 255.8,
|
||||||
|
"close": 255.99,
|
||||||
|
"high": 261.48,
|
||||||
|
"low": 251.0,
|
||||||
|
"volume": 2572727.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -45353,7 +45569,7 @@
|
|||||||
"volume": 13672788.0
|
"volume": 13672788.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051711.208435
|
"updated_at": 1783056801.4764674
|
||||||
},
|
},
|
||||||
"688630": {
|
"688630": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -46316,6 +46532,14 @@
|
|||||||
"high": 499.95,
|
"high": 499.95,
|
||||||
"low": 464.8,
|
"low": 464.8,
|
||||||
"volume": 5841815.0
|
"volume": 5841815.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 467.98,
|
||||||
|
"close": 497.0,
|
||||||
|
"high": 502.0,
|
||||||
|
"low": 444.55,
|
||||||
|
"volume": 4259432.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -46610,7 +46834,7 @@
|
|||||||
"volume": 9660790.0
|
"volume": 9660790.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051587.7339969
|
"updated_at": 1783056704.6600807
|
||||||
},
|
},
|
||||||
"688639": {
|
"688639": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -47573,6 +47797,14 @@
|
|||||||
"high": 17.4,
|
"high": 17.4,
|
||||||
"low": 15.98,
|
"low": 15.98,
|
||||||
"volume": 9065955.0
|
"volume": 9065955.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 17.06,
|
||||||
|
"close": 16.71,
|
||||||
|
"high": 17.2,
|
||||||
|
"low": 16.52,
|
||||||
|
"volume": 4669797.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -47867,7 +48099,7 @@
|
|||||||
"volume": 13996588.0
|
"volume": 13996588.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051733.2346125
|
"updated_at": 1783056806.4868052
|
||||||
},
|
},
|
||||||
"688802": {
|
"688802": {
|
||||||
"daily": [
|
"daily": [
|
||||||
@@ -48830,6 +49062,14 @@
|
|||||||
"high": 784.0,
|
"high": 784.0,
|
||||||
"low": 721.0,
|
"low": 721.0,
|
||||||
"volume": 2036024.0
|
"volume": 2036024.0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2026-07-03",
|
||||||
|
"open": 731.34,
|
||||||
|
"close": 741.27,
|
||||||
|
"high": 757.88,
|
||||||
|
"low": 713.0,
|
||||||
|
"volume": 1323959.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"weekly": [
|
"weekly": [
|
||||||
@@ -49092,6 +49332,6 @@
|
|||||||
"volume": 3202146.0
|
"volume": 3202146.0
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"updated_at": 1783051743.6900556
|
"updated_at": 1783056714.5052435
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+73
-73
@@ -5,9 +5,9 @@
|
|||||||
"name": "中际旭创",
|
"name": "中际旭创",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 1316.53,
|
"cost": 1316.53,
|
||||||
"price": 1157.97,
|
"price": 1145.0,
|
||||||
"market_value": 115797.0,
|
"market_value": 114101.0,
|
||||||
"change_pct": 1.31,
|
"change_pct": 0.17,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 15.27,
|
"position_pct": 15.27,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -18,32 +18,32 @@
|
|||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 228.65,
|
"cost": 228.65,
|
||||||
"price": 178.26,
|
"price": 178.26,
|
||||||
"market_value": 102700.0,
|
"market_value": 89910.0,
|
||||||
"change_pct": 3.859,
|
"change_pct": 3.859,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 13.47,
|
"position_pct": 13.47,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "01478",
|
"code": "01478",
|
||||||
"name": "丘钛科技",
|
"name": "丘钛科技",
|
||||||
"shares": 11000,
|
"shares": 11000,
|
||||||
"cost": 11.68,
|
"cost": 11.68,
|
||||||
"price": 6.11,
|
"price": 6.1,
|
||||||
"market_value": 77550.0,
|
"market_value": 67210.0,
|
||||||
"change_pct": 4.911,
|
"change_pct": 4.762,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 7.97,
|
"position_pct": 7.97,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "601899",
|
"code": "601899",
|
||||||
"name": "紫金矿业",
|
"name": "紫金矿业",
|
||||||
"shares": 2400,
|
"shares": 2400,
|
||||||
"cost": 39.89,
|
"cost": 39.89,
|
||||||
"price": 28.04,
|
"price": 27.8,
|
||||||
"market_value": 67296.0,
|
"market_value": 66864.0,
|
||||||
"change_pct": 6.62,
|
"change_pct": 5.7,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 7.34,
|
"position_pct": 7.34,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -53,9 +53,9 @@
|
|||||||
"name": "海博思创",
|
"name": "海博思创",
|
||||||
"shares": 200,
|
"shares": 200,
|
||||||
"cost": 266.95,
|
"cost": 266.95,
|
||||||
"price": 257.52,
|
"price": 254.75,
|
||||||
"market_value": 51504.0,
|
"market_value": 50842.0,
|
||||||
"change_pct": 0.71,
|
"change_pct": -0.38,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 6.31,
|
"position_pct": 6.31,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -65,9 +65,9 @@
|
|||||||
"name": "中芯国际",
|
"name": "中芯国际",
|
||||||
"shares": 300,
|
"shares": 300,
|
||||||
"cost": 126.07,
|
"cost": 126.07,
|
||||||
"price": 143.65,
|
"price": 142.97,
|
||||||
"market_value": 43095.0,
|
"market_value": 42600.0,
|
||||||
"change_pct": -0.31,
|
"change_pct": -0.78,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 5.44,
|
"position_pct": 5.44,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -78,20 +78,20 @@
|
|||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 76.5,
|
"cost": 76.5,
|
||||||
"price": 74.17,
|
"price": 74.17,
|
||||||
"market_value": 42600.0,
|
"market_value": 37325.0,
|
||||||
"change_pct": 2.088,
|
"change_pct": 2.088,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 5.28,
|
"position_pct": 5.28,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "688639",
|
"code": "688639",
|
||||||
"name": "华恒生物",
|
"name": "华恒生物",
|
||||||
"shares": 2800,
|
"shares": 2800,
|
||||||
"cost": 21.51,
|
"cost": 21.51,
|
||||||
"price": 16.69,
|
"price": 16.72,
|
||||||
"market_value": 46732.0,
|
"market_value": 46900.0,
|
||||||
"change_pct": -1.53,
|
"change_pct": -1.36,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 5.25,
|
"position_pct": 5.25,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -101,9 +101,9 @@
|
|||||||
"name": "宁德时代",
|
"name": "宁德时代",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 401.78,
|
"cost": 401.78,
|
||||||
"price": 384.16,
|
"price": 380.24,
|
||||||
"market_value": 38416.0,
|
"market_value": 38032.0,
|
||||||
"change_pct": 0.47,
|
"change_pct": -0.55,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 4.64,
|
"position_pct": 4.64,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -113,57 +113,57 @@
|
|||||||
"name": "比亚迪股份",
|
"name": "比亚迪股份",
|
||||||
"shares": 600,
|
"shares": 600,
|
||||||
"cost": 90.92,
|
"cost": 90.92,
|
||||||
"price": 71.66,
|
"price": 71.83,
|
||||||
"market_value": 49560.0,
|
"market_value": 43044.0,
|
||||||
"change_pct": 5.556,
|
"change_pct": 5.811,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 4.62,
|
"position_pct": 4.62,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "02202",
|
"code": "02202",
|
||||||
"name": "万科企业",
|
"name": "万科企业",
|
||||||
"shares": 19700,
|
"shares": 19700,
|
||||||
"cost": 4.05,
|
"cost": 4.05,
|
||||||
"price": 2.02,
|
"price": 1.99,
|
||||||
"market_value": 45704.0,
|
"market_value": 39203.0,
|
||||||
"change_pct": 4.484,
|
"change_pct": 2.69,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 4.6,
|
"position_pct": 4.6,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "00700",
|
"code": "00700",
|
||||||
"name": "腾讯",
|
"name": "腾讯",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": null,
|
"cost": null,
|
||||||
"price": 379.05,
|
"price": 376.8,
|
||||||
"market_value": 43700.0,
|
"market_value": 37541.0,
|
||||||
"change_pct": 1.627,
|
"change_pct": 1.023,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": null,
|
"position_pct": null,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "00981",
|
"code": "00981",
|
||||||
"name": "中芯国际",
|
"name": "中芯国际",
|
||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 65.84,
|
"cost": 65.84,
|
||||||
"price": 69.1,
|
"price": 68.41,
|
||||||
"market_value": 39825.0,
|
"market_value": 34440.0,
|
||||||
"change_pct": -0.871,
|
"change_pct": -1.866,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 4.2,
|
"position_pct": 4.2,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"code": "300548",
|
"code": "300548",
|
||||||
"name": "长芯博创",
|
"name": "长芯博创",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 231.46,
|
"cost": 231.46,
|
||||||
"price": 227.0,
|
"price": 226.5,
|
||||||
"market_value": 22700.0,
|
"market_value": 22599.0,
|
||||||
"change_pct": 2.25,
|
"change_pct": 2.03,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 3.2,
|
"position_pct": 3.2,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -173,9 +173,9 @@
|
|||||||
"name": "黄金ETF华安",
|
"name": "黄金ETF华安",
|
||||||
"shares": 2400,
|
"shares": 2400,
|
||||||
"cost": 12.19,
|
"cost": 12.19,
|
||||||
"price": 8.69,
|
"price": 8.67,
|
||||||
"market_value": 20856.0,
|
"market_value": 20832.0,
|
||||||
"change_pct": 2.49,
|
"change_pct": 2.32,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.45,
|
"position_pct": 2.45,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -185,9 +185,9 @@
|
|||||||
"name": "中科电气",
|
"name": "中科电气",
|
||||||
"shares": 1400,
|
"shares": 1400,
|
||||||
"cost": 22.29,
|
"cost": 22.29,
|
||||||
"price": 14.3,
|
"price": 14.38,
|
||||||
"market_value": 20020.0,
|
"market_value": 20062.0,
|
||||||
"change_pct": 0.92,
|
"change_pct": 1.48,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.42,
|
"position_pct": 2.42,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -197,9 +197,9 @@
|
|||||||
"name": "模塑科技",
|
"name": "模塑科技",
|
||||||
"shares": 1400,
|
"shares": 1400,
|
||||||
"cost": 14.83,
|
"cost": 14.83,
|
||||||
"price": 17.37,
|
"price": 17.62,
|
||||||
"market_value": 24318.0,
|
"market_value": 25088.0,
|
||||||
"change_pct": 2.96,
|
"change_pct": 4.45,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.41,
|
"position_pct": 2.41,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -209,9 +209,9 @@
|
|||||||
"name": "法拉电子",
|
"name": "法拉电子",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 147.18,
|
"cost": 147.18,
|
||||||
"price": 161.71,
|
"price": 160.0,
|
||||||
"market_value": 16171.0,
|
"market_value": 15900.0,
|
||||||
"change_pct": -1.58,
|
"change_pct": -2.62,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.3,
|
"position_pct": 2.3,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -221,20 +221,20 @@
|
|||||||
"name": "中国神华",
|
"name": "中国神华",
|
||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 39.79,
|
"cost": 39.79,
|
||||||
"price": 34.84,
|
"price": 34.59,
|
||||||
"market_value": 20090.0,
|
"market_value": 17305.0,
|
||||||
"change_pct": 1.465,
|
"change_pct": 0.758,
|
||||||
"currency": "HKD",
|
"currency": "CNY",
|
||||||
"position_pct": 2.14,
|
"position_pct": 2.14,
|
||||||
"_currency": "HKD"
|
"_currency": "CNY"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total_assets": 864781.03,
|
"total_assets": 860913.13,
|
||||||
"total_mv": 784305.03,
|
"total_mv": 780437.13,
|
||||||
"stock_value": null,
|
"stock_value": null,
|
||||||
"cash": 80476.0,
|
"cash": 80476.0,
|
||||||
"frozen_cash": 0.0,
|
"frozen_cash": 0.0,
|
||||||
"position_pct": 90.69,
|
"position_pct": 90.65,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"updated_at": "2026-07-03 12:10"
|
"updated_at": "2026-07-03 13:42"
|
||||||
}
|
}
|
||||||
@@ -9109,6 +9109,26 @@
|
|||||||
"event_label": "止盈区间",
|
"event_label": "止盈区间",
|
||||||
"timestamp": "2026-07-03T11:54:31.001389",
|
"timestamp": "2026-07-03T11:54:31.001389",
|
||||||
"date": "2026-07-03"
|
"date": "2026-07-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000700",
|
||||||
|
"name": "模塑科技",
|
||||||
|
"event_type": "entry_zone",
|
||||||
|
"price": 17.92,
|
||||||
|
"trigger_value": "15.5~18.08",
|
||||||
|
"event_label": "买入区间",
|
||||||
|
"timestamp": "2026-07-03T13:28:17.259162",
|
||||||
|
"date": "2026-07-03"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "000700",
|
||||||
|
"name": "模塑科技",
|
||||||
|
"event_type": "entry_zone",
|
||||||
|
"price": 17.88,
|
||||||
|
"trigger_value": "15.5~18.08",
|
||||||
|
"event_label": "买入区间",
|
||||||
|
"timestamp": "2026-07-03T13:30:30.458142",
|
||||||
|
"date": "2026-07-03"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+16
-16
@@ -25,8 +25,8 @@
|
|||||||
{
|
{
|
||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 1215.52,
|
"high": 1215.52,
|
||||||
"low": 1190.5,
|
"low": 1188.0,
|
||||||
"close": 1191.31
|
"close": 1188.7
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"02202": [
|
"02202": [
|
||||||
@@ -61,8 +61,8 @@
|
|||||||
{
|
{
|
||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 50.2,
|
"high": 50.2,
|
||||||
"low": 48.6,
|
"low": 48.4,
|
||||||
"close": 48.7
|
"close": 48.44
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"02359": [
|
"02359": [
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 502.0,
|
"high": 502.0,
|
||||||
"low": 444.55,
|
"low": 444.55,
|
||||||
"close": 495.95
|
"close": 494.57
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"06160": [
|
"06160": [
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 687.04,
|
"high": 687.04,
|
||||||
"low": 633.01,
|
"low": 633.01,
|
||||||
"close": 654.0
|
"close": 646.83
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"09868": [
|
"09868": [
|
||||||
@@ -194,9 +194,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 753.88,
|
"high": 757.88,
|
||||||
"low": 713.0,
|
"low": 713.0,
|
||||||
"close": 744.0
|
"close": 733.9
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"300124": [
|
"300124": [
|
||||||
@@ -208,9 +208,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 72.38,
|
"high": 74.63,
|
||||||
"low": 67.31,
|
"low": 67.31,
|
||||||
"close": 72.29
|
"close": 73.13
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"000657": [
|
"000657": [
|
||||||
@@ -224,7 +224,7 @@
|
|||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 101.5,
|
"high": 101.5,
|
||||||
"low": 87.88,
|
"low": 87.88,
|
||||||
"close": 91.88
|
"close": 91.78
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"000711": [
|
"000711": [
|
||||||
@@ -250,9 +250,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 882.5,
|
"high": 892.1,
|
||||||
"low": 795.0,
|
"low": 795.0,
|
||||||
"close": 874.23
|
"close": 887.87
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"002594": [
|
"002594": [
|
||||||
@@ -266,7 +266,7 @@
|
|||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 87.28,
|
"high": 87.28,
|
||||||
"low": 81.9,
|
"low": 81.9,
|
||||||
"close": 86.56
|
"close": 86.45
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"00700": [
|
"00700": [
|
||||||
@@ -332,9 +332,9 @@
|
|||||||
"301308": [
|
"301308": [
|
||||||
{
|
{
|
||||||
"date": "2026-07-03",
|
"date": "2026-07-03",
|
||||||
"high": 631.56,
|
"high": 646.85,
|
||||||
"low": 574.1,
|
"low": 574.1,
|
||||||
"close": 620.0
|
"close": 635.0
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
+11
-29
@@ -21,13 +21,14 @@ import json, os
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
|
|
||||||
|
|
||||||
|
# live_prices.json 已废弃,所有数据走 DB
|
||||||
LIVE_PRICES_PATH = "/home/hmo/web-dashboard/data/live_prices.json"
|
LIVE_PRICES_PATH = "/home/hmo/web-dashboard/data/live_prices.json"
|
||||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
|
||||||
|
|
||||||
|
|
||||||
def is_market_hours():
|
def is_market_hours():
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
if now.weekday() >= 5: # 周六日
|
if now.weekday() >= 5:
|
||||||
return False, "weekend"
|
return False, "weekend"
|
||||||
t = now.hour * 60 + now.minute
|
t = now.hour * 60 + now.minute
|
||||||
if 9*60+30 <= t <= 15*60:
|
if 9*60+30 <= t <= 15*60:
|
||||||
@@ -38,42 +39,23 @@ def is_market_hours():
|
|||||||
def check_fresh():
|
def check_fresh():
|
||||||
"""返回 (ok: bool, msg: str)"""
|
"""返回 (ok: bool, msg: str)"""
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
|
|
||||||
# 先看是不是交易日
|
|
||||||
in_market, period = is_market_hours()
|
in_market, period = is_market_hours()
|
||||||
|
|
||||||
max_age_min = 5 if in_market else 120
|
max_age_min = 5 if in_market else 120
|
||||||
|
|
||||||
# 主指标:live_prices.json
|
# 主指标:DB portfolio_summary.updated_at
|
||||||
if os.path.exists(LIVE_PRICES_PATH):
|
|
||||||
try:
|
try:
|
||||||
lp = json.load(open(LIVE_PRICES_PATH))
|
pf = read_portfolio()
|
||||||
lp_time = lp.get("updated_at", "")
|
|
||||||
if not lp_time:
|
|
||||||
return False, "live_prices.json updated_at 为空"
|
|
||||||
lp_dt = datetime.fromisoformat(lp_time)
|
|
||||||
age = (now - lp_dt).total_seconds() / 60
|
|
||||||
if age > max_age_min:
|
|
||||||
return False, f"live_prices.json 已 {age:.0f} 分钟未更新(阈值 {max_age_min} 分钟)"
|
|
||||||
return True, f"数据新鲜({age:.0f} 分钟前)"
|
|
||||||
except Exception as e:
|
|
||||||
return False, f"live_prices.json 读取失败: {e}"
|
|
||||||
else:
|
|
||||||
# fallback: portfolio.json
|
|
||||||
if os.path.exists(PORTFOLIO_PATH):
|
|
||||||
try:
|
|
||||||
pf = mo_data.read_portfolio()
|
|
||||||
pf_time = pf.get("updated_at", "")
|
pf_time = pf.get("updated_at", "")
|
||||||
if not pf_time:
|
if pf_time:
|
||||||
return False, "portfolio.json updated_at 为空"
|
|
||||||
pf_dt = datetime.fromisoformat(pf_time)
|
pf_dt = datetime.fromisoformat(pf_time)
|
||||||
age = (now - pf_dt).total_seconds() / 60
|
age = (now - pf_dt).total_seconds() / 60
|
||||||
if age > max_age_min:
|
if age > max_age_min:
|
||||||
return False, f"portfolio.json 已 {age:.0f} 分钟未更新(阈值 {max_age_min} 分钟)"
|
return False, f"数据已 {age:.0f} 分钟未更新(阈值 {max_age_min} 分钟)"
|
||||||
return True, f"数据新鲜(portfolio.json {age:.0f} 分钟前)"
|
return True, f"数据新鲜({age:.0f} 分钟前)"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, f"portfolio.json 读取失败: {e}"
|
return False, f"DB 读取失败: {e}"
|
||||||
return False, "live_prices.json 和 portfolio.json 均不存在"
|
|
||||||
|
return False, "无法获取数据新鲜度"
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
+1
-3
@@ -203,10 +203,8 @@ def main():
|
|||||||
}
|
}
|
||||||
|
|
||||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||||
with open(DATA_DIR / "market.json", "w", encoding="utf-8") as f:
|
|
||||||
json.dump(market_data, f, ensure_ascii=False, indent=2)
|
|
||||||
|
|
||||||
# ── SQLite 双写 ──
|
# ── SQLite 写入(替代 market.json)──
|
||||||
conn = get_conn()
|
conn = get_conn()
|
||||||
init_all_tables(conn)
|
init_all_tables(conn)
|
||||||
ok, msg, sid = write_market_snapshot(conn, market_data)
|
ok, msg, sid = write_market_snapshot(conn, market_data)
|
||||||
|
|||||||
+67
@@ -387,6 +387,28 @@ def init_all_tables(conn: sqlite3.Connection):
|
|||||||
last_scanned_at TEXT,
|
last_scanned_at TEXT,
|
||||||
found_count INTEGER DEFAULT 0
|
found_count INTEGER DEFAULT 0
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- 实时价格快照(替代 live_prices.json)
|
||||||
|
CREATE TABLE IF NOT EXISTS live_prices (
|
||||||
|
code TEXT PRIMARY KEY,
|
||||||
|
price REAL,
|
||||||
|
change_pct REAL,
|
||||||
|
updated_at TEXT DEFAULT (datetime('now','localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 多周期缓存(替代 multi_tf_cache.json)
|
||||||
|
CREATE TABLE IF NOT EXISTS mtf_cache (
|
||||||
|
code TEXT PRIMARY KEY,
|
||||||
|
cache_json TEXT,
|
||||||
|
updated_at TEXT DEFAULT (datetime('now','localtime'))
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 资金流缓存(替代 capital_flow_cache.json)
|
||||||
|
CREATE TABLE IF NOT EXISTS capital_flow_cache (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
cache_json TEXT,
|
||||||
|
updated_at TEXT DEFAULT (datetime('now','localtime'))
|
||||||
|
);
|
||||||
""")
|
""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
@@ -1120,3 +1142,48 @@ def query_cash_log(conn, limit: int = 20) -> list[dict]:
|
|||||||
"SELECT * FROM cash_log ORDER BY id DESC LIMIT ?", (limit,)
|
"SELECT * FROM cash_log ORDER BY id DESC LIMIT ?", (limit,)
|
||||||
).fetchall()
|
).fetchall()
|
||||||
return [dict(r) for r in rows]
|
return [dict(r) for r in rows]
|
||||||
|
|
||||||
|
|
||||||
|
# ═══ live_prices / mtf_cache / capital_flow_cache 写函数 ═══
|
||||||
|
|
||||||
|
def write_live_prices(conn, prices: dict):
|
||||||
|
"""写入实时价格快照(替代 live_prices.json)"""
|
||||||
|
import json
|
||||||
|
for code, info in prices.items():
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO live_prices (code, price, change_pct, updated_at) VALUES (?,?,?,datetime('now','localtime'))",
|
||||||
|
(code, info.get('price'), info.get('change_pct'))
|
||||||
|
)
|
||||||
|
|
||||||
|
def read_live_prices(conn) -> dict:
|
||||||
|
rows = conn.execute("SELECT code, price, change_pct FROM live_prices").fetchall()
|
||||||
|
return {r['code']: {'price': r['price'], 'change_pct': r['change_pct']} for r in rows}
|
||||||
|
|
||||||
|
|
||||||
|
def write_mtf_cache(conn, code: str, data: dict):
|
||||||
|
"""写入多周期缓存(替代 multi_tf_cache.json 单条)"""
|
||||||
|
import json
|
||||||
|
conn.execute(
|
||||||
|
"INSERT OR REPLACE INTO mtf_cache (code, cache_json, updated_at) VALUES (?,?,datetime('now','localtime'))",
|
||||||
|
(code, json.dumps(data, ensure_ascii=False))
|
||||||
|
)
|
||||||
|
|
||||||
|
def read_mtf_cache(conn, code: str) -> dict:
|
||||||
|
import json
|
||||||
|
r = conn.execute("SELECT cache_json FROM mtf_cache WHERE code=?", (code,)).fetchone()
|
||||||
|
return json.loads(r['cache_json']) if r else {}
|
||||||
|
|
||||||
|
|
||||||
|
def write_capital_flow_cache(conn, data: dict):
|
||||||
|
"""写入资金流缓存(替代 capital_flow_cache.json)"""
|
||||||
|
import json
|
||||||
|
conn.execute("DELETE FROM capital_flow_cache")
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO capital_flow_cache (cache_json, updated_at) VALUES (?,datetime('now','localtime'))",
|
||||||
|
(json.dumps(data, ensure_ascii=False),)
|
||||||
|
)
|
||||||
|
|
||||||
|
def read_capital_flow_cache(conn) -> dict:
|
||||||
|
import json
|
||||||
|
r = conn.execute("SELECT cache_json FROM capital_flow_cache ORDER BY id DESC LIMIT 1").fetchone()
|
||||||
|
return json.loads(r['cache_json']) if r else {}
|
||||||
|
|||||||
+33
-31
@@ -19,7 +19,7 @@ from typing import Optional
|
|||||||
|
|
||||||
DATA_DIR = "/home/hmo/web-dashboard/data"
|
DATA_DIR = "/home/hmo/web-dashboard/data"
|
||||||
HISTORY_PATH = os.path.join(DATA_DIR, "price_history.json")
|
HISTORY_PATH = os.path.join(DATA_DIR, "price_history.json")
|
||||||
MTF_CACHE_PATH = os.path.join(DATA_DIR, "multi_tf_cache.json") # 多周期缓存独立存储
|
# multi_tf_cache.json 已迁移到 DB (mtf_cache 表)
|
||||||
|
|
||||||
# 腾讯API K线端点
|
# 腾讯API K线端点
|
||||||
KLINE_URL = "http://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param={market}{code},{period},,,{count},qfq"
|
KLINE_URL = "http://web.ifzq.gtimg.cn/appstock/app/fqkline/get?param={market}{code},{period},,,{count},qfq"
|
||||||
@@ -80,40 +80,47 @@ def _user_agent() -> dict:
|
|||||||
# 多周期缓存TTL(秒):日K线1小时,周/月K线1天
|
# 多周期缓存TTL(秒):日K线1小时,周/月K线1天
|
||||||
_KLINE_CACHE_TTL = {"day": 3600, "week": 86400, "month": 86400}
|
_KLINE_CACHE_TTL = {"day": 3600, "week": 86400, "month": 86400}
|
||||||
|
|
||||||
# 模块级缓存:避免每次fetch_kline都重新读/写大文件
|
# 模块级缓存:避免每次 fetch_kline 都重新读 DB
|
||||||
_MTF_CACHE_DATA = None # {code: {daily:[], weekly:[], monthly:[], updated_at: float, fundamentals:{}}}
|
_MTF_CACHE_DATA = None
|
||||||
_MTF_CACHE_MTIME = 0 # 文件最后修改时间
|
_MTF_CACHE_DIRTY = False
|
||||||
|
|
||||||
|
|
||||||
def _load_mtf_cache():
|
def _load_mtf_cache():
|
||||||
"""加载多周期缓存(带模块级缓存,避免频繁读盘)"""
|
"""从 DB 加载多周期缓存"""
|
||||||
global _MTF_CACHE_DATA, _MTF_CACHE_MTIME
|
global _MTF_CACHE_DATA
|
||||||
import time
|
if _MTF_CACHE_DATA is not None:
|
||||||
|
return _MTF_CACHE_DATA
|
||||||
try:
|
try:
|
||||||
current_mtime = os.path.getmtime(MTF_CACHE_PATH)
|
import sqlite3
|
||||||
if _MTF_CACHE_DATA is not None and current_mtime == _MTF_CACHE_MTIME:
|
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||||
return _MTF_CACHE_DATA
|
rows = db.execute("SELECT code, cache_json FROM mtf_cache").fetchall()
|
||||||
with open(MTF_CACHE_PATH) as f:
|
|
||||||
_MTF_CACHE_DATA = json.load(f)
|
|
||||||
_MTF_CACHE_MTIME = current_mtime
|
|
||||||
return _MTF_CACHE_DATA
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError, OSError):
|
|
||||||
_MTF_CACHE_DATA = {}
|
_MTF_CACHE_DATA = {}
|
||||||
_MTF_CACHE_MTIME = 0
|
for code, json_str in rows:
|
||||||
return {}
|
try:
|
||||||
|
_MTF_CACHE_DATA[code] = json.loads(json_str)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
db.close()
|
||||||
|
except Exception:
|
||||||
|
_MTF_CACHE_DATA = {}
|
||||||
|
return _MTF_CACHE_DATA
|
||||||
|
|
||||||
|
|
||||||
def _save_mtf_cache():
|
def _save_mtf_cache():
|
||||||
"""将模块级缓存写回磁盘"""
|
"""将模块级缓存写回 DB"""
|
||||||
global _MTF_CACHE_DATA, _MTF_CACHE_MTIME
|
global _MTF_CACHE_DATA
|
||||||
if _MTF_CACHE_DATA is None:
|
if _MTF_CACHE_DATA is None:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
os.makedirs(os.path.dirname(MTF_CACHE_PATH), exist_ok=True)
|
import sqlite3
|
||||||
with open(MTF_CACHE_PATH, "w") as f:
|
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||||
json.dump(_MTF_CACHE_DATA, f, ensure_ascii=False, indent=2)
|
for code, data in _MTF_CACHE_DATA.items():
|
||||||
import time
|
db.execute(
|
||||||
_MTF_CACHE_MTIME = os.path.getmtime(MTF_CACHE_PATH) if os.path.exists(MTF_CACHE_PATH) else time.time()
|
"INSERT OR REPLACE INTO mtf_cache (code, cache_json, updated_at) VALUES (?,?,datetime('now','localtime'))",
|
||||||
|
(code, json.dumps(data, ensure_ascii=False))
|
||||||
|
)
|
||||||
|
db.commit()
|
||||||
|
db.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -582,13 +589,8 @@ def _generate_strategy_adjustment(analysis: dict) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def _load_local_history(code: str, period: str) -> list:
|
def _load_local_history(code: str, period: str) -> list:
|
||||||
"""从本地多周期缓存读取历史数据,不修改 price_history.json"""
|
"""从 DB 多周期缓存读取历史数据"""
|
||||||
try:
|
data = _load_mtf_cache()
|
||||||
with open(MTF_CACHE_PATH) as f:
|
|
||||||
data = json.load(f)
|
|
||||||
except (FileNotFoundError, json.JSONDecodeError):
|
|
||||||
return []
|
|
||||||
|
|
||||||
stock = data.get(code, {})
|
stock = data.get(code, {})
|
||||||
return stock.get(period, [])
|
return stock.get(period, [])
|
||||||
|
|
||||||
|
|||||||
+8
-6
@@ -12,7 +12,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
# ── MoFin unified model ──────────────────────────────────────────────
|
# ── MoFin unified model ──────────────────────────────────────────────
|
||||||
from mo_models import is_hk_stock, get_hk_rate, calc_total_assets, calc_total_mv, calc_position_pct
|
from mo_models import is_hk_stock, get_hk_rate, calc_total_assets, calc_total_mv, calc_position_pct
|
||||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_price_event, write_watchlist_stock, write_holding_strategy
|
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_price_event, write_watchlist_stock, write_live_prices, read_capital_flow_cache, write_holding_strategy
|
||||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
|
|
||||||
BREACH_PATH = "/home/hmo/.hermes/zone_breach.json"
|
BREACH_PATH = "/home/hmo/.hermes/zone_breach.json"
|
||||||
@@ -231,14 +231,16 @@ def refresh_data_prices():
|
|||||||
prices.update(hk_prices)
|
prices.update(hk_prices)
|
||||||
updated = 0
|
updated = 0
|
||||||
|
|
||||||
# 保存全量实时价快照(供报告管道消费,确保分析用最新数据)
|
# 保存全量实时价快照到 DB(替代 live_prices.json)
|
||||||
try:
|
try:
|
||||||
live = {"updated_at": datetime.now().isoformat(), "prices": {}}
|
live = {}
|
||||||
for code in all_codes:
|
for code in all_codes:
|
||||||
if code in prices:
|
if code in prices:
|
||||||
p, c, chg = prices[code]
|
p, c, chg = prices[code]
|
||||||
live["prices"][code] = {"price": p, "change_pct": chg}
|
live[code] = {"price": p, "change_pct": chg}
|
||||||
json.dump(live, open("/home/hmo/web-dashboard/data/live_prices.json", "w"), indent=2)
|
conn = get_conn()
|
||||||
|
write_live_prices(conn, live)
|
||||||
|
conn.close()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -663,7 +665,7 @@ def run_once(round_label=""):
|
|||||||
|
|
||||||
# === 3.5 资金流异常检测(2026-06-27 新增)===
|
# === 3.5 资金流异常检测(2026-06-27 新增)===
|
||||||
try:
|
try:
|
||||||
cf = json.load(open("/home/hmo/web-dashboard/data/capital_flow_cache.json"))
|
cf = read_capital_flow_cache(get_conn())
|
||||||
# 检查所有 active decision 中的资金流异常
|
# 检查所有 active decision 中的资金流异常
|
||||||
for d in active:
|
for d in active:
|
||||||
code = d["code"]
|
code = d["code"]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from urllib.request import urlopen, Request
|
|||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from threading import Semaphore
|
from threading import Semaphore
|
||||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
|
from mofin_db import get_conn, write_capital_flow_cache
|
||||||
|
|
||||||
DATA_DIR = "/home/hmo/web-dashboard/data"
|
DATA_DIR = "/home/hmo/web-dashboard/data"
|
||||||
DECISIONS_PATH = f"{DATA_DIR}/decisions.json"
|
DECISIONS_PATH = f"{DATA_DIR}/decisions.json"
|
||||||
@@ -175,7 +176,10 @@ def main():
|
|||||||
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
"updated_at": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||||||
"stocks": all_flows,
|
"stocks": all_flows,
|
||||||
}
|
}
|
||||||
json.dump(cache, open(CACHE_PATH, "w"), indent=2, ensure_ascii=False)
|
# 写 DB(替代 capital_flow_cache.json)
|
||||||
|
conn = get_conn()
|
||||||
|
write_capital_flow_cache(conn, cache)
|
||||||
|
conn.close()
|
||||||
print(f"[capital_flow] {len(all_flows)}/{len(code_list)}只更新完成")
|
print(f"[capital_flow] {len(all_flows)}/{len(code_list)}只更新完成")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import sys, traceback
|
||||||
|
sys.path.insert(0, '/home/hmo/MoFin')
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
for mod, name in [
|
||||||
|
('mo_data', 'read_portfolio'),
|
||||||
|
('mo_data', 'read_decisions'),
|
||||||
|
('mo_data', 'read_watchlist'),
|
||||||
|
('mofin_db', 'get_conn'),
|
||||||
|
('mofin_db', 'write_holdings_batch'),
|
||||||
|
('mofin_db', 'write_portfolio_summary'),
|
||||||
|
('mofin_db', 'write_watchlist_stock'),
|
||||||
|
('mofin_db', 'write_holding_strategy'),
|
||||||
|
('mo_models', 'is_hk_stock'),
|
||||||
|
('mo_models', 'get_hk_rate'),
|
||||||
|
]:
|
||||||
|
try:
|
||||||
|
m = __import__(mod, fromlist=[name])
|
||||||
|
getattr(m, name)
|
||||||
|
print(f"OK: {mod}.{name}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"FAIL: {mod}.{name} -> {e}")
|
||||||
|
errors.append(str(e))
|
||||||
|
|
||||||
|
print(f"\n=== price_monitor.py import test ===")
|
||||||
|
try:
|
||||||
|
import price_monitor
|
||||||
|
print("price_monitor imported OK")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"FAIL: {traceback.format_exc()}")
|
||||||
|
|
||||||
|
if errors:
|
||||||
|
print(f"\n{len(errors)} import errors!")
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import sqlite3
|
||||||
|
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||||
|
|
||||||
|
# Check when portfolio was last updated
|
||||||
|
r = db.execute("SELECT updated_at, total_assets, total_mv, cash FROM portfolio_summary WHERE id=1").fetchone()
|
||||||
|
print(f"Portfolio last updated: {r[0]}")
|
||||||
|
print(f"total_assets={r[1]} total_mv={r[2]} cash={r[3]}")
|
||||||
|
|
||||||
|
# Check hold prices
|
||||||
|
print("\nAll holdings:")
|
||||||
|
for r in db.execute("SELECT code, name, price, change_pct, cost, shares FROM holdings WHERE is_active=1 ORDER BY code"):
|
||||||
|
mv = (r[2] or 0) * (r[5] or 0)
|
||||||
|
print(f" {r[0]} {r[1]}: price={r[2]} chg={r[3]} cost={r[4]} shares={r[5]} mv={mv}")
|
||||||
|
|
||||||
|
db.close()
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Remove held stocks from watchlist"""
|
"""Remove held stocks from watchlist"""
|
||||||
|
|
||||||
import json, os
|
import json, os, sys
|
||||||
|
|
||||||
|
# 确保 MoFin 根目录在模块搜索路径中(兼容 cron 环境)
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
from mofin_db import get_conn, write_watchlist_stock, write_holding_strategy
|
from mofin_db import get_conn, write_watchlist_stock, write_holding_strategy
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""Diagnose: check HK stock costs, prices, total assets calculation"""
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/home/hmo/MoFin')
|
||||||
|
from mo_models import calc_total_assets, calc_total_mv, is_hk_stock, to_cny, get_hk_rate
|
||||||
|
from mo_data import read_portfolio
|
||||||
|
import json
|
||||||
|
|
||||||
|
pf = read_portfolio()
|
||||||
|
holdings = pf.get('holdings', [])
|
||||||
|
rate = get_hk_rate()
|
||||||
|
|
||||||
|
print(f"HK_RATE: {rate}")
|
||||||
|
print(f"total_assets (from DB): {pf.get('total_assets')}")
|
||||||
|
print(f"total_mv (from DB): {pf.get('total_mv')}")
|
||||||
|
print(f"cash: {pf.get('cash')}")
|
||||||
|
print(f"frozen_cash: {pf.get('frozen_cash')}")
|
||||||
|
print(f"position_pct: {pf.get('position_pct')}")
|
||||||
|
|
||||||
|
total_mv_calc = calc_total_mv(holdings)
|
||||||
|
total_assets_calc = calc_total_assets(pf)
|
||||||
|
print(f"\ncalc_total_mv: {total_mv_calc}")
|
||||||
|
print(f"calc_total_assets: {total_assets_calc}")
|
||||||
|
|
||||||
|
print(f"\n=== HK stocks ===")
|
||||||
|
for h in holdings:
|
||||||
|
code = h.get('code', '')
|
||||||
|
if is_hk_stock(str(code)):
|
||||||
|
cost = h.get('cost', 0) or 0
|
||||||
|
price = h.get('price', 0) or 0
|
||||||
|
shares = h.get('shares', 0) or 0
|
||||||
|
mv = price * shares
|
||||||
|
cost_calc = cost * shares
|
||||||
|
pnl = (price - cost) * shares if cost > 0 else 0
|
||||||
|
pnl_pct = (price - cost) / cost * 100 if cost > 0 else 0
|
||||||
|
print(f" {code} {h.get('name')}: cost={cost} price={price} shares={shares} mv={mv} pnl={pnl:.1f} ({pnl_pct:+.1f}%)")
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""Quick verification after JSON→DB migration"""
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '/home/hmo/MoFin')
|
||||||
|
|
||||||
|
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||||
|
|
||||||
|
ok = 0
|
||||||
|
err = 0
|
||||||
|
|
||||||
|
for name, fn in [("portfolio", read_portfolio), ("decisions", read_decisions), ("watchlist", read_watchlist)]:
|
||||||
|
try:
|
||||||
|
r = fn()
|
||||||
|
if name == "portfolio":
|
||||||
|
n = len(r.get('holdings', []))
|
||||||
|
elif name == "decisions":
|
||||||
|
n = len(r.get('decisions', []))
|
||||||
|
else:
|
||||||
|
n = len(r.get('stocks', []))
|
||||||
|
print(f" {name}: {n} records OK")
|
||||||
|
ok += 1
|
||||||
|
except Exception as e:
|
||||||
|
print(f" {name}: ERROR -> {e}")
|
||||||
|
err += 1
|
||||||
|
|
||||||
|
print(f"\n{ok}/3 passed, {err} errors")
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
"""Test Eastmoney API response time for HK stocks"""
|
||||||
|
import urllib.request, json, time
|
||||||
|
|
||||||
|
codes = ['00700', '01888', '00981']
|
||||||
|
UA = 'Mozilla/5.0'
|
||||||
|
|
||||||
|
for code in codes:
|
||||||
|
url = f"https://push2.eastmoney.com/api/qt/stock/get?secid=116.{code}&fields=f43,f170&fltt=2"
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||||
|
with urllib.request.urlopen(req, timeout=30) as r:
|
||||||
|
resp = json.loads(r.read().decode("utf-8"))
|
||||||
|
elapsed = time.time() - start
|
||||||
|
price = resp.get('data', {}).get('f43', '?')
|
||||||
|
print(f"{code}: {elapsed:.1f}s, price={price}, rc={resp.get('rc')}")
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
print(f"{code}: {elapsed:.1f}s, ERROR: {type(e).__name__}: {e}")
|
||||||
|
|
||||||
|
# Also test Tencent fallback
|
||||||
|
print("\nTencent fallback:")
|
||||||
|
url = "http://qt.gtimg.cn/q=hk00700,hk01888,hk00981"
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as r:
|
||||||
|
text = r.read().decode("gbk")
|
||||||
|
elapsed = time.time() - start
|
||||||
|
print(f"Tencent: {elapsed:.1f}s, {len(text)} bytes")
|
||||||
|
# Parse first line
|
||||||
|
line = text.strip().split('\n')[0]
|
||||||
|
print(f" sample: {line[:80]}...")
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
print(f"Tencent: {elapsed:.1f}s, ERROR: {e}")
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
"""Test if steady 5s interval avoids rate limit"""
|
||||||
|
import urllib.request, json, time
|
||||||
|
|
||||||
|
UA = 'Mozilla/5.0'
|
||||||
|
codes = ['00700', '01888', '00981']
|
||||||
|
|
||||||
|
for i, code in enumerate(codes):
|
||||||
|
url = f"https://push2.eastmoney.com/api/qt/stock/get?secid=116.{code}&fields=f43,f170&fltt=2"
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as r:
|
||||||
|
resp = json.loads(r.read().decode("utf-8"))
|
||||||
|
elapsed = time.time() - start
|
||||||
|
price = resp.get('data', {}).get('f43', '?')
|
||||||
|
print(f"#{i+1} {code}: OK in {elapsed:.1f}s, price={price}")
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
print(f"#{i+1} {code}: FAIL in {elapsed:.1f}s — {type(e).__name__}")
|
||||||
|
break
|
||||||
|
if i < len(codes) - 1:
|
||||||
|
time.sleep(5)
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
"""Test Eastmoney rate limit — find safe interval between requests"""
|
||||||
|
import urllib.request, json, time
|
||||||
|
|
||||||
|
UA = 'Mozilla/5.0'
|
||||||
|
CODE = '00700'
|
||||||
|
url = f"https://push2.eastmoney.com/api/qt/stock/get?secid=116.{CODE}&fields=f43,f170&fltt=2"
|
||||||
|
|
||||||
|
def try_fetch():
|
||||||
|
start = time.time()
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(url, headers={"User-Agent": UA})
|
||||||
|
with urllib.request.urlopen(req, timeout=5) as r:
|
||||||
|
resp = json.loads(r.read().decode("utf-8"))
|
||||||
|
elapsed = time.time() - start
|
||||||
|
return True, elapsed, resp.get('data', {}).get('f43', '?'), ''
|
||||||
|
except Exception as e:
|
||||||
|
elapsed = time.time() - start
|
||||||
|
return False, elapsed, 0, str(e)[:50]
|
||||||
|
|
||||||
|
# Test 1: single request
|
||||||
|
ok, t, price, err = try_fetch()
|
||||||
|
print(f"Single: {'OK' if ok else 'FAIL'} in {t:.2f}s price={price} {err}")
|
||||||
|
|
||||||
|
if ok:
|
||||||
|
# If it works, test minimal interval
|
||||||
|
for delay in [0.5, 1, 2, 3, 5]:
|
||||||
|
time.sleep(delay)
|
||||||
|
ok2, t2, p2, e2 = try_fetch()
|
||||||
|
print(f"After {delay:.1f}s: {'OK' if ok2 else 'FAIL'} in {t2:.2f}s price={p2} {e2}")
|
||||||
|
if not ok2:
|
||||||
|
print(f" -> Rate limited! Need > {delay}s between requests")
|
||||||
|
else:
|
||||||
|
# Currently blocked, wait and retry
|
||||||
|
print("Currently blocked. Waiting 10s then retry...")
|
||||||
|
time.sleep(10)
|
||||||
|
ok, t, price, err = try_fetch()
|
||||||
|
print(f"After 10s: {'OK' if ok else 'FAIL'} in {t:.2f}s price={price} {err}")
|
||||||
|
if ok:
|
||||||
|
time.sleep(2)
|
||||||
|
ok2, t2, p2, e2 = try_fetch()
|
||||||
|
print(f"After +2s: {'OK' if ok2 else 'FAIL'} in {t2:.2f}s price={p2} {e2}")
|
||||||
|
else:
|
||||||
|
for w in [20, 30, 60]:
|
||||||
|
print(f"Waiting {w}s...")
|
||||||
|
time.sleep(w)
|
||||||
|
ok, t, price, err = try_fetch()
|
||||||
|
print(f"After {w}s: {'OK' if ok else 'FAIL'} in {t:.2f}s price={price} {err}")
|
||||||
|
if ok:
|
||||||
|
break
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
"""Verify total assets and P&L calculation"""
|
||||||
|
import sqlite3, sys
|
||||||
|
sys.path.insert(0, '/home/hmo/MoFin')
|
||||||
|
from mo_models import calc_total_assets, calc_total_mv
|
||||||
|
|
||||||
|
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||||
|
r = db.execute("SELECT * FROM portfolio_summary WHERE id=1").fetchone()
|
||||||
|
keys = [d[0] for d in db.execute("SELECT * FROM portfolio_summary LIMIT 0").description]
|
||||||
|
summary = dict(zip(keys, r))
|
||||||
|
|
||||||
|
print(f"total_assets (stored): {summary.get('total_assets')}")
|
||||||
|
print(f"total_mv (stored): {summary.get('total_mv')}")
|
||||||
|
print(f"total_pnl (stored): {summary.get('total_pnl')}")
|
||||||
|
print(f"cash: {summary.get('cash')}")
|
||||||
|
print(f"frozen_cash: {summary.get('frozen_cash')}")
|
||||||
|
|
||||||
|
holdings = []
|
||||||
|
for r in db.execute("SELECT code, name, cost, price, shares FROM holdings WHERE is_active=1"):
|
||||||
|
holdings.append({'code': r[0], 'name': r[1], 'cost': r[2] or 0, 'price': r[3] or 0, 'shares': r[4] or 0})
|
||||||
|
|
||||||
|
mv = sum(h['price'] * h['shares'] for h in holdings)
|
||||||
|
total_cost = sum(h['cost'] * h['shares'] for h in holdings)
|
||||||
|
pnl = mv - total_cost
|
||||||
|
ta = mv + (summary.get('cash') or 0) + (summary.get('frozen_cash') or 0)
|
||||||
|
|
||||||
|
print(f"\nCalculated:")
|
||||||
|
print(f"total_mv = {mv:.2f}")
|
||||||
|
print(f"total_cost = {total_cost:.2f}")
|
||||||
|
print(f"total_pnl = {pnl:.2f}")
|
||||||
|
print(f"total_assets = {ta:.2f}")
|
||||||
|
|
||||||
|
# Check for HK stocks with cost=0 (never converted)
|
||||||
|
print("\nStocks with cost=0 or None:")
|
||||||
|
for h in holdings:
|
||||||
|
if h['cost'] <= 0 and h['shares'] > 0:
|
||||||
|
print(f" {h['code']} {h['name']}: cost={h['cost']} shares={h['shares']} price={h['price']}")
|
||||||
|
|
||||||
|
db.close()
|
||||||
@@ -13,6 +13,10 @@ no_agent模式:有发现→输出,无→静默
|
|||||||
import json, os, sqlite3, sys, time, urllib.request
|
import json, os, sqlite3, sys, time, urllib.request
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
# 确保 MoFin 根目录在模块搜索路径中(兼容 cron 环境)
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
from mo_data import read_watchlist
|
from mo_data import read_watchlist
|
||||||
from mofin_db import write_watchlist_stock
|
from mofin_db import write_watchlist_stock
|
||||||
|
|
||||||
@@ -34,8 +38,13 @@ def clean_proxy():
|
|||||||
def fetch_quote(code):
|
def fetch_quote(code):
|
||||||
"""拉行情。DB 优先,腾讯 fallback"""
|
"""拉行情。DB 优先,腾讯 fallback"""
|
||||||
# DB 优先
|
# DB 优先
|
||||||
try: from mofin_db import get_price_from_db; p, chg = get_price_from_db(code); return {"name":"", "code":code, "price":p, "change_pct":chg or 0} if p else None
|
try:
|
||||||
except: pass
|
from mofin_db import get_price_from_db
|
||||||
|
p, chg = get_price_from_db(code)
|
||||||
|
if p:
|
||||||
|
return {"name":"", "code":code, "price":p, "change_pct":chg or 0}
|
||||||
|
except:
|
||||||
|
pass
|
||||||
# Fallback: 腾讯
|
# Fallback: 腾讯
|
||||||
try:
|
try:
|
||||||
prefix = "sh" if code.startswith(('60','68','51','56','50')) else "sz" if code.startswith(('00','30','15')) else "hk"
|
prefix = "sh" if code.startswith(('60','68','51','56','50')) else "sz" if code.startswith(('00','30','15')) else "hk"
|
||||||
|
|||||||
Reference in New Issue
Block a user