migrate: remove JSON, DB-only — mo_data, server, scripts, prompts (27 files)
This commit is contained in:
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"last_updated": "2026-06-30 11:11",
|
||||
"total_candidates": 3,
|
||||
"last_updated": "2026-07-03 11:32",
|
||||
"total_candidates": 5,
|
||||
"sectors_analyzed_today": [
|
||||
"半导体",
|
||||
"金属新材料",
|
||||
@@ -439,6 +439,70 @@
|
||||
"drop_reason": null,
|
||||
"trend_warning": false,
|
||||
"trend_note": ""
|
||||
},
|
||||
{
|
||||
"code": "300699",
|
||||
"name": "光威复材",
|
||||
"sector": "军工装备",
|
||||
"xiaoguo_score": 8.5,
|
||||
"xiaoguo_reason": "碳纤维核心供应商,与中简科技同属新材料主线,业绩确定性强,机构持仓集中。技术面放量突破震荡平台,量价配合健康。",
|
||||
"xiaoguo_strategy": {
|
||||
"entry_range": "38.5-40.2元",
|
||||
"stop_loss": "36.8元",
|
||||
"target": "45.0-48.0元"
|
||||
},
|
||||
"verified_price": 30.28,
|
||||
"verified_change": 6.62,
|
||||
"added_at": "2026-07-03 11:10",
|
||||
"last_updated": "2026-07-03 11:10",
|
||||
"num_observations": 1,
|
||||
"score_history": [
|
||||
{
|
||||
"date": "2026-07-03 11:10",
|
||||
"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": "600760",
|
||||
"name": "中航沈飞",
|
||||
"sector": "军工装备",
|
||||
"xiaoguo_score": 8.0,
|
||||
"xiaoguo_reason": "歼击机核心总装平台,订单饱满且估值处于历史中低位。板块情绪升温时具备高弹性,适合作为稳健底仓。",
|
||||
"xiaoguo_strategy": {
|
||||
"entry_range": "32.0-33.5元",
|
||||
"stop_loss": "30.5元",
|
||||
"target": "36.5-38.0元"
|
||||
},
|
||||
"verified_price": 41.39,
|
||||
"verified_change": 1.5,
|
||||
"added_at": "2026-07-03 11:10",
|
||||
"last_updated": "2026-07-03 11:10",
|
||||
"num_observations": 1,
|
||||
"score_history": [
|
||||
{
|
||||
"date": "2026-07-03 11:10",
|
||||
"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": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
+506
-844
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
+39
-39
@@ -1254,7 +1254,7 @@
|
||||
"volume": 147766189.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045022.0597436
|
||||
"updated_at": 1783051747.536662
|
||||
},
|
||||
"688795": {
|
||||
"daily": [
|
||||
@@ -2479,7 +2479,7 @@
|
||||
"volume": 4788252.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045017.2558444
|
||||
"updated_at": 1783051740.058768
|
||||
},
|
||||
"000657": {
|
||||
"daily": [
|
||||
@@ -3736,7 +3736,7 @@
|
||||
"volume": 1051508.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044767.899178
|
||||
"updated_at": 1783051278.4453125
|
||||
},
|
||||
"000700": {
|
||||
"daily": [
|
||||
@@ -4993,7 +4993,7 @@
|
||||
"volume": 1265397.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044853.6724539
|
||||
"updated_at": 1783051627.982171
|
||||
},
|
||||
"000711": {
|
||||
"daily": [
|
||||
@@ -6250,7 +6250,7 @@
|
||||
"volume": 496248.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044770.290931
|
||||
"updated_at": 1783051282.8847363
|
||||
},
|
||||
"001309": {
|
||||
"daily": [
|
||||
@@ -7507,7 +7507,7 @@
|
||||
"volume": 216663.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044771.251316
|
||||
"updated_at": 1783051286.5401492
|
||||
},
|
||||
"002594": {
|
||||
"daily": [
|
||||
@@ -8764,7 +8764,7 @@
|
||||
"volume": 934285.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044773.5265088
|
||||
"updated_at": 1783051286.9981902
|
||||
},
|
||||
"00700": {
|
||||
"daily": [
|
||||
@@ -10029,7 +10029,7 @@
|
||||
"volume": 13032847.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044900.4552383
|
||||
"updated_at": 1783051633.4890287
|
||||
},
|
||||
"00968": {
|
||||
"daily": [
|
||||
@@ -11294,7 +11294,7 @@
|
||||
"volume": 19422000.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044902.281637
|
||||
"updated_at": 1783045247.978355
|
||||
},
|
||||
"00981": {
|
||||
"daily": [
|
||||
@@ -12559,7 +12559,7 @@
|
||||
"volume": 60114819.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044904.170763
|
||||
"updated_at": 1783051638.5061805
|
||||
},
|
||||
"01070": {
|
||||
"daily": [
|
||||
@@ -13824,7 +13824,7 @@
|
||||
"volume": 690000.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044904.831619
|
||||
"updated_at": 1783045568.5510702
|
||||
},
|
||||
"01088": {
|
||||
"daily": [
|
||||
@@ -15089,7 +15089,7 @@
|
||||
"volume": 2870057.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044905.7026997
|
||||
"updated_at": 1783051643.521601
|
||||
},
|
||||
"01211": {
|
||||
"daily": [
|
||||
@@ -16354,7 +16354,7 @@
|
||||
"volume": 13286402.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044907.9523368
|
||||
"updated_at": 1783051648.5394585
|
||||
},
|
||||
"01478": {
|
||||
"daily": [
|
||||
@@ -17619,7 +17619,7 @@
|
||||
"volume": 1618000.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044914.9662342
|
||||
"updated_at": 1783051653.5516405
|
||||
},
|
||||
"01888": {
|
||||
"daily": [
|
||||
@@ -18884,7 +18884,7 @@
|
||||
"volume": 29941901.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044916.768711
|
||||
"updated_at": 1783051658.5684023
|
||||
},
|
||||
"02202": {
|
||||
"daily": [
|
||||
@@ -20149,7 +20149,7 @@
|
||||
"volume": 19786580.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044922.7405882
|
||||
"updated_at": 1783051663.581557
|
||||
},
|
||||
"02318": {
|
||||
"daily": [
|
||||
@@ -21414,7 +21414,7 @@
|
||||
"volume": 11523457.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044925.0042746
|
||||
"updated_at": 1783045619.0956357
|
||||
},
|
||||
"02359": {
|
||||
"daily": [
|
||||
@@ -22679,7 +22679,7 @@
|
||||
"volume": 1730975.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044933.7568398
|
||||
"updated_at": 1783045626.3903263
|
||||
},
|
||||
"02388": {
|
||||
"daily": [
|
||||
@@ -23944,7 +23944,7 @@
|
||||
"volume": 3823095.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044937.2679691
|
||||
"updated_at": 1783045637.5225153
|
||||
},
|
||||
"02628": {
|
||||
"daily": [
|
||||
@@ -25209,7 +25209,7 @@
|
||||
"volume": 23110112.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044942.9842496
|
||||
"updated_at": 1783045647.7615216
|
||||
},
|
||||
"06160": {
|
||||
"daily": [
|
||||
@@ -26474,7 +26474,7 @@
|
||||
"volume": 2575221.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044948.6934419
|
||||
"updated_at": 1783045655.0431929
|
||||
},
|
||||
"06869": {
|
||||
"daily": [
|
||||
@@ -27739,7 +27739,7 @@
|
||||
"volume": 15066251.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044950.480943
|
||||
"updated_at": 1783051668.878092
|
||||
},
|
||||
"09868": {
|
||||
"daily": [
|
||||
@@ -29004,7 +29004,7 @@
|
||||
"volume": 10168056.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044951.3377807
|
||||
"updated_at": 1783045674.3812273
|
||||
},
|
||||
"09988": {
|
||||
"daily": [
|
||||
@@ -30269,7 +30269,7 @@
|
||||
"volume": 35148396.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044956.074646
|
||||
"updated_at": 1783045677.2402296
|
||||
},
|
||||
"300035": {
|
||||
"daily": [
|
||||
@@ -31526,7 +31526,7 @@
|
||||
"volume": 230937.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044957.8285887
|
||||
"updated_at": 1783051674.5847087
|
||||
},
|
||||
"300124": {
|
||||
"daily": [
|
||||
@@ -32783,7 +32783,7 @@
|
||||
"volume": 722493.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044958.4761739
|
||||
"updated_at": 1783051344.573262
|
||||
},
|
||||
"300308": {
|
||||
"daily": [
|
||||
@@ -34040,7 +34040,7 @@
|
||||
"volume": 389058.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044962.1302354
|
||||
"updated_at": 1783051680.1109805
|
||||
},
|
||||
"300548": {
|
||||
"daily": [
|
||||
@@ -35297,7 +35297,7 @@
|
||||
"volume": 242727.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044974.5997705
|
||||
"updated_at": 1783051685.6375031
|
||||
},
|
||||
"300750": {
|
||||
"daily": [
|
||||
@@ -36554,7 +36554,7 @@
|
||||
"volume": 551212.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044988.1779277
|
||||
"updated_at": 1783051690.893728
|
||||
},
|
||||
"301308": {
|
||||
"daily": [
|
||||
@@ -37811,7 +37811,7 @@
|
||||
"volume": 296230.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783044993.181729
|
||||
"updated_at": 1783051380.6922731
|
||||
},
|
||||
"518880": {
|
||||
"daily": [
|
||||
@@ -39068,7 +39068,7 @@
|
||||
"volume": 3915247.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045000.125372
|
||||
"updated_at": 1783051695.915503
|
||||
},
|
||||
"600519": {
|
||||
"daily": [
|
||||
@@ -40325,7 +40325,7 @@
|
||||
"volume": 64803.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045000.7318592
|
||||
"updated_at": 1783051561.7579694
|
||||
},
|
||||
"600563": {
|
||||
"daily": [
|
||||
@@ -41582,7 +41582,7 @@
|
||||
"volume": 180947.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045002.30473
|
||||
"updated_at": 1783051700.9725504
|
||||
},
|
||||
"601318": {
|
||||
"daily": [
|
||||
@@ -42839,7 +42839,7 @@
|
||||
"volume": 1746202.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045007.7435973
|
||||
"updated_at": 1783051575.149798
|
||||
},
|
||||
"601899": {
|
||||
"daily": [
|
||||
@@ -44096,7 +44096,7 @@
|
||||
"volume": 4780454.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045008.845117
|
||||
"updated_at": 1783051705.9927075
|
||||
},
|
||||
"688411": {
|
||||
"daily": [
|
||||
@@ -45353,7 +45353,7 @@
|
||||
"volume": 13672788.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045009.6267552
|
||||
"updated_at": 1783051711.208435
|
||||
},
|
||||
"688630": {
|
||||
"daily": [
|
||||
@@ -46610,7 +46610,7 @@
|
||||
"volume": 9660790.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045010.4224615
|
||||
"updated_at": 1783051587.7339969
|
||||
},
|
||||
"688639": {
|
||||
"daily": [
|
||||
@@ -47867,7 +47867,7 @@
|
||||
"volume": 13996588.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045016.168726
|
||||
"updated_at": 1783051733.2346125
|
||||
},
|
||||
"688802": {
|
||||
"daily": [
|
||||
@@ -49092,6 +49092,6 @@
|
||||
"volume": 3202146.0
|
||||
}
|
||||
],
|
||||
"updated_at": 1783045018.4521751
|
||||
"updated_at": 1783051743.6900556
|
||||
}
|
||||
}
|
||||
+67
-67
@@ -5,9 +5,9 @@
|
||||
"name": "中际旭创",
|
||||
"shares": 100,
|
||||
"cost": 1316.53,
|
||||
"price": 1145.31,
|
||||
"market_value": 115899.0,
|
||||
"change_pct": 0.2,
|
||||
"price": 1157.97,
|
||||
"market_value": 115797.0,
|
||||
"change_pct": 1.31,
|
||||
"currency": "CNY",
|
||||
"position_pct": 15.27,
|
||||
"_currency": "CNY"
|
||||
@@ -16,10 +16,10 @@
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"shares": 500,
|
||||
"cost": 263.72,
|
||||
"price": 202.6,
|
||||
"market_value": 87825.0,
|
||||
"change_pct": 2.34,
|
||||
"cost": 228.65,
|
||||
"price": 178.26,
|
||||
"market_value": 102700.0,
|
||||
"change_pct": 3.859,
|
||||
"currency": "HKD",
|
||||
"position_pct": 13.47,
|
||||
"_currency": "HKD"
|
||||
@@ -28,10 +28,10 @@
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"shares": 11000,
|
||||
"cost": 13.47,
|
||||
"price": 6.94,
|
||||
"market_value": 66110.0,
|
||||
"change_pct": 3.27,
|
||||
"cost": 11.68,
|
||||
"price": 6.11,
|
||||
"market_value": 77550.0,
|
||||
"change_pct": 4.911,
|
||||
"currency": "HKD",
|
||||
"position_pct": 7.97,
|
||||
"_currency": "HKD"
|
||||
@@ -41,9 +41,9 @@
|
||||
"name": "紫金矿业",
|
||||
"shares": 2400,
|
||||
"cost": 39.89,
|
||||
"price": 28.05,
|
||||
"market_value": 66528.0,
|
||||
"change_pct": 6.65,
|
||||
"price": 28.04,
|
||||
"market_value": 67296.0,
|
||||
"change_pct": 6.62,
|
||||
"currency": "CNY",
|
||||
"position_pct": 7.34,
|
||||
"_currency": "CNY"
|
||||
@@ -53,9 +53,9 @@
|
||||
"name": "海博思创",
|
||||
"shares": 200,
|
||||
"cost": 266.95,
|
||||
"price": 257.0,
|
||||
"market_value": 51770.00000000001,
|
||||
"change_pct": 0.5,
|
||||
"price": 257.52,
|
||||
"market_value": 51504.0,
|
||||
"change_pct": 0.71,
|
||||
"currency": "CNY",
|
||||
"position_pct": 6.31,
|
||||
"_currency": "CNY"
|
||||
@@ -65,9 +65,9 @@
|
||||
"name": "中芯国际",
|
||||
"shares": 300,
|
||||
"cost": 126.07,
|
||||
"price": 140.66,
|
||||
"market_value": 43011.0,
|
||||
"change_pct": -2.39,
|
||||
"price": 143.65,
|
||||
"market_value": 43095.0,
|
||||
"change_pct": -0.31,
|
||||
"currency": "CNY",
|
||||
"position_pct": 5.44,
|
||||
"_currency": "CNY"
|
||||
@@ -76,10 +76,10 @@
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"shares": 500,
|
||||
"cost": 88.23,
|
||||
"price": 86.45,
|
||||
"market_value": 42450.0,
|
||||
"change_pct": 3.16,
|
||||
"cost": 76.5,
|
||||
"price": 74.17,
|
||||
"market_value": 42600.0,
|
||||
"change_pct": 2.088,
|
||||
"currency": "HKD",
|
||||
"position_pct": 5.28,
|
||||
"_currency": "HKD"
|
||||
@@ -90,7 +90,7 @@
|
||||
"shares": 2800,
|
||||
"cost": 21.51,
|
||||
"price": 16.69,
|
||||
"market_value": 46871.99999999999,
|
||||
"market_value": 46732.0,
|
||||
"change_pct": -1.53,
|
||||
"currency": "CNY",
|
||||
"position_pct": 5.25,
|
||||
@@ -101,9 +101,9 @@
|
||||
"name": "宁德时代",
|
||||
"shares": 100,
|
||||
"cost": 401.78,
|
||||
"price": 386.2,
|
||||
"market_value": 38427.0,
|
||||
"change_pct": 1.01,
|
||||
"price": 384.16,
|
||||
"market_value": 38416.0,
|
||||
"change_pct": 0.47,
|
||||
"currency": "CNY",
|
||||
"position_pct": 4.64,
|
||||
"_currency": "CNY"
|
||||
@@ -112,10 +112,10 @@
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"shares": 600,
|
||||
"cost": 104.87,
|
||||
"price": 82.5,
|
||||
"market_value": 42918.0,
|
||||
"change_pct": 5.36,
|
||||
"cost": 90.92,
|
||||
"price": 71.66,
|
||||
"market_value": 49560.0,
|
||||
"change_pct": 5.556,
|
||||
"currency": "HKD",
|
||||
"position_pct": 4.62,
|
||||
"_currency": "HKD"
|
||||
@@ -124,10 +124,10 @@
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"shares": 19700,
|
||||
"cost": 4.67,
|
||||
"price": 2.34,
|
||||
"market_value": 39794.0,
|
||||
"change_pct": 4.93,
|
||||
"cost": 4.05,
|
||||
"price": 2.02,
|
||||
"market_value": 45704.0,
|
||||
"change_pct": 4.484,
|
||||
"currency": "HKD",
|
||||
"position_pct": 4.6,
|
||||
"_currency": "HKD"
|
||||
@@ -137,9 +137,9 @@
|
||||
"name": "腾讯",
|
||||
"shares": 100,
|
||||
"cost": null,
|
||||
"price": 442.2,
|
||||
"market_value": 38269.0,
|
||||
"change_pct": 2.79,
|
||||
"price": 379.05,
|
||||
"market_value": 43700.0,
|
||||
"change_pct": 1.627,
|
||||
"currency": "HKD",
|
||||
"position_pct": null,
|
||||
"_currency": "HKD"
|
||||
@@ -148,10 +148,10 @@
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"shares": 500,
|
||||
"cost": 75.94,
|
||||
"price": 79.8,
|
||||
"market_value": 34570.0,
|
||||
"change_pct": -0.75,
|
||||
"cost": 65.84,
|
||||
"price": 69.1,
|
||||
"market_value": 39825.0,
|
||||
"change_pct": -0.871,
|
||||
"currency": "HKD",
|
||||
"position_pct": 4.2,
|
||||
"_currency": "HKD"
|
||||
@@ -161,9 +161,9 @@
|
||||
"name": "长芯博创",
|
||||
"shares": 100,
|
||||
"cost": 231.46,
|
||||
"price": 224.15,
|
||||
"market_value": 22623.0,
|
||||
"change_pct": 0.97,
|
||||
"price": 227.0,
|
||||
"market_value": 22700.0,
|
||||
"change_pct": 2.25,
|
||||
"currency": "CNY",
|
||||
"position_pct": 3.2,
|
||||
"_currency": "CNY"
|
||||
@@ -173,9 +173,9 @@
|
||||
"name": "黄金ETF华安",
|
||||
"shares": 2400,
|
||||
"cost": 12.19,
|
||||
"price": 8.68,
|
||||
"market_value": 20832.0,
|
||||
"change_pct": 2.4,
|
||||
"price": 8.69,
|
||||
"market_value": 20856.0,
|
||||
"change_pct": 2.49,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.45,
|
||||
"_currency": "CNY"
|
||||
@@ -185,9 +185,9 @@
|
||||
"name": "中科电气",
|
||||
"shares": 1400,
|
||||
"cost": 22.29,
|
||||
"price": 14.45,
|
||||
"market_value": 20118.0,
|
||||
"change_pct": 1.98,
|
||||
"price": 14.3,
|
||||
"market_value": 20020.0,
|
||||
"change_pct": 0.92,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.42,
|
||||
"_currency": "CNY"
|
||||
@@ -197,9 +197,9 @@
|
||||
"name": "模塑科技",
|
||||
"shares": 1400,
|
||||
"cost": 14.83,
|
||||
"price": 17.55,
|
||||
"market_value": 24304.0,
|
||||
"change_pct": 4.03,
|
||||
"price": 17.37,
|
||||
"market_value": 24318.0,
|
||||
"change_pct": 2.96,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.41,
|
||||
"_currency": "CNY"
|
||||
@@ -209,9 +209,9 @@
|
||||
"name": "法拉电子",
|
||||
"shares": 100,
|
||||
"cost": 147.18,
|
||||
"price": 157.0,
|
||||
"market_value": 15900.0,
|
||||
"change_pct": -4.44,
|
||||
"price": 161.71,
|
||||
"market_value": 16171.0,
|
||||
"change_pct": -1.58,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.3,
|
||||
"_currency": "CNY"
|
||||
@@ -220,21 +220,21 @@
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"shares": 500,
|
||||
"cost": 45.89,
|
||||
"price": 39.7,
|
||||
"market_value": 17220.0,
|
||||
"change_pct": 0.25,
|
||||
"cost": 39.79,
|
||||
"price": 34.84,
|
||||
"market_value": 20090.0,
|
||||
"change_pct": 1.465,
|
||||
"currency": "HKD",
|
||||
"position_pct": 2.14,
|
||||
"_currency": "HKD"
|
||||
}
|
||||
],
|
||||
"total_assets": 961185.34,
|
||||
"total_mv": 829063.41,
|
||||
"total_assets": 864781.03,
|
||||
"total_mv": 784305.03,
|
||||
"stock_value": null,
|
||||
"cash": 132121.93,
|
||||
"cash": 80476.0,
|
||||
"frozen_cash": 0.0,
|
||||
"position_pct": 86.25,
|
||||
"position_pct": 90.69,
|
||||
"currency": "CNY",
|
||||
"updated_at": "2026-07-03 10:22"
|
||||
"updated_at": "2026-07-03 12:10"
|
||||
}
|
||||
@@ -8309,6 +8309,806 @@
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:20:24.988282",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "001309",
|
||||
"name": "德明利",
|
||||
"event_type": "entry_zone",
|
||||
"price": 833.99,
|
||||
"trigger_value": "818.3~838.69",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:22:24.916521",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "600519",
|
||||
"name": "贵州茅台",
|
||||
"event_type": "entry_zone",
|
||||
"price": 1203.0,
|
||||
"trigger_value": "1179.92~1206.14",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:22:25.114242",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000657",
|
||||
"name": "中钨高新",
|
||||
"event_type": "entry_zone",
|
||||
"price": 87.93,
|
||||
"trigger_value": "86.73~90.27",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:22:55.561315",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.24,
|
||||
"trigger_value": "5.14~5.26",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:22:55.893638",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "688630",
|
||||
"name": "芯碁微装",
|
||||
"event_type": "entry_zone",
|
||||
"price": 486.0,
|
||||
"trigger_value": "475.2~486.17",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:22:57.837686",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000657",
|
||||
"name": "中钨高新",
|
||||
"event_type": "entry_zone",
|
||||
"price": 88.89,
|
||||
"trigger_value": "86.27~89.79",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:27:25.917807",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.23,
|
||||
"trigger_value": "5.14~5.25",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:27:31.525100",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02359",
|
||||
"name": "药明康德",
|
||||
"event_type": "entry_zone",
|
||||
"price": 152.9,
|
||||
"trigger_value": "149.45~154.86",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:28:16.907063",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06160",
|
||||
"name": "百济神州",
|
||||
"event_type": "entry_zone",
|
||||
"price": 181.6,
|
||||
"trigger_value": "179.63~183.4",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:28:19.403509",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 200.4,
|
||||
"trigger_value": "182.52~212.94",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:28:19.456833",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "09868",
|
||||
"name": "小鹏汽车-W",
|
||||
"event_type": "entry_zone",
|
||||
"price": 50.25,
|
||||
"trigger_value": "50.18~51.14",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:28:19.522580",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "09988",
|
||||
"name": "阿里巴巴-W",
|
||||
"event_type": "entry_zone",
|
||||
"price": 94.1,
|
||||
"trigger_value": "93.49~94.92",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:28:19.602866",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000657",
|
||||
"name": "中钨高新",
|
||||
"event_type": "entry_zone",
|
||||
"price": 88.67,
|
||||
"trigger_value": "86.83~90.37",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:30:19.791987",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.25,
|
||||
"trigger_value": "5.14~5.25",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:30:25.159354",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000657",
|
||||
"name": "中钨高新",
|
||||
"event_type": "entry_zone",
|
||||
"price": 88.79,
|
||||
"trigger_value": "86.83~90.37",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:34:15.409862",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.25,
|
||||
"trigger_value": "5.14~5.25",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:34:15.537060",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "300124",
|
||||
"name": "汇川技术",
|
||||
"event_type": "entry_zone",
|
||||
"price": 71.24,
|
||||
"trigger_value": "70.48~71.55",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:38:29.251662",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000700",
|
||||
"name": "模塑科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 17.31,
|
||||
"trigger_value": "0~18.93",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T10:38:39.296028",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "300124",
|
||||
"name": "汇川技术",
|
||||
"event_type": "entry_zone",
|
||||
"price": 71.23,
|
||||
"trigger_value": "70.48~71.55",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:38:42.781286",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "600563",
|
||||
"name": "法拉电子",
|
||||
"event_type": "entry_zone",
|
||||
"price": 159.56,
|
||||
"trigger_value": "0~171.96",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T10:38:42.981115",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "601899",
|
||||
"name": "紫金矿业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 28.24,
|
||||
"trigger_value": "0~30.38",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T10:38:43.067796",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "688981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 142.5,
|
||||
"trigger_value": "0~153.58",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T10:38:43.142300",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.26,
|
||||
"trigger_value": "5.15~5.26",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:44:30.388534",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.26,
|
||||
"trigger_value": "5.15~5.26",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:48:28.599391",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "000711",
|
||||
"name": "ST京蓝",
|
||||
"event_type": "entry_zone",
|
||||
"price": 5.26,
|
||||
"trigger_value": "5.15~5.26",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T10:50:35.989841",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "300124",
|
||||
"name": "汇川技术",
|
||||
"event_type": "entry_zone",
|
||||
"price": 71.12,
|
||||
"trigger_value": "69.93~71.47",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:10:13.492666",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "688795",
|
||||
"name": "摩尔线程-U",
|
||||
"event_type": "entry_zone",
|
||||
"price": 656.41,
|
||||
"trigger_value": "640.4~661.55",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:10:14.388968",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "688802",
|
||||
"name": "沐曦股份-U",
|
||||
"event_type": "entry_zone",
|
||||
"price": 747.1,
|
||||
"trigger_value": "735.17~758.83",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:10:14.569334",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "301308",
|
||||
"name": "江波龙",
|
||||
"event_type": "entry_zone",
|
||||
"price": 619.0,
|
||||
"trigger_value": "613.93~620.18",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:13:01.494442",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯",
|
||||
"event_type": "entry_zone",
|
||||
"price": 437.0,
|
||||
"trigger_value": "427.28~444.72",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:23.996542",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯",
|
||||
"event_type": "entry_zone",
|
||||
"price": 437.0,
|
||||
"trigger_value": "0~470.88",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:24.624140",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 80.7,
|
||||
"trigger_value": "72.45~84.53",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:24.732225",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 80.7,
|
||||
"trigger_value": "0~86.94",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:25.084481",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"event_type": "entry_zone",
|
||||
"price": 40.16,
|
||||
"trigger_value": "35.93~41.92",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:28.993449",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"event_type": "entry_zone",
|
||||
"price": 40.16,
|
||||
"trigger_value": "0~43.11",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:29.629844",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"event_type": "entry_zone",
|
||||
"price": 82.6,
|
||||
"trigger_value": "74.03~86.36",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:29.965400",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"event_type": "entry_zone",
|
||||
"price": 82.6,
|
||||
"trigger_value": "0~88.83",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:30.114407",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 7.05,
|
||||
"trigger_value": "6.32~7.37",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:30.304612",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 7.05,
|
||||
"trigger_value": "0~7.58",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:35.358231",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"event_type": "entry_zone",
|
||||
"price": 85.6,
|
||||
"trigger_value": "78.08~91.09",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:40.394717",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"event_type": "entry_zone",
|
||||
"price": 85.6,
|
||||
"trigger_value": "0~93.69",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:45.434897",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 2.32,
|
||||
"trigger_value": "2.07~2.42",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:14:50.523069",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 2.32,
|
||||
"trigger_value": "0~2.48",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:14:59.705931",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 206.8,
|
||||
"trigger_value": "186.12~217.14",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:15:00.287164",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 206.8,
|
||||
"trigger_value": "0~223.34",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:15:00.430498",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "301308",
|
||||
"name": "江波龙",
|
||||
"event_type": "entry_zone",
|
||||
"price": 618.02,
|
||||
"trigger_value": "611.31~619.68",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:22:07.308667",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "300124",
|
||||
"name": "汇川技术",
|
||||
"event_type": "entry_zone",
|
||||
"price": 71.11,
|
||||
"trigger_value": "69.91~71.42",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:22:20.859749",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "301308",
|
||||
"name": "江波龙",
|
||||
"event_type": "entry_zone",
|
||||
"price": 617.89,
|
||||
"trigger_value": "611.31~619.68",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:22:22.199650",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯",
|
||||
"event_type": "entry_zone",
|
||||
"price": 438.6,
|
||||
"trigger_value": "427.87~445.33",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:11.456641",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯",
|
||||
"event_type": "entry_zone",
|
||||
"price": 438.6,
|
||||
"trigger_value": "0~471.53",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:18.294805",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 79.9,
|
||||
"trigger_value": "72.09~84.11",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:26.253956",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 79.9,
|
||||
"trigger_value": "0~86.51",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:29.577070",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"event_type": "entry_zone",
|
||||
"price": 40.3,
|
||||
"trigger_value": "36.36~42.42",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:30.340343",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"event_type": "entry_zone",
|
||||
"price": 40.3,
|
||||
"trigger_value": "0~43.63",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:32.714618",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"event_type": "entry_zone",
|
||||
"price": 82.6,
|
||||
"trigger_value": "74.39~86.78",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:36.541205",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"event_type": "entry_zone",
|
||||
"price": 82.6,
|
||||
"trigger_value": "0~89.26",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:36.686582",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 7.1,
|
||||
"trigger_value": "6.35~7.41",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:37.242356",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 7.1,
|
||||
"trigger_value": "0~7.62",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:37.443395",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"event_type": "entry_zone",
|
||||
"price": 85.65,
|
||||
"trigger_value": "76.77~89.56",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:38.817321",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"event_type": "entry_zone",
|
||||
"price": 85.65,
|
||||
"trigger_value": "0~92.12",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:39.528996",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 2.34,
|
||||
"trigger_value": "2.1~2.45",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:39.720487",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 2.34,
|
||||
"trigger_value": "0~2.52",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:41.301094",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 206.6,
|
||||
"trigger_value": "184.68~215.46",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:52:45.543223",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 206.6,
|
||||
"trigger_value": "0~221.62",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:52:47.160071",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯",
|
||||
"event_type": "entry_zone",
|
||||
"price": 437.8,
|
||||
"trigger_value": "427.87~445.33",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:14.974895",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00700",
|
||||
"name": "腾讯",
|
||||
"event_type": "entry_zone",
|
||||
"price": 437.8,
|
||||
"trigger_value": "0~471.53",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:17.221621",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 79.7,
|
||||
"trigger_value": "72.09~84.11",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:22.810159",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "00981",
|
||||
"name": "中芯国际",
|
||||
"event_type": "entry_zone",
|
||||
"price": 79.7,
|
||||
"trigger_value": "0~86.51",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:24.193676",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"event_type": "entry_zone",
|
||||
"price": 40.22,
|
||||
"trigger_value": "36.36~42.42",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:24.477745",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01088",
|
||||
"name": "中国神华",
|
||||
"event_type": "entry_zone",
|
||||
"price": 40.22,
|
||||
"trigger_value": "0~43.63",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:24.655213",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"event_type": "entry_zone",
|
||||
"price": 82.8,
|
||||
"trigger_value": "74.39~86.78",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:25.091753",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01211",
|
||||
"name": "比亚迪股份",
|
||||
"event_type": "entry_zone",
|
||||
"price": 82.8,
|
||||
"trigger_value": "0~89.26",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:25.321463",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 7.08,
|
||||
"trigger_value": "6.35~7.41",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:26.301603",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01478",
|
||||
"name": "丘钛科技",
|
||||
"event_type": "entry_zone",
|
||||
"price": 7.08,
|
||||
"trigger_value": "0~7.62",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:29.534882",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"event_type": "entry_zone",
|
||||
"price": 85.7,
|
||||
"trigger_value": "76.77~89.56",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:29.649302",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "01888",
|
||||
"name": "建滔积层板",
|
||||
"event_type": "entry_zone",
|
||||
"price": 85.7,
|
||||
"trigger_value": "0~92.12",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:30.678788",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 2.34,
|
||||
"trigger_value": "2.1~2.45",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:30.768058",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "02202",
|
||||
"name": "万科企业",
|
||||
"event_type": "entry_zone",
|
||||
"price": 2.34,
|
||||
"trigger_value": "0~2.52",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:30.829554",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 206.2,
|
||||
"trigger_value": "184.68~215.46",
|
||||
"event_label": "买入区间",
|
||||
"timestamp": "2026-07-03T11:54:30.916574",
|
||||
"date": "2026-07-03"
|
||||
},
|
||||
{
|
||||
"code": "06869",
|
||||
"name": "长飞光纤光缆",
|
||||
"event_type": "entry_zone",
|
||||
"price": 206.2,
|
||||
"trigger_value": "0~221.62",
|
||||
"event_label": "止盈区间",
|
||||
"timestamp": "2026-07-03T11:54:31.001389",
|
||||
"date": "2026-07-03"
|
||||
}
|
||||
]
|
||||
}
|
||||
+29
-29
@@ -25,8 +25,8 @@
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 1215.52,
|
||||
"low": 1190.51,
|
||||
"close": 1203.6
|
||||
"low": 1190.5,
|
||||
"close": 1191.31
|
||||
}
|
||||
],
|
||||
"02202": [
|
||||
@@ -48,7 +48,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 53.0,
|
||||
"low": 51.7,
|
||||
"close": 52.5
|
||||
"close": 52.45
|
||||
}
|
||||
],
|
||||
"601318": [
|
||||
@@ -61,8 +61,8 @@
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 50.2,
|
||||
"low": 48.8,
|
||||
"close": 49.12
|
||||
"low": 48.6,
|
||||
"close": 48.7
|
||||
}
|
||||
],
|
||||
"02359": [
|
||||
@@ -76,7 +76,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 158.7,
|
||||
"low": 151.1,
|
||||
"close": 153.1
|
||||
"close": 152.5
|
||||
}
|
||||
],
|
||||
"02388": [
|
||||
@@ -90,7 +90,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 42.98,
|
||||
"low": 41.94,
|
||||
"close": 42.66
|
||||
"close": 42.68
|
||||
}
|
||||
],
|
||||
"02628": [
|
||||
@@ -104,7 +104,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 29.08,
|
||||
"low": 27.5,
|
||||
"close": 28.66
|
||||
"close": 28.64
|
||||
}
|
||||
],
|
||||
"688630": [
|
||||
@@ -116,9 +116,9 @@
|
||||
},
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 499.95,
|
||||
"high": 502.0,
|
||||
"low": 444.55,
|
||||
"close": 486.69
|
||||
"close": 495.95
|
||||
}
|
||||
],
|
||||
"06160": [
|
||||
@@ -132,7 +132,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 183.9,
|
||||
"low": 171.9,
|
||||
"close": 183.3
|
||||
"close": 182.9
|
||||
}
|
||||
],
|
||||
"06869": [
|
||||
@@ -154,7 +154,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 687.04,
|
||||
"low": 633.01,
|
||||
"close": 642.05
|
||||
"close": 654.0
|
||||
}
|
||||
],
|
||||
"09868": [
|
||||
@@ -168,7 +168,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 53.7,
|
||||
"low": 49.3,
|
||||
"close": 51.1
|
||||
"close": 50.9
|
||||
}
|
||||
],
|
||||
"09988": [
|
||||
@@ -182,7 +182,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 97.4,
|
||||
"low": 93.55,
|
||||
"close": 95.5
|
||||
"close": 95.15
|
||||
}
|
||||
],
|
||||
"688802": [
|
||||
@@ -194,9 +194,9 @@
|
||||
},
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 733.99,
|
||||
"high": 753.88,
|
||||
"low": 713.0,
|
||||
"close": 719.01
|
||||
"close": 744.0
|
||||
}
|
||||
],
|
||||
"300124": [
|
||||
@@ -208,9 +208,9 @@
|
||||
},
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 71.79,
|
||||
"high": 72.38,
|
||||
"low": 67.31,
|
||||
"close": 71.07
|
||||
"close": 72.29
|
||||
}
|
||||
],
|
||||
"000657": [
|
||||
@@ -223,8 +223,8 @@
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 101.5,
|
||||
"low": 87.92,
|
||||
"close": 88.6
|
||||
"low": 87.88,
|
||||
"close": 91.88
|
||||
}
|
||||
],
|
||||
"000711": [
|
||||
@@ -238,7 +238,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 5.26,
|
||||
"low": 4.87,
|
||||
"close": 5.25
|
||||
"close": 5.26
|
||||
}
|
||||
],
|
||||
"001309": [
|
||||
@@ -250,9 +250,9 @@
|
||||
},
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 872.83,
|
||||
"high": 882.5,
|
||||
"low": 795.0,
|
||||
"close": 835.99
|
||||
"close": 874.23
|
||||
}
|
||||
],
|
||||
"002594": [
|
||||
@@ -266,7 +266,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 87.28,
|
||||
"low": 81.9,
|
||||
"close": 86.88
|
||||
"close": 86.56
|
||||
}
|
||||
],
|
||||
"00700": [
|
||||
@@ -288,7 +288,7 @@
|
||||
"date": "2026-07-03",
|
||||
"high": 2.15,
|
||||
"low": 2.03,
|
||||
"close": 2.11
|
||||
"close": 2.1
|
||||
}
|
||||
],
|
||||
"00981": [
|
||||
@@ -308,9 +308,9 @@
|
||||
},
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 13.31,
|
||||
"high": 13.39,
|
||||
"low": 12.63,
|
||||
"close": 13.31
|
||||
"close": 13.36
|
||||
}
|
||||
],
|
||||
"01088": [
|
||||
@@ -332,9 +332,9 @@
|
||||
"301308": [
|
||||
{
|
||||
"date": "2026-07-03",
|
||||
"high": 612.3,
|
||||
"high": 631.56,
|
||||
"low": 574.1,
|
||||
"close": 600.8
|
||||
"close": 620.0
|
||||
}
|
||||
]
|
||||
}
|
||||
+35
-83
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
mo_data.py — MoFin 统一数据读取层
|
||||
mo_data.py — MoFin 统一数据层(纯 DB)
|
||||
|
||||
替代 json.load(open(portfolio.json)) 等直接读 JSON 文件的方式。
|
||||
所有数据从 DB 读取,币种字段强制存在。
|
||||
所有数据从 SQLite 读取。不做 JSON fallback。
|
||||
JSON 文件已弃用,仅保留为历史备份。
|
||||
|
||||
用法:
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
@@ -13,16 +13,11 @@ mo_data.py — MoFin 统一数据读取层
|
||||
wl = read_watchlist() # 返回和 watchlist.json 一样的 dict 结构
|
||||
"""
|
||||
|
||||
import sqlite3, json, os
|
||||
import sqlite3, json
|
||||
from datetime import datetime
|
||||
|
||||
DB_PATH = '/home/hmo/web-dashboard/data/mofin.db'
|
||||
|
||||
# JSON 文件路径(冷备,仅当 DB 不可用时 fallback)
|
||||
PORTFOLIO_JSON = '/home/hmo/web-dashboard/data/portfolio.json'
|
||||
DECISIONS_JSON = '/home/hmo/web-dashboard/data/decisions.json'
|
||||
WATCHLIST_JSON = '/home/hmo/web-dashboard/data/watchlist.json'
|
||||
|
||||
|
||||
def _get_db():
|
||||
db = sqlite3.connect(DB_PATH)
|
||||
@@ -30,13 +25,11 @@ def _get_db():
|
||||
return db
|
||||
|
||||
|
||||
# ── portfolio.json → holdings + portfolio_summary ─────────────────
|
||||
# ── portfolio ─────────────────────────────────────────────────────
|
||||
|
||||
def read_portfolio():
|
||||
"""返回 portfolio.json 等价 dict。DB 优先,JSON 冷备。"""
|
||||
try:
|
||||
"""返回 portfolio.json 等价 dict。纯 DB。"""
|
||||
db = _get_db()
|
||||
# holdings
|
||||
rows = db.execute(
|
||||
"SELECT code, name, shares, cost, price, market_value, "
|
||||
"change_pct, currency, position_pct "
|
||||
@@ -45,10 +38,9 @@ def read_portfolio():
|
||||
holdings = []
|
||||
for r in rows:
|
||||
h = dict(r)
|
||||
h['_currency'] = h.get('currency', 'CNY') # 兼容旧代码的 _currency 字段名
|
||||
h['_currency'] = h.get('currency', 'CNY')
|
||||
holdings.append(h)
|
||||
|
||||
# summary
|
||||
sum_row = db.execute("SELECT * FROM portfolio_summary WHERE id=1").fetchone()
|
||||
summary = dict(sum_row) if sum_row else {}
|
||||
|
||||
@@ -65,21 +57,19 @@ def read_portfolio():
|
||||
"currency": summary.get("currency", "CNY"),
|
||||
"updated_at": summary.get("updated_at", ""),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# JSON 冷备
|
||||
try:
|
||||
return json.load(open(PORTFOLIO_JSON, encoding='utf-8'))
|
||||
except:
|
||||
return {"holdings": [], "total_assets": 0, "cash": 0, "frozen_cash": 0}
|
||||
|
||||
|
||||
# ── decisions.json → holding_strategies ────────────────────────────
|
||||
# ── decisions ─────────────────────────────────────────────────────
|
||||
|
||||
def _parse_json(val, default):
|
||||
if val:
|
||||
try: return json.loads(val)
|
||||
except: pass
|
||||
return default
|
||||
|
||||
|
||||
def read_decisions():
|
||||
"""返回 decisions.json 等价 dict。DB 优先,JSON 冷备。"""
|
||||
try:
|
||||
"""返回 decisions.json 等价 dict。纯 DB。"""
|
||||
db = _get_db()
|
||||
rows = db.execute(
|
||||
"SELECT code, name, version, price, cost, shares, "
|
||||
@@ -87,7 +77,10 @@ def read_decisions():
|
||||
"currency, strategy_type, action, timing_signal, "
|
||||
"rr_ratio, tech_snapshot, stock_category, sector_context, "
|
||||
"status, trigger_json, changelog_json, source, reason, "
|
||||
"created_at, updated_at "
|
||||
"created_at, updated_at, "
|
||||
"avg_price, decision_timestamp, note, quality_check, "
|
||||
"quality_checked_at, quality_issues_json, position_advice, "
|
||||
"signal_factors_json, time_horizon, decision_type "
|
||||
"FROM holding_strategies WHERE status IN ('active','updated') "
|
||||
"ORDER BY code"
|
||||
).fetchall()
|
||||
@@ -95,22 +88,12 @@ def read_decisions():
|
||||
decisions = []
|
||||
for r in rows:
|
||||
d = dict(r)
|
||||
# 还原 trigger 字段
|
||||
if d.get('trigger_json'):
|
||||
try:
|
||||
d['trigger'] = json.loads(d['trigger_json'])
|
||||
except:
|
||||
d['trigger'] = {}
|
||||
else:
|
||||
d['trigger'] = {}
|
||||
# 还原 changelog 字段
|
||||
if d.get('changelog_json'):
|
||||
try:
|
||||
d['changelog'] = json.loads(d['changelog_json'])
|
||||
except:
|
||||
d['changelog'] = []
|
||||
else:
|
||||
d['changelog'] = []
|
||||
d['trigger'] = _parse_json(r['trigger_json'], {})
|
||||
d['changelog'] = _parse_json(r['changelog_json'], [])
|
||||
d['quality_issues'] = _parse_json(r['quality_issues_json'], {})
|
||||
d['signal_factors'] = _parse_json(r['signal_factors_json'], [])
|
||||
d['timestamp'] = r['decision_timestamp'] or r['created_at'] or ''
|
||||
d['type'] = r['decision_type'] or r['strategy_type'] or '持仓策略'
|
||||
decisions.append(d)
|
||||
|
||||
db.close()
|
||||
@@ -120,20 +103,12 @@ def read_decisions():
|
||||
"total": len(decisions),
|
||||
"regenerated_at": datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
return json.load(open(DECISIONS_JSON, encoding='utf-8'))
|
||||
except:
|
||||
return {"decisions": [], "total": 0}
|
||||
|
||||
|
||||
# ── watchlist.json → watchlist_stocks ──────────────────────────────
|
||||
# ── watchlist ─────────────────────────────────────────────────────
|
||||
|
||||
def read_watchlist():
|
||||
"""返回 watchlist.json 等价 dict。DB 优先,JSON 冷备。"""
|
||||
try:
|
||||
"""返回 watchlist.json 等价 dict。纯 DB。"""
|
||||
db = _get_db()
|
||||
rows = db.execute(
|
||||
"SELECT code, name, price, entry_low, entry_high, "
|
||||
@@ -145,12 +120,9 @@ def read_watchlist():
|
||||
stocks = []
|
||||
for r in rows:
|
||||
s = dict(r)
|
||||
if s.get('source_detail'):
|
||||
try: s['source_detail'] = json.loads(s['source_detail'])
|
||||
except: pass
|
||||
if s.get('analysis_json'):
|
||||
try: s['analysis'] = json.loads(s['analysis_json'])
|
||||
except: s['analysis'] = {}
|
||||
s['source_detail'] = _parse_json(r['source_detail'], None)
|
||||
if r['analysis_json']:
|
||||
s['analysis'] = _parse_json(r['analysis_json'], {})
|
||||
else:
|
||||
s['analysis'] = {}
|
||||
stocks.append(s)
|
||||
@@ -161,45 +133,25 @@ def read_watchlist():
|
||||
"stocks": stocks,
|
||||
"updated_at": datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
}
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
return json.load(open(WATCHLIST_JSON, encoding='utf-8'))
|
||||
except:
|
||||
return {"stocks": []}
|
||||
|
||||
|
||||
# ── 便捷函数 ────────────────────────────────────────────────────────
|
||||
# ── 便捷别名 ───────────────────────────────────────────────────────
|
||||
|
||||
def read_portfolio_json():
|
||||
"""别名(兼容旧代码直接 import 后调用)"""
|
||||
return read_portfolio()
|
||||
|
||||
def read_decisions_json():
|
||||
"""别名"""
|
||||
return read_decisions()
|
||||
|
||||
def read_watchlist_json():
|
||||
"""别名"""
|
||||
return read_watchlist()
|
||||
|
||||
|
||||
# ── cash_log 写入 ─────────────────────────────────────────────────────
|
||||
# ── cash_log 写入 ──────────────────────────────────────────────────
|
||||
|
||||
def write_cash_log(cash_before, cash_after, frozen_before, frozen_after,
|
||||
source, note, verified=0):
|
||||
"""记录现金变更到 cash_log 表。
|
||||
|
||||
参数:
|
||||
cash_before/after — 变更前后可用现金
|
||||
frozen_before/after — 变更前后冻结资金
|
||||
source — 'screenshot'/'manual'/'trade'/'import_xls'
|
||||
note — 备注(如"卖出法拉电子200股")
|
||||
verified — 0=未验证 1=Dad已确认
|
||||
返回:
|
||||
log_id (int)
|
||||
"""
|
||||
"""记录现金变更到 cash_log 表。"""
|
||||
change_amount = round(cash_after - cash_before, 2) if cash_after is not None and cash_before is not None else 0
|
||||
db = sqlite3.connect(DB_PATH)
|
||||
try:
|
||||
@@ -217,7 +169,7 @@ def write_cash_log(cash_before, cash_after, frozen_before, frozen_after,
|
||||
db.close()
|
||||
|
||||
|
||||
# ── 自检 ────────────────────────────────────────────────────────────
|
||||
# ── 自检 ───────────────────────────────────────────────────────────
|
||||
|
||||
if __name__ == "__main__":
|
||||
pf = read_portfolio()
|
||||
|
||||
+50
-16
@@ -145,8 +145,8 @@ def init_all_tables(conn: sqlite3.Connection):
|
||||
code TEXT NOT NULL REFERENCES holdings(code),
|
||||
name TEXT,
|
||||
version INTEGER DEFAULT 1,
|
||||
price REAL, -- 当前价格
|
||||
cost REAL, -- 成本价
|
||||
price REAL,
|
||||
cost REAL,
|
||||
shares INTEGER DEFAULT 0,
|
||||
stop_loss REAL,
|
||||
take_profit REAL,
|
||||
@@ -154,20 +154,31 @@ def init_all_tables(conn: sqlite3.Connection):
|
||||
entry_high REAL,
|
||||
currency TEXT NOT NULL DEFAULT 'CNY' CHECK(currency IN ('CNY','HKD')),
|
||||
strategy_type TEXT DEFAULT 'holding',
|
||||
action TEXT, -- 买入/持有/卖出/观望
|
||||
timing_signal TEXT, -- 时机信号
|
||||
rr_ratio REAL, -- 盈亏比
|
||||
tech_snapshot TEXT, -- 技术面快照
|
||||
stock_category TEXT, -- 股票分类
|
||||
sector_context TEXT, -- 板块背景
|
||||
status TEXT DEFAULT 'active', -- active/updated/closed
|
||||
trigger_json TEXT, -- trigger JSON (entry_zone/stop_loss/take_profit_zone)
|
||||
changelog_json TEXT, -- changelog JSON 数组
|
||||
action TEXT,
|
||||
timing_signal TEXT,
|
||||
rr_ratio REAL,
|
||||
tech_snapshot TEXT,
|
||||
stock_category TEXT,
|
||||
sector_context TEXT,
|
||||
status TEXT DEFAULT 'active',
|
||||
trigger_json TEXT,
|
||||
changelog_json TEXT,
|
||||
source TEXT,
|
||||
reason TEXT,
|
||||
created_at TEXT DEFAULT (datetime('now','localtime')),
|
||||
updated_at TEXT,
|
||||
superseded_at TEXT
|
||||
superseded_at TEXT,
|
||||
-- 以下为 decisions.json→DB 迁移新增列
|
||||
avg_price REAL,
|
||||
decision_timestamp TEXT,
|
||||
note TEXT,
|
||||
quality_check TEXT,
|
||||
quality_checked_at TEXT,
|
||||
quality_issues_json TEXT,
|
||||
position_advice TEXT,
|
||||
signal_factors_json TEXT,
|
||||
time_horizon TEXT,
|
||||
decision_type TEXT
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_strategy_code ON holding_strategies(code);
|
||||
CREATE INDEX IF NOT EXISTS idx_strategy_status ON holding_strategies(status);
|
||||
@@ -954,7 +965,14 @@ def write_holding_strategy(conn, code: str, name: str, data: dict) -> tuple[bool
|
||||
"""写入持仓策略(替代 decisions.json 单条写入)。data 必须包含 currency。"""
|
||||
try:
|
||||
currency = data.get('currency', 'CNY')
|
||||
# DELETE + INSERT(避免依赖 code 的 UNIQUE 约束)
|
||||
# Serialize JSON fields
|
||||
import json as _json
|
||||
trigger_j = _json.dumps(data.get('trigger', {}), ensure_ascii=False) if isinstance(data.get('trigger'), dict) else str(data.get('trigger', '{}'))
|
||||
changelog_j = _json.dumps(data.get('changelog', []), ensure_ascii=False) if isinstance(data.get('changelog'), list) else str(data.get('changelog', '[]'))
|
||||
quality_issues_j = _json.dumps(data.get('quality_issues', {}), ensure_ascii=False) if isinstance(data.get('quality_issues'), dict) else data.get('quality_issues_json', '')
|
||||
signal_factors_j = _json.dumps(data.get('signal_factors', []), ensure_ascii=False) if isinstance(data.get('signal_factors'), list) else data.get('signal_factors_json', '')
|
||||
|
||||
# DELETE + INSERT
|
||||
conn.execute("DELETE FROM holding_strategies WHERE code=?", (code,))
|
||||
conn.execute("""
|
||||
INSERT INTO holding_strategies
|
||||
@@ -962,8 +980,13 @@ def write_holding_strategy(conn, code: str, name: str, data: dict) -> tuple[bool
|
||||
entry_low, entry_high, currency, strategy_type, action,
|
||||
timing_signal, rr_ratio, tech_snapshot, stock_category,
|
||||
sector_context, status, trigger_json, changelog_json,
|
||||
source, reason, updated_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,datetime('now','localtime'))
|
||||
source, reason, updated_at,
|
||||
avg_price, decision_timestamp, note, quality_check,
|
||||
quality_checked_at, quality_issues_json, position_advice,
|
||||
signal_factors_json, time_horizon, decision_type)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,
|
||||
datetime('now','localtime'),
|
||||
?,?,?,?,?,?,?,?,?)
|
||||
""", (
|
||||
code, name,
|
||||
data.get('version', 1), data.get('price'), data.get('cost'),
|
||||
@@ -973,8 +996,19 @@ def write_holding_strategy(conn, code: str, name: str, data: dict) -> tuple[bool
|
||||
data.get('timing_signal'), data.get('rr_ratio'),
|
||||
data.get('tech_snapshot'), data.get('stock_category'),
|
||||
data.get('sector_context'), data.get('status', 'active'),
|
||||
data.get('trigger_json'), data.get('changelog_json'),
|
||||
trigger_j, changelog_j,
|
||||
data.get('source'), data.get('reason'),
|
||||
# new columns
|
||||
data.get('avg_price', 0),
|
||||
data.get('timestamp') or data.get('created_at', ''),
|
||||
data.get('note', ''),
|
||||
data.get('quality_check', ''),
|
||||
data.get('quality_checked_at', ''),
|
||||
quality_issues_j,
|
||||
data.get('position_advice', ''),
|
||||
signal_factors_j,
|
||||
data.get('time_horizon', ''),
|
||||
data.get('type', data.get('strategy_type', 'holding')),
|
||||
))
|
||||
conn.commit()
|
||||
return True, f"策略 {code} 已写入"
|
||||
|
||||
+16
-15
@@ -12,12 +12,9 @@ from datetime import datetime
|
||||
|
||||
# ── MoFin unified model ──────────────────────────────────────────────
|
||||
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
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_price_event, write_watchlist_stock, write_holding_strategy
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
WATCHLIST_PATH = "/home/hmo/web-dashboard/data/watchlist.json"
|
||||
BREACH_PATH = "/home/hmo/.hermes/zone_breach.json"
|
||||
STATE_PATH = os.path.expanduser("~/.hermes/price_trigger_state.json")
|
||||
EVENTS_PATH = "/home/hmo/web-dashboard/data/price_events.json"
|
||||
@@ -280,14 +277,17 @@ def refresh_data_prices():
|
||||
import time; time.sleep((attempt+1)*1)
|
||||
else:
|
||||
print(f" [DB写入失败 3次重试后放弃] {e}", flush=True)
|
||||
# 保留 JSON 副本作为冷备
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
elif pf.get('updated_at'):
|
||||
try:
|
||||
last_ts = datetime.strptime(pf['updated_at'], '%Y-%m-%d %H:%M')
|
||||
if (datetime.now() - last_ts).total_seconds() > 600:
|
||||
pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
try:
|
||||
conn = get_conn()
|
||||
write_portfolio_summary(conn, pf)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -314,8 +314,6 @@ def refresh_data_prices():
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f" [DB watchlist写入失败] {e}", flush=True)
|
||||
# 保留 JSON 冷备
|
||||
json.dump(wl, open(WATCHLIST_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
|
||||
# --- 汇总值重算(使用 mo_models 唯一公式)---
|
||||
try:
|
||||
@@ -336,8 +334,6 @@ def refresh_data_prices():
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f" [DB汇总写入失败] {e}", flush=True)
|
||||
# JSON 冷备
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f" [汇总重算失败] {e}", flush=True)
|
||||
# --- 结束汇总重算 ---
|
||||
@@ -377,7 +373,13 @@ def _record_branch_trigger(code, branch_id, price):
|
||||
b['last_trigger_price'] = round(price, 2)
|
||||
b['last_triggered'] = datetime.now().isoformat()
|
||||
break
|
||||
json.dump(raw, open(DECISIONS_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
try:
|
||||
conn = get_conn()
|
||||
for d in raw.get('decisions', []):
|
||||
write_holding_strategy(conn, d['code'], d.get('name', ''), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -504,10 +506,9 @@ def run_once(round_label=""):
|
||||
|
||||
# === 第二步:检查触发条件 ===
|
||||
try:
|
||||
with open(DECISIONS_PATH) as f:
|
||||
dec = json.load(f)
|
||||
dec = read_decisions()
|
||||
except:
|
||||
print(f"❌{label} 无法读取decisions.json", file=sys.stderr)
|
||||
print(f"❌{label} 无法读取决策数据", file=sys.stderr)
|
||||
return
|
||||
|
||||
active = [d for d in dec.get("decisions", []) if d.get("status") in ("active", "updated")]
|
||||
|
||||
@@ -12,9 +12,7 @@ from .tracking import load_associations, get_associations_for_prompt_version
|
||||
from .registry import get_prompt, get_version_history
|
||||
|
||||
PROJECT_DIR = Path("/home/hmo/projects/MoFin")
|
||||
DECISIONS_PATH = PROJECT_DIR / "data" / "decisions.json"
|
||||
ACCURACY_PATH = PROJECT_DIR / "data" / "accuracy_stats.json"
|
||||
EVAL_PATH = PROJECT_DIR / "data" / "evaluation.json"
|
||||
|
||||
|
||||
def _load_json(path, default=None):
|
||||
@@ -25,10 +23,19 @@ def _load_json(path, default=None):
|
||||
return {} if default is None else default
|
||||
|
||||
|
||||
def _load_decisions():
|
||||
"""从 DB 读取 decisions 数据"""
|
||||
try:
|
||||
from mo_data import read_decisions
|
||||
return read_decisions()
|
||||
except Exception:
|
||||
return {"decisions": []}
|
||||
|
||||
|
||||
def analyze_prompt_version_effectiveness() -> dict:
|
||||
"""按提示词版本聚合策略评估结果
|
||||
|
||||
关联 decisions.json 中的 evaluation 字段和 associations.json 中的版本记录,
|
||||
关联 DB holding_strategies 中的 evaluation 字段和 associations.json 中的版本记录,
|
||||
计算出每个提示词版本的:
|
||||
- 生成的策略总数
|
||||
- 达到止盈数(成功)
|
||||
@@ -37,7 +44,7 @@ def analyze_prompt_version_effectiveness() -> dict:
|
||||
- 盈亏比平均值
|
||||
"""
|
||||
# 加载数据
|
||||
decisions = _load_json(DECISIONS_PATH, {"decisions": []})
|
||||
decisions = _load_decisions()
|
||||
associations = load_associations().get("associations", [])
|
||||
|
||||
# 建立 code → 最新关联的映射
|
||||
@@ -182,5 +189,5 @@ def generate_report() -> str:
|
||||
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("注:数据来自 decisions.json evaluation + associations.json")
|
||||
lines.append("注:数据来自 DB holding_strategies evaluation + associations.json")
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -282,9 +282,9 @@ add_version("knowledge-extraction", PromptVersion(
|
||||
version="v1",
|
||||
label="初始版本",
|
||||
created_at="2026-06-11T16:30:00",
|
||||
changelog="初始版本,从 decisions.json 和 evaluation.json 中提炼经验写入知识日志",
|
||||
changelog="初始版本,从 DB holding_strategies 表中提炼经验写入知识日志",
|
||||
content="""知识萃取 v1(当前):
|
||||
1. 读 decisions.json 的 changelog 和 evaluation
|
||||
1. 从 DB holding_strategies 表读取 changelog 和 evaluation 字段
|
||||
2. 读当天分析输出的 anomaly 信号
|
||||
3. 提炼 1-3 条可复用知识
|
||||
4. 写入 /home/hmo/Obsidian/knowledge/finance/analyst-knowledge-log.md
|
||||
@@ -370,7 +370,7 @@ add_version("system-health-check", PromptVersion(
|
||||
content="""健康检查 v2(当前):18项检查
|
||||
- 进程:mofin-dashboard, xmpp-zhiwei, ejabberd
|
||||
- 端口:8899, 5222, 8643
|
||||
- 数据:portfolio/watchlist/decisions/market/price_events/evaluation/accuracy_stats
|
||||
- 数据:holds/strategies/watchlist/market/events (全部DB)
|
||||
- 活动:价格事件数/策略评估数/建议记录数
|
||||
- Cron: 两个profile
|
||||
- 数据新鲜度:文件更新时间
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
"""批量再生所有持仓+自选策略,结合技术面支撑/压力位"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
sys.path.insert(0, '/home/hmo/web-dashboard')
|
||||
|
||||
from technical_analysis import full_analysis
|
||||
from strategy_lifecycle import reassess_strategy
|
||||
|
||||
PF = '/home/hmo/web-dashboard/data/portfolio.json'
|
||||
WL = '/home/hmo/web-dashboard/data/watchlist.json'
|
||||
from mo_data import read_portfolio, read_watchlist
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_watchlist_stock
|
||||
|
||||
def main():
|
||||
# 持仓
|
||||
pf = json.load(open(PF))
|
||||
pf = read_portfolio()
|
||||
for s in pf['holdings']:
|
||||
code = s['code']
|
||||
name = s['name']
|
||||
@@ -48,11 +46,14 @@ def main():
|
||||
|
||||
print(f"损{result['stop_loss']} 盈{result['take_profit']} 区{result['entry_low']}~{result['entry_high']}")
|
||||
|
||||
json.dump(pf, open(PF, 'w'), ensure_ascii=False, indent=2)
|
||||
conn = get_conn()
|
||||
write_holdings_batch(conn, pf['holdings'])
|
||||
write_portfolio_summary(conn, pf)
|
||||
conn.close()
|
||||
print(f"\n持仓策略已更新: {len(pf['holdings'])} 条")
|
||||
|
||||
# 自选股 - 简单重新计算买入区
|
||||
wl = json.load(open(WL))
|
||||
wl = read_watchlist()
|
||||
updated = 0
|
||||
for s in wl['stocks']:
|
||||
code = s['code']
|
||||
@@ -88,7 +89,10 @@ def main():
|
||||
updated += 1
|
||||
print(f" {s['name']}({code}) 买入区={ws:.2f}~{wr:.2f}")
|
||||
|
||||
json.dump(wl, open(WL, 'w'), ensure_ascii=False, indent=2)
|
||||
conn = get_conn()
|
||||
for s in wl['stocks']:
|
||||
write_watchlist_stock(conn, s)
|
||||
conn.close()
|
||||
print(f"\n自选策略已更新: {updated} 条")
|
||||
print("\n✅ 全部策略再生完成")
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ branch_evaluator.py — 分支自成长引擎
|
||||
|
||||
import json, sys, os, re
|
||||
from datetime import datetime, date
|
||||
from mo_data import read_portfolio, read_decisions
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
# 路径
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
@@ -37,8 +39,7 @@ def get_live_prices():
|
||||
"""从 portfolio.json 读取实时价格"""
|
||||
prices = {}
|
||||
try:
|
||||
with open(PORTFOLIO_PATH) as f:
|
||||
pf = json.load(f)
|
||||
pf = read_portfolio()
|
||||
for h in pf.get("holdings", []):
|
||||
code = str(h.get("code", ""))
|
||||
prices[code] = h.get("price", 0)
|
||||
@@ -50,8 +51,7 @@ def get_live_prices():
|
||||
def evaluate_all():
|
||||
"""评估所有已触发策略树的分支"""
|
||||
try:
|
||||
with open(DECISIONS_PATH) as f:
|
||||
data = json.load(f)
|
||||
data = read_decisions()
|
||||
except Exception as e:
|
||||
print(f"[错误] 读 decisions.json 失败: {e}", file=sys.stderr)
|
||||
return
|
||||
@@ -124,9 +124,17 @@ def evaluate_all():
|
||||
# 标记评估时间
|
||||
tree["last_evaluated"] = now_ts
|
||||
|
||||
# 写回文件
|
||||
with open(DECISIONS_PATH, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
# 写回 — DB 优先
|
||||
try:
|
||||
conn = get_conn()
|
||||
for d in data.get("decisions", []):
|
||||
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# with open(DECISIONS_PATH, "w") as f:
|
||||
# json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# 输出摘要(空 = 静默)
|
||||
lines = []
|
||||
|
||||
@@ -17,6 +17,8 @@ branch_scanner.py — 分支自成长数据采集器(全静默)
|
||||
import json, sys, re
|
||||
from datetime import datetime
|
||||
from urllib.request import Request, urlopen
|
||||
from mo_data import read_decisions
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
SCANNER_STATE = "/home/hmo/web-dashboard/data/scanner_state.json"
|
||||
@@ -82,8 +84,7 @@ def main():
|
||||
scenario = get_scenario()
|
||||
sid = scenario.get("id", "unknown")
|
||||
|
||||
with open(DECISIONS_PATH) as f:
|
||||
data = json.load(f)
|
||||
data = read_decisions()
|
||||
decisions = data.get("decisions", [])
|
||||
|
||||
for entry in decisions:
|
||||
@@ -101,8 +102,17 @@ def main():
|
||||
br["last_triggered"] = now.strftime("%Y-%m-%d")
|
||||
break
|
||||
|
||||
with open(DECISIONS_PATH, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
# DB 写入(替代 json.dump)
|
||||
try:
|
||||
conn = get_conn()
|
||||
for d in data.get("decisions", []):
|
||||
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# with open(DECISIONS_PATH, "w") as f:
|
||||
# json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# 更新状态快照
|
||||
state = {"scenario": sid, "updated_at": now.isoformat(), "branches": {}}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import sqlite3
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
sql = db.execute("SELECT sql FROM sqlite_master WHERE name='holding_strategies'").fetchone()
|
||||
print(sql[0][:600] if sql else "NOT FOUND")
|
||||
db.close()
|
||||
@@ -18,6 +18,8 @@
|
||||
|
||||
import json, os, sqlite3, time, urllib.request
|
||||
from datetime import datetime, timedelta
|
||||
from mo_data import read_decisions
|
||||
from mo_data import read_decisions
|
||||
from pathlib import Path
|
||||
|
||||
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
|
||||
@@ -245,7 +247,7 @@ if __name__ == "__main__":
|
||||
cf = ChipFactors()
|
||||
|
||||
# 从decisions.json获取持仓+自选
|
||||
dec = json.loads(open(str(MOFIN_ROOT / "data" / "decisions.json")).read())
|
||||
dec = read_decisions()
|
||||
stocks = [(s["code"], s.get("name","")) for s in dec.get("decisions", []) if s.get("status") != "closed"]
|
||||
|
||||
results = cf.batch_calc(stocks)
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
|
||||
import json, os
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
from mofin_db import get_conn, write_watchlist_stock, write_holding_strategy
|
||||
|
||||
WL = "/home/hmo/web-dashboard/data/watchlist.json"
|
||||
DEC = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
|
||||
holding_codes = set()
|
||||
pf = mo_data.read_portfolio()
|
||||
pf = read_portfolio()
|
||||
for h in pf.get("holdings", []):
|
||||
c = h.get("code", "")
|
||||
if c:
|
||||
@@ -17,7 +18,7 @@ for h in pf.get("holdings", []):
|
||||
print(f"持仓 codes: {sorted(holding_codes)}")
|
||||
|
||||
# Load watchlist
|
||||
wl = json.load(open(WL))
|
||||
wl = read_watchlist()
|
||||
stocks = wl.get("stocks", [])
|
||||
before = len(stocks)
|
||||
|
||||
@@ -30,7 +31,14 @@ wl["stocks"] = new_stocks
|
||||
|
||||
# Backup
|
||||
os.rename(WL, WL + ".bak2")
|
||||
json.dump(wl, open(WL, "w"), indent=2, ensure_ascii=False)
|
||||
# DB 写入
|
||||
conn = get_conn()
|
||||
for s in wl.get("stocks", []):
|
||||
s.setdefault("currency", "CNY")
|
||||
write_watchlist_stock(conn, s)
|
||||
conn.close()
|
||||
# [migrated to DB] — cold backup removed
|
||||
# json.dump(wl, open(WL, "w"), indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n自选: {before} → {after} 只")
|
||||
print(f"移除 {len(removed)} 只:")
|
||||
@@ -38,7 +46,7 @@ for r in removed:
|
||||
print(f" {r['code']} {r.get('name','')}")
|
||||
|
||||
# Also update decisions.json - set them to "managed_by_holdings" or remove watchlist-only fields
|
||||
dec = json.load(open(DEC))
|
||||
dec = read_decisions()
|
||||
dec_changed = 0
|
||||
for d in dec.get("decisions", []):
|
||||
code = d.get("code", "")
|
||||
@@ -50,7 +58,13 @@ for d in dec.get("decisions", []):
|
||||
|
||||
if dec_changed:
|
||||
os.rename(DEC, DEC + ".bak3")
|
||||
json.dump(dec, open(DEC, "w"), indent=2, ensure_ascii=False)
|
||||
# DB 写入
|
||||
conn = get_conn()
|
||||
for d in dec.get("decisions", []):
|
||||
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
|
||||
conn.close()
|
||||
# [migrated to DB] — cold backup removed
|
||||
# json.dump(dec, open(DEC, "w"), indent=2, ensure_ascii=False)
|
||||
print(f"\ndecisions.json: {dec_changed} 只更新标签")
|
||||
else:
|
||||
print(f"\ndecisions.json: 无需更新")
|
||||
@@ -91,7 +105,14 @@ if prev_held:
|
||||
print(f" ← 已清仓→加回自选: {code} {info['name']}")
|
||||
if added:
|
||||
wl["stocks"] = new_stocks
|
||||
json.dump(wl, open(WL, "w"), indent=2, ensure_ascii=False)
|
||||
# DB 写入
|
||||
conn = get_conn()
|
||||
for s in wl.get("stocks", []):
|
||||
s.setdefault("currency", "CNY")
|
||||
write_watchlist_stock(conn, s)
|
||||
conn.close()
|
||||
# [migrated to DB] — cold backup removed
|
||||
# json.dump(wl, open(WL, "w"), indent=2, ensure_ascii=False)
|
||||
print(f"\n反过程: {added} 只清仓股已加回自选")
|
||||
else:
|
||||
print("\n反过程: 无清仓股需加回")
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Deep check 01888 decisions"""
|
||||
import json, sqlite3
|
||||
|
||||
# Check decisions.json directly
|
||||
with open('/home/hmo/web-dashboard/data/decisions.json') as f:
|
||||
raw = json.load(f)
|
||||
|
||||
count = 0
|
||||
for d in raw.get('decisions', []):
|
||||
if d.get('code') == '01888':
|
||||
count += 1
|
||||
print(f"[JSON entry {count}] price={d.get('price')} cost={d.get('cost')} shares={d.get('shares')}")
|
||||
print(f" curr={d.get('currency','none')} status={d.get('status')} type={d.get('type')}")
|
||||
print(f" stop_loss={d.get('stop_loss')} take_profit={d.get('trigger',{}).get('take_profit')}")
|
||||
|
||||
print(f"\nTotal 01888 entries in decisions.json: {count}")
|
||||
|
||||
# Also check holding_strategies table
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
db.row_factory = sqlite3.Row
|
||||
rows = db.execute("SELECT * FROM holding_strategies WHERE code='01888' ORDER BY updated_at DESC").fetchall()
|
||||
print(f"\nholding_strategies entries: {len(rows)}")
|
||||
for r in rows:
|
||||
print(f" cost={r['cost']} price={r['price']} shares={r['shares']} curr={r['currency']} status={r['status']} updated={r['updated_at']}")
|
||||
db.close()
|
||||
@@ -0,0 +1,23 @@
|
||||
import sqlite3, sys
|
||||
sys.path.insert(0, '/home/hmo/MoFin')
|
||||
from strategy_lifecycle import regenerate_all
|
||||
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
|
||||
# Step 1: Add UNIQUE index if not exists
|
||||
try:
|
||||
db.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_strategy_code ON holding_strategies(code)")
|
||||
print("UNIQUE index added")
|
||||
except sqlite3.OperationalError as e:
|
||||
print(f"Index already exists or error: {e}")
|
||||
|
||||
# Step 2: Clear old stale data from holding_strategies (regenerate_all will rewrite)
|
||||
deleted = db.execute("DELETE FROM holding_strategies").rowcount
|
||||
print(f"Cleared {deleted} old strategy entries")
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
# Step 3: Full regenerate
|
||||
print("\n=== Running regenerate_all ===")
|
||||
regenerate_all(stdout=True)
|
||||
print("\nDone!")
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env python3
|
||||
"""一次性修复:将 holdings 表中港股成本从 HKD 转为 CNY。
|
||||
运行时机:import_holding_xls.py 修复后,旧数据需要转换。
|
||||
"""
|
||||
import sqlite3, sys, os
|
||||
|
||||
sys.path.insert(0, '/home/hmo/MoFin')
|
||||
from mo_models import is_hk_stock
|
||||
from hk_rate import hkd_to_cny
|
||||
|
||||
rate = hkd_to_cny()
|
||||
DB = '/home/hmo/web-dashboard/data/mofin.db'
|
||||
|
||||
print(f"HK_RATE = {rate}")
|
||||
print(f"DB = {DB}")
|
||||
|
||||
db = sqlite3.connect(DB)
|
||||
rows = db.execute(
|
||||
"SELECT code, name, cost, shares, currency FROM holdings WHERE is_active=1"
|
||||
).fetchall()
|
||||
|
||||
fixed = 0
|
||||
for r in rows:
|
||||
code = r[0]
|
||||
name = r[1]
|
||||
cost = r[2] or 0
|
||||
shares = r[3] or 0
|
||||
curr = r[4] or 'CNY'
|
||||
|
||||
if not is_hk_stock(str(code)):
|
||||
continue
|
||||
if cost <= 0:
|
||||
continue
|
||||
|
||||
cost_cny = round(cost * rate, 2)
|
||||
print(f" FIX: {code} {name}: cost {cost} -> {cost_cny} CNY (shares={shares})")
|
||||
db.execute(
|
||||
"UPDATE holdings SET cost=?, currency=? WHERE code=?",
|
||||
(cost_cny, 'CNY', code)
|
||||
)
|
||||
fixed += 1
|
||||
|
||||
db.commit()
|
||||
db.close()
|
||||
|
||||
print(f"\nDone. Fixed {fixed} HK stock costs.")
|
||||
+10
-4
@@ -3,13 +3,13 @@
|
||||
|
||||
import json, sys, os
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
DECISIONS_BAK = DECISIONS_PATH + ".bak"
|
||||
|
||||
try:
|
||||
with open(DECISIONS_PATH) as f:
|
||||
dec = json.load(f)
|
||||
dec = read_decisions()
|
||||
except Exception as e:
|
||||
print(f"读decisions.json失败: {e}")
|
||||
sys.exit(1)
|
||||
@@ -45,7 +45,13 @@ os.makedirs(os.path.dirname(DECISIONS_BAK), exist_ok=True)
|
||||
with open(DECISIONS_BAK, 'w') as f:
|
||||
json.dump(mo_data.read_decisions(), f, indent=2, ensure_ascii=False)
|
||||
|
||||
with open(DECISIONS_PATH, 'w') as f:
|
||||
json.dump(dec, f, indent=2, ensure_ascii=False)
|
||||
# DB 写入(替代 json.dump)
|
||||
conn = get_conn()
|
||||
for d in dec.get("decisions", []):
|
||||
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
|
||||
conn.close()
|
||||
# [migrated to DB] — cold backup removed
|
||||
# with open(DECISIONS_PATH, 'w') as f:
|
||||
# json.dump(dec, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n共{count}只,已更新trigger字段")
|
||||
|
||||
@@ -10,13 +10,15 @@
|
||||
"""
|
||||
import json, sys
|
||||
from datetime import datetime
|
||||
from mo_data import read_portfolio, read_decisions
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_holding_strategy
|
||||
|
||||
DECISIONS = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
PORTFOLIO = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
|
||||
def main():
|
||||
dec = json.load(open(DECISIONS))
|
||||
pf = json.load(open(PORTFOLIO))
|
||||
dec = read_decisions()
|
||||
pf = read_portfolio()
|
||||
|
||||
# Build maps
|
||||
dmap = {d["code"]: d for d in dec.get("decisions", [])}
|
||||
@@ -120,11 +122,9 @@ def main():
|
||||
print(f"decisions stock_value: {dec_total:.2f}")
|
||||
print(f"decisions count(shares>0): {len([d for d in dec['decisions'] if d.get('shares',0)>0])}")
|
||||
|
||||
# Write — DB 优先(强制币种约束),JSON 冷备
|
||||
# Write — DB 优先,JSON 冷备已移除
|
||||
dec["total"] = len(dec["decisions"])
|
||||
try:
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_holding_strategy
|
||||
conn = get_conn()
|
||||
write_holdings_batch(conn, pf.get('holdings', []))
|
||||
write_portfolio_summary(conn, pf)
|
||||
@@ -133,8 +133,9 @@ def main():
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f" [DB写入失败] {e}")
|
||||
json.dump(dec, open(DECISIONS, "w"), ensure_ascii=False, indent=2)
|
||||
json.dump(pf, open(PORTFOLIO, "w"), ensure_ascii=False, indent=2)
|
||||
# [migrated to DB] — cold backup removed; DB writes above
|
||||
# json.dump(dec, open(DECISIONS, "w"), ensure_ascii=False, indent=2)
|
||||
# json.dump(pf, open(PORTFOLIO, "w"), ensure_ascii=False, indent=2)
|
||||
print(f"done")
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -15,6 +15,8 @@ import_holding_xls.py — 从 holding.xls 导入持仓到全系统
|
||||
"""
|
||||
import csv, json, sys, subprocess, sqlite3, os
|
||||
from datetime import datetime
|
||||
from mo_data import read_decisions
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_holding_strategy
|
||||
|
||||
STOCKS_FILE = "/home/hmo/stocks/holding.xls"
|
||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
@@ -149,11 +151,11 @@ def main():
|
||||
'updated_at': datetime.now().strftime('%Y-%m-%d %H:%M'),
|
||||
'source': STOCKS_FILE,
|
||||
}
|
||||
with open(PORTFOLIO_PATH, 'w') as f:
|
||||
json.dump(portfolio, f, indent=2, ensure_ascii=False)
|
||||
# [migrated to DB] — cold backup removed; DB writes below
|
||||
# with open(PORTFOLIO_PATH, 'w') as f:
|
||||
# json.dump(portfolio, f, indent=2, ensure_ascii=False)
|
||||
# DB 写入
|
||||
try:
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary
|
||||
conn = get_conn()
|
||||
write_holdings_batch(conn, portfolio.get('holdings', []))
|
||||
write_portfolio_summary(conn, portfolio)
|
||||
@@ -165,8 +167,7 @@ def main():
|
||||
print("\n→ 重建决策树...")
|
||||
sys.path.insert(0, '/home/hmo/web-dashboard')
|
||||
from strategy_tree import init_default_branches
|
||||
with open('/home/hmo/web-dashboard/data/decisions.json') as f:
|
||||
data = json.load(f)
|
||||
data = read_decisions()
|
||||
ok = 0
|
||||
for e in data.get('decisions', []):
|
||||
branches = init_default_branches(
|
||||
@@ -175,8 +176,17 @@ def main():
|
||||
e.get('stop_loss', 0), e.get('take_profit', 0))
|
||||
e['strategy_tree'] = {'branches': branches, 'created_at': datetime.now().strftime('%Y-%m-%d')}
|
||||
ok += 1
|
||||
with open('/home/hmo/web-dashboard/data/decisions.json', 'w') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
# DB 写入(替代 json.dump)
|
||||
try:
|
||||
conn = get_conn()
|
||||
for d in data.get('decisions', []):
|
||||
_whs(conn, d.get('code', ''), d.get('name', ''), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# with open('/home/hmo/web-dashboard/data/decisions.json', 'w') as f:
|
||||
# json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print(f"\n{'='*50}")
|
||||
print(f"导入完成:{len(holdings)}只持仓")
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import json
|
||||
with open('/home/hmo/web-dashboard/data/decisions.json') as f:
|
||||
d = json.load(f)
|
||||
for i, e in enumerate(d.get('decisions', [])[:3]):
|
||||
print(f"\n=== Entry {i}: {e.get('code')} {e.get('name')} ===")
|
||||
for k, v in sorted(e.items()):
|
||||
t = type(v).__name__
|
||||
sample = str(v)[:60] if v is not None else 'None'
|
||||
print(f" {k}: {t} = {sample}")
|
||||
if i >= 2:
|
||||
break
|
||||
@@ -9,6 +9,10 @@ import sys, json, os, re
|
||||
|
||||
sys.path.insert(0, "/home/hmo/web-dashboard")
|
||||
from strategy_lifecycle import reassess_with_context as reassess_strategy
|
||||
from mo_data import read_decisions, read_portfolio
|
||||
|
||||
sys.path.insert(0, "/home/hmo/MoFin")
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
|
||||
@@ -23,8 +27,7 @@ def main():
|
||||
return
|
||||
|
||||
# 读现有 decisions
|
||||
with open(DECISIONS_PATH) as f:
|
||||
raw = json.load(f)
|
||||
raw = read_decisions()
|
||||
decisions_map = {d["code"]: d for d in raw.get("decisions", []) if d.get("code")}
|
||||
|
||||
ok = 0
|
||||
@@ -57,14 +60,6 @@ def main():
|
||||
db.close()
|
||||
if price > 0:
|
||||
print(f" 实时价: {price} (来自DB)")
|
||||
else:
|
||||
# fallback to portfolio.json
|
||||
with open("/home/hmo/web-dashboard/data/portfolio.json") as _pf:
|
||||
_pf_data = json.load(_pf)
|
||||
for _h in _pf_data.get("holdings", []):
|
||||
if _h["code"] == code_raw:
|
||||
price = float(_h.get("price", 0))
|
||||
break
|
||||
if price <= 0:
|
||||
price = entry.get("current_price") or entry.get("price") or 0
|
||||
except Exception as e:
|
||||
@@ -153,8 +148,17 @@ def main():
|
||||
raw["total"] = len(raw["decisions"])
|
||||
from datetime import datetime
|
||||
raw["regenerated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
with open(DECISIONS_PATH, "w") as f:
|
||||
json.dump(raw, f, ensure_ascii=False, indent=2)
|
||||
# DB 写入(替代 json.dump)
|
||||
try:
|
||||
conn = get_conn()
|
||||
for d in raw.get("decisions", []):
|
||||
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# with open(DECISIONS_PATH, "w") as f:
|
||||
# json.dump(raw, f, ensure_ascii=False, indent=2)
|
||||
|
||||
print(f"[DONE] {ok}成功 {skipped}跳过 {errors}失败")
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Phase 1: Add missing columns to holding_strategies for decisions.json→DB migration"""
|
||||
import sqlite3
|
||||
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
|
||||
new_cols = {
|
||||
'avg_price': 'REAL',
|
||||
'decision_timestamp': 'TEXT', # avoids conflict with SQLite timestamp
|
||||
'note': 'TEXT',
|
||||
'quality_check': 'TEXT',
|
||||
'quality_checked_at': 'TEXT',
|
||||
'quality_issues_json': 'TEXT',
|
||||
'position_advice': 'TEXT',
|
||||
'signal_factors_json': 'TEXT',
|
||||
'time_horizon': 'TEXT',
|
||||
'decision_type': 'TEXT',
|
||||
}
|
||||
|
||||
# Get existing columns
|
||||
existing = {r[1] for r in db.execute("PRAGMA table_info(holding_strategies)")}
|
||||
|
||||
for col, ctype in new_cols.items():
|
||||
if col in existing:
|
||||
print(f"SKIP: {col} already exists")
|
||||
else:
|
||||
db.execute(f"ALTER TABLE holding_strategies ADD COLUMN {col} {ctype}")
|
||||
print(f"ADDED: {col} {ctype}")
|
||||
|
||||
db.commit()
|
||||
db.close()
|
||||
print("\nSchema migration done.")
|
||||
+27
-11
@@ -13,7 +13,7 @@ from datetime import datetime
|
||||
# ── MoFin unified model ──────────────────────────────────────────────
|
||||
sys.path.insert(0, "/home/hmo/MoFin")
|
||||
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
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_price_event, write_watchlist_stock, write_holding_strategy, write_holding_strategy
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
@@ -346,14 +346,22 @@ def refresh_data_prices():
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f" [DB写入失败] {e}", flush=True)
|
||||
# 保留 JSON 副本作为冷备
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
# [migrated to DB] — JSON cold backup removed
|
||||
# json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
elif pf.get('updated_at'):
|
||||
try:
|
||||
last_ts = datetime.strptime(pf['updated_at'], '%Y-%m-%d %H:%M')
|
||||
if (datetime.now() - last_ts).total_seconds() > 600:
|
||||
pf['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
# DB 写入(时间戳更新)
|
||||
try:
|
||||
conn2 = get_conn()
|
||||
write_portfolio_summary(conn2, pf)
|
||||
conn2.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -383,8 +391,8 @@ def refresh_data_prices():
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f" [DB watchlist写入失败] {e}", flush=True)
|
||||
# 保留 JSON 冷备
|
||||
json.dump(wl, open(WATCHLIST_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
# [migrated to DB] — cold backup removed; DB writes above
|
||||
# json.dump(wl, open(WATCHLIST_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
|
||||
# --- 汇总值重算(使用 mo_models 唯一公式)---
|
||||
try:
|
||||
@@ -405,8 +413,8 @@ def refresh_data_prices():
|
||||
conn.close()
|
||||
except Exception as e:
|
||||
print(f" [DB汇总写入失败] {e}", flush=True)
|
||||
# JSON 冷备
|
||||
json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
# [migrated to DB] — cold backup removed; DB writes above
|
||||
# json.dump(pf, open(PORTFOLIO_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
except Exception as e:
|
||||
print(f" [汇总重算失败] {e}", flush=True)
|
||||
# --- 结束汇总重算 ---
|
||||
@@ -446,7 +454,16 @@ def _record_branch_trigger(code, branch_id, price):
|
||||
b['last_trigger_price'] = round(price, 2)
|
||||
b['last_triggered'] = datetime.now().isoformat()
|
||||
break
|
||||
json.dump(raw, open(DECISIONS_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
# DB 写入(替代 json.dump)
|
||||
try:
|
||||
conn3 = get_conn()
|
||||
for d in raw.get('decisions', []):
|
||||
write_holding_strategy(conn3, d.get('code', ''), d.get('name', ''), d)
|
||||
conn3.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# json.dump(raw, open(DECISIONS_PATH, 'w'), ensure_ascii=False, indent=2)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -573,8 +590,7 @@ def run_once(round_label=""):
|
||||
|
||||
# === 第二步:检查触发条件 ===
|
||||
try:
|
||||
with open(DECISIONS_PATH) as f:
|
||||
dec = json.load(f)
|
||||
dec = read_decisions()
|
||||
except:
|
||||
print(f"❌{label} 无法读取decisions.json", file=sys.stderr)
|
||||
return
|
||||
|
||||
@@ -17,6 +17,8 @@ Dad 发交易截图后,填入以下信息运行此脚本:
|
||||
import json, sys, os
|
||||
from datetime import datetime
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_holding_strategy
|
||||
|
||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
@@ -121,21 +123,18 @@ def main():
|
||||
})
|
||||
break
|
||||
|
||||
# 写入 — DB 优先
|
||||
# 写入 — DB(替代 json.dump)
|
||||
pf["updated_at"] = now
|
||||
try:
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_holding_strategy
|
||||
conn = get_conn()
|
||||
write_holdings_batch(conn, pf.get('holdings', []))
|
||||
write_portfolio_summary(conn, pf)
|
||||
for d in dec.get('decisions', []):
|
||||
write_holding_strategy(conn, d.get('code', ''), d.get('name', ''), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
json.dump(pf, open(PORTFOLIO_PATH, "w"), indent=2, ensure_ascii=False)
|
||||
json.dump(dec, open(DECISIONS_PATH, "w"), indent=2, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f" [DB写入失败] {e}", file=sys.stderr)
|
||||
# [migrated to DB] — JSON cold backup removed
|
||||
|
||||
# 重算总资产
|
||||
total_mv = sum((h.get("shares",0) or 0) * (h.get("price",0) or 0) for h in pf["holdings"])
|
||||
|
||||
@@ -13,19 +13,29 @@ prune_branches.py — 每日剪枝
|
||||
|
||||
import json, sys, os
|
||||
from datetime import datetime, timedelta
|
||||
from mo_data import read_decisions
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
PRUNE_LOG = "/home/hmo/MoFin/data/prune_log.json"
|
||||
|
||||
|
||||
def load_decisions():
|
||||
with open(DECISIONS_PATH) as f:
|
||||
return json.load(f)
|
||||
return read_decisions()
|
||||
|
||||
|
||||
def save_decisions(data):
|
||||
with open(DECISIONS_PATH, "w") as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
# DB 写入(替代 json.dump)
|
||||
try:
|
||||
conn = get_conn()
|
||||
for d in data.get("decisions", []):
|
||||
write_holding_strategy(conn, d.get("code", ""), d.get("name", ""), d)
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — cold backup removed
|
||||
# with open(DECISIONS_PATH, "w") as f:
|
||||
# json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -10,6 +10,7 @@ review_needed_watchdog.py — review_needed 策略自动跟进
|
||||
"""
|
||||
|
||||
import sys, json, os, datetime
|
||||
from mo_data import read_decisions
|
||||
|
||||
sys.path.insert(0, "/home/hmo/web-dashboard")
|
||||
os.chdir("/home/hmo/MoFin")
|
||||
@@ -39,7 +40,7 @@ def push_xmpp(text):
|
||||
print(f" [XMPP推送失败] {e}")
|
||||
|
||||
def main():
|
||||
dec = json.load(open(DEC_PATH))
|
||||
dec = read_decisions()
|
||||
review_list = [d for d in dec.get("decisions", []) if d.get("status") == "review_needed"]
|
||||
retry_data = load_retry()
|
||||
today = datetime.date.today().isoformat()
|
||||
@@ -70,7 +71,7 @@ def main():
|
||||
print(f" {out[:200]}")
|
||||
|
||||
# 重读决策
|
||||
dec2 = json.load(open(DEC_PATH))
|
||||
dec2 = read_decisions()
|
||||
for d2 in dec2.get("decisions", []):
|
||||
if d2["code"] == code:
|
||||
if d2.get("status") == "active":
|
||||
|
||||
@@ -114,8 +114,7 @@ def main():
|
||||
cash = 0
|
||||
total_assets = 0
|
||||
try:
|
||||
with open(PORTFOLIO_PATH) as f:
|
||||
pf = json.load(f)
|
||||
pf = read_portfolio()
|
||||
position_pct = pf.get("position_pct", 0)
|
||||
cash = pf.get("cash", 0)
|
||||
total_assets = pf.get("total_assets", 0)
|
||||
|
||||
@@ -19,6 +19,7 @@ import os
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, time
|
||||
from mo_data import read_portfolio, read_decisions
|
||||
|
||||
# ── MoFin unified model ──────────────────────────────────────────────
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
@@ -226,8 +227,7 @@ def trigger_regen_sync(stock_codes=None):
|
||||
def load_cash():
|
||||
"""从 portfolio.json 实时读可用现金(可用 ≈ 实时买力),不硬编码"""
|
||||
try:
|
||||
with open(PORTFOLIO_PATH) as f:
|
||||
data = json.load(f)
|
||||
data = read_portfolio()
|
||||
if isinstance(data, dict):
|
||||
# 先读 cash_available(拆分了可用/冻结),fallback 到 cash
|
||||
return data.get("cash_available", data.get("cash", 0))
|
||||
@@ -338,8 +338,7 @@ def main():
|
||||
# 读 decisions.json 获取完整策略数据
|
||||
code_data = {}
|
||||
try:
|
||||
with open("/home/hmo/web-dashboard/data/decisions.json") as f:
|
||||
dec = json.load(f)
|
||||
dec = read_decisions()
|
||||
for e in dec.get("decisions", []):
|
||||
code_data[e["code"]] = e
|
||||
except Exception:
|
||||
@@ -395,8 +394,7 @@ def main():
|
||||
# 重评完成,re-read decisions.json 获取最新策略
|
||||
code_data = {}
|
||||
try:
|
||||
with open("/home/hmo/web-dashboard/data/decisions.json") as f:
|
||||
dec = json.load(f)
|
||||
dec = read_decisions()
|
||||
for e in dec.get("decisions", []):
|
||||
code_data[e["code"]] = e
|
||||
except Exception:
|
||||
@@ -417,8 +415,7 @@ def main():
|
||||
# 加载portfolio获取持仓信息(A/H去重用)
|
||||
pf = {"holdings": []}
|
||||
try:
|
||||
with open(PORTFOLIO_PATH) as f:
|
||||
pf = json.load(f)
|
||||
pf = read_portfolio()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -492,8 +489,7 @@ def main():
|
||||
total_assets = 0
|
||||
available_cash = 0
|
||||
try:
|
||||
with open("/home/hmo/web-dashboard/data/portfolio.json") as f:
|
||||
pf = json.load(f)
|
||||
pf = read_portfolio()
|
||||
available_cash = pf.get("cash_available", pf.get("cash", 0)) or 0
|
||||
# 直接取 portfolio.json 的总资产(导入时已做港币→人民币换算)
|
||||
total_assets = pf.get("total_assets", 0) or 0
|
||||
@@ -845,7 +841,7 @@ def main():
|
||||
# ── T+2前瞻:扫描近期可能入买区的A股,提前准备现金 ──
|
||||
t2_lines = []
|
||||
try:
|
||||
dec_t2 = json.loads(open("/home/hmo/web-dashboard/data/decisions.json").read())
|
||||
dec_t2 = read_decisions()
|
||||
for entry in dec_t2.get("decisions", []):
|
||||
if entry.get("status") == "closed" or entry.get("type") != "自选策略":
|
||||
continue
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
|
||||
import json, sys, os, re, urllib.request
|
||||
from datetime import datetime
|
||||
from mo_data import read_decisions, read_portfolio
|
||||
from mo_data import read_decisions, read_portfolio
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
OUTPUT_PATH = "/home/hmo/web-dashboard/data/strategy_staleness_report.json"
|
||||
@@ -59,8 +61,7 @@ def main():
|
||||
path = DECISIONS_PATH if os.path.exists(DECISIONS_PATH) else FALLBACK_PATH
|
||||
is_new_format = (path == DECISIONS_PATH)
|
||||
|
||||
with open(path) as f:
|
||||
data = json.load(f)
|
||||
data = read_decisions()
|
||||
|
||||
# Filter: exclude closed strategies
|
||||
all_entries = data.get("decisions", [])
|
||||
@@ -190,8 +191,7 @@ def main():
|
||||
position_pct = 0
|
||||
cash = 0
|
||||
try:
|
||||
with open(PORTFOLIO_PATH) as pf:
|
||||
pdata = json.load(pf)
|
||||
pdata = read_portfolio()
|
||||
position_pct = pdata.get("position_pct", 0)
|
||||
cash = pdata.get("cash", 0)
|
||||
except: pass
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify 01888 data after fix"""
|
||||
import sys
|
||||
sys.path.insert(0, '/home/hmo/MoFin')
|
||||
from mo_data import read_portfolio, read_decisions
|
||||
import sqlite3
|
||||
|
||||
# Check DB
|
||||
db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db')
|
||||
r = db.execute("SELECT code, name, cost, shares, currency FROM holdings WHERE code='01888'").fetchone()
|
||||
if r:
|
||||
print(f"[DB] {r[0]} {r[1]}: cost={r[2]} shares={r[3]} curr={r[4]}")
|
||||
|
||||
# Check decisions
|
||||
dec = read_decisions()
|
||||
for d in dec.get('decisions', []):
|
||||
if d.get('code') == '01888':
|
||||
print(f"[DEC] price={d.get('price')} cost={d.get('cost')} shares={d.get('shares')}")
|
||||
print(f" curr={d.get('currency')} status={d.get('status')}")
|
||||
c = d.get('cost', 0)
|
||||
p = d.get('price', 0)
|
||||
if c > 0 and p > 0:
|
||||
print(f" PnL={(p-c)/c*100:.1f}%")
|
||||
|
||||
# Check portfolio
|
||||
pf = read_portfolio()
|
||||
for h in pf.get('holdings', []):
|
||||
if h.get('code') == '01888':
|
||||
print(f"[PF] {h.get('code')} cost={h.get('cost')} price={h.get('price')} shares={h.get('shares')}")
|
||||
|
||||
db.close()
|
||||
@@ -13,6 +13,8 @@ no_agent模式:有发现→输出,无→静默
|
||||
import json, os, sqlite3, sys, time, urllib.request
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from mo_data import read_watchlist
|
||||
from mofin_db import write_watchlist_stock
|
||||
|
||||
BASE = Path("/home/hmo/MoFin")
|
||||
DATA = BASE / "data"
|
||||
@@ -64,7 +66,7 @@ def is_in_portfolio(conn, code):
|
||||
return "watchlist"
|
||||
# 也检查 watchlist.json
|
||||
try:
|
||||
wl = json.loads(WATCHLIST_PATH.read_text())
|
||||
wl = read_watchlist()
|
||||
for s in wl.get("stocks", []):
|
||||
if s.get("code") == code or s.get("code", "").lstrip("0") == code_stripped:
|
||||
return "watchlist"
|
||||
@@ -240,20 +242,30 @@ def main():
|
||||
results.append(f"✅ {sector_name}({code}): {summary}")
|
||||
# 写入 watchlist.json
|
||||
try:
|
||||
wl = json.loads(WATCHLIST_PATH.read_text())
|
||||
wl = read_watchlist()
|
||||
wl.setdefault("stocks", [])
|
||||
# 检查是否已在
|
||||
existing = [s for s in wl["stocks"] if s.get("code") == code]
|
||||
if not existing:
|
||||
wl["stocks"].append({
|
||||
new_stock = {
|
||||
"code": code,
|
||||
"name": quote.get("name", sector_name),
|
||||
"price": quote.get("price", 0),
|
||||
"status": "watching",
|
||||
"source": "xiaoguo_scanner",
|
||||
"added_at": today,
|
||||
})
|
||||
WATCHLIST_PATH.write_text(json.dumps(wl, ensure_ascii=False, indent=2))
|
||||
}
|
||||
wl["stocks"].append(new_stock)
|
||||
# DB 写入(watchlist_stocks)
|
||||
try:
|
||||
conn2 = get_conn()
|
||||
new_stock["currency"] = "CNY"
|
||||
write_watchlist_stock(conn2, new_stock)
|
||||
conn2.close()
|
||||
except Exception:
|
||||
pass
|
||||
# [migrated to DB] — JSON cold backup removed
|
||||
# WATCHLIST_PATH.write_text(json.dumps(wl, ensure_ascii=False, indent=2))
|
||||
except:
|
||||
pass
|
||||
elif action == "monitor":
|
||||
|
||||
@@ -15,6 +15,10 @@ from flask import Flask, jsonify, send_from_directory, request
|
||||
# 提示词管理模块
|
||||
from prompt_manager.dashboard_views import register_routes
|
||||
|
||||
# MoFin 数据层(纯 DB,不再读 JSON)
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
from mofin_db import get_conn, write_holdings_batch, write_portfolio_summary, write_watchlist_stock, write_holding_strategy
|
||||
|
||||
app = Flask(__name__, static_folder="static", static_url_path="")
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
@@ -26,6 +30,7 @@ API_KEY = "hermes123"
|
||||
|
||||
|
||||
def _load_json(path, default=None):
|
||||
"""仅用于非核心文件(reports, stocks, market 等)。portfolio/decisions/watchlist 已迁移到 DB。"""
|
||||
try:
|
||||
with open(path, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
@@ -33,26 +38,41 @@ def _load_json(path, default=None):
|
||||
return {} if default is None else default
|
||||
|
||||
|
||||
def _load_from_db(query_func, json_path, default=None):
|
||||
"""优先从 SQLite 读取,失败回退 JSON"""
|
||||
try:
|
||||
from mofin_db import get_conn
|
||||
conn = get_conn()
|
||||
result = query_func(conn)
|
||||
conn.close()
|
||||
if result:
|
||||
return result
|
||||
except Exception:
|
||||
pass
|
||||
return _load_json(json_path, default)
|
||||
|
||||
|
||||
def _save_json(path, data):
|
||||
"""仅用于非核心文件(reports, stocks, market 等)。portfolio/decisions/watchlist 已迁移到 DB。"""
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w", encoding="utf-8") as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||
|
||||
|
||||
def _save_portfolio(data):
|
||||
"""写入持仓数据到 DB。data 必须包含 holdings[] 和顶层 summary 字段。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
write_holdings_batch(conn, data.get('holdings', []))
|
||||
write_portfolio_summary(conn, data)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _save_decision(code, name, data):
|
||||
"""写入单条决策到 DB。"""
|
||||
conn = get_conn()
|
||||
try:
|
||||
write_holding_strategy(conn, code, name, data)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def _save_watchlist(data):
|
||||
"""写入自选股列表到 DB。"""
|
||||
conn = get_conn()
|
||||
for s in data.get('stocks', []):
|
||||
s.setdefault('currency', 'CNY')
|
||||
write_watchlist_stock(conn, s)
|
||||
conn.close()
|
||||
|
||||
|
||||
# ── API 路由 ──────────────────────────────────────────
|
||||
|
||||
@app.route("/")
|
||||
@@ -75,7 +95,7 @@ def api_portfolio():
|
||||
return jsonify(data)
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify(_load_json(DATA_DIR / "portfolio.json"))
|
||||
return jsonify({"error": "数据库查询失败"}), 500
|
||||
|
||||
|
||||
@app.route("/api/watchlist")
|
||||
@@ -90,7 +110,7 @@ def api_watchlist():
|
||||
return jsonify({"stocks": stocks})
|
||||
except Exception:
|
||||
pass
|
||||
return jsonify(_load_json(DATA_DIR / "watchlist.json"))
|
||||
return jsonify({"error": "数据库查询失败"}), 500
|
||||
|
||||
|
||||
@app.route("/api/overview")
|
||||
@@ -121,7 +141,7 @@ def api_overview():
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
portfolio = _load_json(DATA_DIR / "portfolio.json", [])
|
||||
portfolio = read_portfolio()
|
||||
market = _load_json(DATA_DIR / "market.json", {})
|
||||
alerts = _load_json(DATA_DIR / "alerts.json", [])
|
||||
total_assets = portfolio.get("total_assets", 0)
|
||||
@@ -255,14 +275,14 @@ def api_xiaoguo_scan():
|
||||
@app.route("/api/update/portfolio", methods=["POST"])
|
||||
def update_portfolio():
|
||||
data = request.get_json(force=True)
|
||||
_save_json(DATA_DIR / "portfolio.json", data)
|
||||
_save_portfolio(data)
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
|
||||
@app.route("/api/update/watchlist", methods=["POST"])
|
||||
def update_watchlist():
|
||||
data = request.get_json(force=True)
|
||||
_save_json(DATA_DIR / "watchlist.json", data)
|
||||
_save_watchlist(data)
|
||||
return jsonify({"status": "ok"})
|
||||
|
||||
|
||||
@@ -310,7 +330,7 @@ def analysis_batch():
|
||||
|
||||
# 更新持仓
|
||||
if "holdings" in data:
|
||||
pf = _load_json(DATA_DIR / "portfolio.json", {})
|
||||
pf = read_portfolio()
|
||||
idx = {h["code"]: i for i, h in enumerate(pf.get("holdings", []))}
|
||||
for item in data["holdings"]:
|
||||
code = item.get("code", "")
|
||||
@@ -327,11 +347,11 @@ def analysis_batch():
|
||||
"reason": item.get("reason"),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
}
|
||||
_save_json(DATA_DIR / "portfolio.json", pf)
|
||||
_save_portfolio(pf)
|
||||
|
||||
# 更新自选
|
||||
if "watchlist" in data:
|
||||
wl = _load_json(DATA_DIR / "watchlist.json", {})
|
||||
wl = read_watchlist()
|
||||
idx = {s["code"]: i for i, s in enumerate(wl.get("stocks", []))}
|
||||
for item in data["watchlist"]:
|
||||
code = item.get("code", "")
|
||||
@@ -345,7 +365,7 @@ def analysis_batch():
|
||||
"reason": item.get("reason"),
|
||||
"updated_at": datetime.now().isoformat(),
|
||||
}
|
||||
_save_json(DATA_DIR / "watchlist.json", wl)
|
||||
_save_watchlist(wl)
|
||||
|
||||
return jsonify({"status": "ok", "updated_at": datetime.now().isoformat()})
|
||||
|
||||
@@ -354,14 +374,14 @@ def analysis_batch():
|
||||
@app.route("/api/decisions", methods=["GET"])
|
||||
def get_decisions():
|
||||
"""返回决策库数据,统一新旧格式"""
|
||||
raw = _load_json(DATA_DIR / "decisions.json", {"decisions": []})
|
||||
raw = read_decisions()
|
||||
decisions = raw.get("decisions", [])
|
||||
if not decisions and isinstance(raw, list):
|
||||
decisions = raw
|
||||
|
||||
# portfolio 用来判断是持仓还是自选
|
||||
portfolio = _load_json(DATA_DIR / "portfolio.json", {"holdings": []})
|
||||
watchlist = _load_json(DATA_DIR / "watchlist.json", {"stocks": []})
|
||||
portfolio = read_portfolio()
|
||||
watchlist = read_watchlist()
|
||||
holding_codes = {h.get("code","") for h in portfolio.get("holdings",[])}
|
||||
watch_codes = {s.get("code","") for s in watchlist.get("stocks",[])}
|
||||
|
||||
@@ -508,7 +528,7 @@ def add_decision():
|
||||
if not code:
|
||||
return jsonify({"status": "error", "message": "code required"}), 400
|
||||
|
||||
d = _load_json(DATA_DIR / "decisions.json", {"decisions": []})
|
||||
d = read_decisions()
|
||||
|
||||
# 同一股票旧决策标记为superseded
|
||||
for e in d["decisions"]:
|
||||
@@ -537,7 +557,7 @@ def add_decision():
|
||||
"analysis": data.get("analysis", {}),
|
||||
}
|
||||
d["decisions"].append(entry)
|
||||
_save_json(DATA_DIR / "decisions.json", d)
|
||||
_save_decision(code, entry.get('name',''), entry)
|
||||
return jsonify({"status": "ok", "entry": entry})
|
||||
|
||||
|
||||
@@ -550,7 +570,7 @@ def set_decision_tag():
|
||||
if not code:
|
||||
return jsonify({"status": "error", "message": "code required"}), 400
|
||||
|
||||
d = _load_json(DATA_DIR / "decisions.json", {"decisions": []})
|
||||
d = read_decisions()
|
||||
found = False
|
||||
for e in d.get("decisions", []):
|
||||
if e.get("code") == code:
|
||||
@@ -562,14 +582,14 @@ def set_decision_tag():
|
||||
if not found:
|
||||
return jsonify({"status": "error", "message": f"stock {code} not found"}), 404
|
||||
|
||||
_save_json(DATA_DIR / "decisions.json", d)
|
||||
_save_decision(code, e.get('name',''), e)
|
||||
return jsonify({"status": "ok", "code": code, "tag": tag})
|
||||
|
||||
|
||||
@app.route("/api/decisions/pending")
|
||||
def get_pending_decisions():
|
||||
"""返回所有有未确认建议的条目"""
|
||||
d = _load_json(DATA_DIR / "decisions.json", {"decisions": []})
|
||||
d = read_decisions()
|
||||
pending = []
|
||||
for entry in d["decisions"]:
|
||||
timeline = entry.get("advice_timeline", [])
|
||||
@@ -595,7 +615,7 @@ def record_advice():
|
||||
direction = data.get("direction", "持有")
|
||||
today = datetime.now().strftime("%Y-%m-%d")
|
||||
|
||||
d = _load_json(DATA_DIR / "decisions.json", {"decisions": []})
|
||||
d = read_decisions()
|
||||
|
||||
entry = None
|
||||
for e in d["decisions"]:
|
||||
@@ -625,7 +645,7 @@ def record_advice():
|
||||
"status": "pending",
|
||||
}
|
||||
timeline.append(advice)
|
||||
_save_json(DATA_DIR / "decisions.json", d)
|
||||
_save_decision(code, entry.get('name',''), entry)
|
||||
return jsonify({"status": "ok", "advice": advice})
|
||||
|
||||
|
||||
@@ -638,7 +658,7 @@ def confirm_advice():
|
||||
action = data.get("action", "confirmed") # confirmed | ignored | executed
|
||||
result = data.get("result", "")
|
||||
|
||||
d = _load_json(DATA_DIR / "decisions.json", {"decisions": []})
|
||||
d = read_decisions()
|
||||
for e in d["decisions"]:
|
||||
if e["code"] == code and e["status"] == "active":
|
||||
timeline = e.get("advice_timeline", [])
|
||||
@@ -649,7 +669,7 @@ def confirm_advice():
|
||||
timeline[idx]["evaluated_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
if result:
|
||||
timeline[idx]["result"] = result
|
||||
_save_json(DATA_DIR / "decisions.json", d)
|
||||
_save_decision(code, e.get('name',''), e)
|
||||
return jsonify({"status": "ok"})
|
||||
return jsonify({"status": "error", "message": "not found"}), 404
|
||||
|
||||
@@ -672,7 +692,7 @@ def get_evaluation():
|
||||
return jsonify(strategies)
|
||||
|
||||
# 备选:从 decisions.json 的 evaluation 字段读取(尚未反写时的兼容)
|
||||
decisions = _load_json(DECISIONS_PATH if 'DECISIONS_PATH' in dir() else DATA_DIR / "decisions.json", {"decisions": []})
|
||||
decisions = read_decisions()
|
||||
evals = []
|
||||
for d in decisions.get("decisions", []):
|
||||
e = d.get("evaluation", [])
|
||||
@@ -937,7 +957,7 @@ def upload_confirm():
|
||||
|
||||
# 更新对应数据文件
|
||||
if doc_type == "portfolio":
|
||||
existing = _load_json(DATA_DIR / "portfolio.json", {})
|
||||
existing = read_portfolio()
|
||||
old_holdings = {h["code"]: h for h in existing.get("holdings", []) if h.get("code")}
|
||||
new_holdings = []
|
||||
for s in stocks:
|
||||
@@ -984,11 +1004,11 @@ def upload_confirm():
|
||||
# 计算仓位%
|
||||
if existing["total_assets"] > 0:
|
||||
existing["position_pct"] = round(existing["stock_value"] / existing["total_assets"] * 100, 2)
|
||||
_save_json(DATA_DIR / "portfolio.json", existing)
|
||||
_save_portfolio(existing)
|
||||
msg = f"更新了 {len(stocks)} 只持仓股"
|
||||
|
||||
elif doc_type == "watchlist":
|
||||
existing = _load_json(DATA_DIR / "watchlist.json", {})
|
||||
existing = read_watchlist()
|
||||
existing["stocks"] = [
|
||||
{
|
||||
"code": s.get("code", ""),
|
||||
@@ -998,7 +1018,7 @@ def upload_confirm():
|
||||
for s in stocks
|
||||
]
|
||||
existing["updated_at"] = datetime.now().isoformat()
|
||||
_save_json(DATA_DIR / "watchlist.json", existing)
|
||||
_save_watchlist(existing)
|
||||
msg = f"更新了 {len(stocks)} 只自选股"
|
||||
|
||||
else:
|
||||
@@ -1019,7 +1039,7 @@ def update_realtime():
|
||||
return jsonify({"status": "error", "message": "没有股票数据"}), 400
|
||||
|
||||
# 更新 portfolio.json 中的实时价格(change_pct字段)
|
||||
pf = _load_json(DATA_DIR / "portfolio.json", {"holdings": []})
|
||||
pf = read_portfolio()
|
||||
pf_holdings = {h["code"]: h for h in pf.get("holdings", [])}
|
||||
|
||||
updated = 0
|
||||
@@ -1037,7 +1057,7 @@ def update_realtime():
|
||||
updated += 1
|
||||
|
||||
# 也更新 watchlist.json
|
||||
wl = _load_json(DATA_DIR / "watchlist.json", {"stocks": []})
|
||||
wl = read_watchlist()
|
||||
wl_stocks = {s["code"]: s for s in wl.get("stocks", [])}
|
||||
|
||||
for s in stocks:
|
||||
@@ -1048,8 +1068,8 @@ def update_realtime():
|
||||
|
||||
pf["updated_at"] = datetime.now().isoformat()
|
||||
wl["updated_at"] = datetime.now().isoformat()
|
||||
_save_json(DATA_DIR / "portfolio.json", pf)
|
||||
_save_json(DATA_DIR / "watchlist.json", wl)
|
||||
_save_portfolio(pf)
|
||||
_save_watchlist(wl)
|
||||
|
||||
return jsonify({
|
||||
"status": "ok",
|
||||
|
||||
@@ -20,10 +20,10 @@ import sys
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from mo_data import read_decisions, read_portfolio
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
DECISIONS_PATH = DATA_DIR / "decisions.json"
|
||||
PORTFOLIO_PATH = DATA_DIR / "portfolio.json"
|
||||
ACCURACY_PATH = DATA_DIR / "accuracy_stats.json"
|
||||
|
||||
UA = "Mozilla/5.0"
|
||||
@@ -441,8 +441,8 @@ def evaluate_phase2(decision, price_info, holding, prev_eval):
|
||||
|
||||
|
||||
def run():
|
||||
decisions = load_json(DECISIONS_PATH, {"decisions": []})
|
||||
portfolio = load_json(PORTFOLIO_PATH, {"holdings": []})
|
||||
decisions = read_decisions()
|
||||
portfolio = read_portfolio()
|
||||
holdings_map = {h["code"]: h for h in portfolio.get("holdings", [])}
|
||||
|
||||
# 收集所有代码
|
||||
@@ -476,8 +476,11 @@ def run():
|
||||
# 更新 decisions.json 的 evaluation 字段
|
||||
d["evaluation"] = [e for e in [eval1] + ([eval2] if prev_eval and prev_eval.get("phase") == 1 else [])]
|
||||
|
||||
# 保存更新
|
||||
save_json(DECISIONS_PATH, decisions)
|
||||
# 保存更新到 DB
|
||||
conn = get_conn()
|
||||
for d in decisions.get("decisions", []):
|
||||
write_holding_strategy(conn, d["code"], d.get("name", ""), d)
|
||||
conn.close()
|
||||
|
||||
# 汇总统计
|
||||
for r in results:
|
||||
|
||||
@@ -14,10 +14,9 @@ import json
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
from mo_data import read_decisions
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
DECISIONS_PATH = DATA_DIR / "decisions.json"
|
||||
PORTFOLIO_PATH = DATA_DIR / "portfolio.json"
|
||||
ACCURACY_PATH = DATA_DIR / "accuracy_stats.json"
|
||||
EVENTS_PATH = DATA_DIR / "price_events.json"
|
||||
FEEDBACK_PATH = DATA_DIR / "strategy_feedback.json"
|
||||
@@ -174,7 +173,7 @@ def generate_adjustment(decision, phase_check, accuracy_trend):
|
||||
|
||||
|
||||
def run():
|
||||
decisions = load_json(DECISIONS_PATH, {"decisions": []})
|
||||
decisions = read_decisions()
|
||||
# 优先从 SQLite 读取价格事件
|
||||
try:
|
||||
from mofin_db import get_conn, query_price_events
|
||||
|
||||
+13
-4
@@ -18,9 +18,9 @@ strategy_tree.py — 情景化多分支策略决策引擎
|
||||
import json, os, sys, re
|
||||
from datetime import datetime, date, timedelta
|
||||
from mo_data import read_portfolio, read_decisions, read_watchlist
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DECISIONS_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
PORTFOLIO_PATH = "/home/hmo/web-dashboard/data/portfolio.json"
|
||||
MACRO_PATH = "/home/hmo/web-dashboard/data/macro_context.json"
|
||||
MARKET_PATH = "/home/hmo/web-dashboard/data/market.json"
|
||||
TREND_PATH = "/home/hmo/web-dashboard/data/trend_signals.json"
|
||||
@@ -248,7 +248,12 @@ def record_branch_trigger(code, branch_id):
|
||||
br["last_triggered"] = datetime.now().isoformat()
|
||||
break
|
||||
break
|
||||
json.dump(dec, open(DECISIONS_PATH, "w"), ensure_ascii=False, indent=2)
|
||||
conn = get_conn()
|
||||
for e in dec.get("decisions", []):
|
||||
if e.get("code") == code:
|
||||
write_holding_strategy(conn, code, e.get('name', ''), e)
|
||||
break
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -286,7 +291,11 @@ def prune_low_performance_branches(min_triggers=5, min_success_rate=0.3):
|
||||
st["branches"] = kept
|
||||
|
||||
if pruned:
|
||||
json.dump(dec, open(DECISIONS_PATH, "w"), ensure_ascii=False, indent=2)
|
||||
conn = get_conn()
|
||||
for e in dec.get("decisions", []):
|
||||
if e.get("strategy_tree", {}).get("branches") is not None:
|
||||
write_holding_strategy(conn, e.get("code"), e.get('name', ''), e)
|
||||
conn.close()
|
||||
|
||||
return pruned
|
||||
|
||||
|
||||
+11
-11
@@ -6,6 +6,8 @@ import os
|
||||
import re
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from mo_data import read_decisions
|
||||
from mofin_db import get_conn, write_holding_strategy
|
||||
|
||||
DATA_DIR = Path(__file__).parent / "data"
|
||||
|
||||
@@ -163,13 +165,10 @@ def extract_stock_mentions():
|
||||
|
||||
def sync_to_decisions():
|
||||
"""将个股建议同步到决策库(advice_timeline),自动去重"""
|
||||
decisions_path = DATA_DIR / "decisions.json"
|
||||
if not decisions_path.exists():
|
||||
return 0
|
||||
|
||||
decisions = json.loads(decisions_path.read_text(encoding="utf-8"))
|
||||
decisions = read_decisions()
|
||||
stocks_dir = DATA_DIR / "stocks"
|
||||
synced = 0
|
||||
conn = get_conn()
|
||||
|
||||
for f in sorted(stocks_dir.iterdir()):
|
||||
if f.suffix != ".json":
|
||||
@@ -186,13 +185,13 @@ def sync_to_decisions():
|
||||
|
||||
# 找决策库中是否有此股
|
||||
existing = None
|
||||
for d in decisions["decisions"]:
|
||||
for d in decisions.get("decisions", []):
|
||||
if d["code"] == code:
|
||||
existing = d
|
||||
break
|
||||
|
||||
if not existing:
|
||||
# 无决策记录→生成inactive记录
|
||||
# 无决策记录→生成inactive记录,写入 DB
|
||||
existing = {
|
||||
"code": code,
|
||||
"name": stock.get("name", ""),
|
||||
@@ -201,9 +200,11 @@ def sync_to_decisions():
|
||||
"current": "自动从update_data同步",
|
||||
"status": "inactive",
|
||||
"updated_by": "system(update_data)",
|
||||
"advice_timeline": []
|
||||
"advice_timeline": [],
|
||||
"source": "update_data",
|
||||
}
|
||||
decisions["decisions"].append(existing)
|
||||
write_holding_strategy(conn, code, stock.get("name", ""), existing)
|
||||
|
||||
# 去重合并
|
||||
timeline = existing.setdefault("advice_timeline", [])
|
||||
@@ -244,11 +245,10 @@ def sync_to_decisions():
|
||||
if new_count > 0:
|
||||
# 按日期排序
|
||||
timeline.sort(key=lambda e: e.get("date", ""))
|
||||
write_holding_strategy(conn, code, existing.get("name", ""), existing)
|
||||
synced += new_count
|
||||
|
||||
decisions_path.write_text(
|
||||
json.dumps(decisions, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
conn.close()
|
||||
return synced
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user