更新了数据库格式
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
232
Backend/app.py
232
Backend/app.py
@@ -1,7 +1,7 @@
|
||||
from flask import Flask, request, jsonify
|
||||
from flask_cors import CORS
|
||||
from database import init_db, get_db
|
||||
from models import FundBasicInfo, FundDetail
|
||||
from models import FundBasicInfo, FundTrend, FundEstimate, FundPortfolio, FundExtraData
|
||||
from fund_api import FundAPI
|
||||
from fund_list_cache import get_fund_list_cache
|
||||
from sqlalchemy.orm import Session
|
||||
@@ -15,6 +15,71 @@ init_db()
|
||||
fund_api = FundAPI()
|
||||
fund_list_cache = get_fund_list_cache()
|
||||
|
||||
def _json_dumps(data):
|
||||
return json.dumps(data, ensure_ascii=False) if data is not None else None
|
||||
|
||||
def _json_loads(data, default):
|
||||
if not data:
|
||||
return default
|
||||
try:
|
||||
return json.loads(data)
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
def _build_cached_response(db: Session, fund_code: str):
|
||||
basic = db.query(FundBasicInfo).filter(FundBasicInfo.fund_code == fund_code).first()
|
||||
trend = db.query(FundTrend).filter(FundTrend.fund_code == fund_code).first()
|
||||
estimate = db.query(FundEstimate).filter(FundEstimate.fund_code == fund_code).first()
|
||||
portfolio = db.query(FundPortfolio).filter(FundPortfolio.fund_code == fund_code).first()
|
||||
extra = db.query(FundExtraData).filter(FundExtraData.fund_code == fund_code).first()
|
||||
|
||||
if not any([basic, trend, estimate, portfolio, extra]):
|
||||
return None
|
||||
|
||||
data = {}
|
||||
|
||||
if basic:
|
||||
data['basic_info'] = _json_loads(basic.basic_json, {})
|
||||
data['performance'] = _json_loads(basic.performance_json, {})
|
||||
|
||||
if trend:
|
||||
data['net_worth_trend'] = _json_loads(trend.net_worth_trend_json, [])
|
||||
data['accumulated_net_worth'] = _json_loads(trend.accumulated_net_worth_json, [])
|
||||
data['position_trend'] = _json_loads(trend.position_trend_json, [])
|
||||
data['total_return_trend'] = _json_loads(trend.total_return_trend_json, [])
|
||||
data['ranking_trend'] = _json_loads(trend.ranking_trend_json, [])
|
||||
data['ranking_percentage'] = _json_loads(trend.ranking_percentage_json, [])
|
||||
data['scale_fluctuation'] = _json_loads(trend.scale_fluctuation_json, {})
|
||||
|
||||
if estimate:
|
||||
data['realtime_estimate'] = {
|
||||
'name': estimate.name,
|
||||
'fund_code': fund_code,
|
||||
'net_worth': estimate.net_worth,
|
||||
'net_worth_date': estimate.net_worth_date,
|
||||
'estimate_value': estimate.estimate_value,
|
||||
'estimate_change': estimate.estimate_change,
|
||||
'estimate_time': estimate.estimate_time
|
||||
}
|
||||
|
||||
if portfolio:
|
||||
data['portfolio'] = {
|
||||
'stock_codes': _json_loads(portfolio.stock_codes_json, []),
|
||||
'bond_codes': _json_loads(portfolio.bond_codes_json, []),
|
||||
'stock_codes_new': _json_loads(portfolio.stock_codes_new_json, []),
|
||||
'bond_codes_new': _json_loads(portfolio.bond_codes_new_json, [])
|
||||
}
|
||||
|
||||
if extra:
|
||||
data['holder_structure'] = _json_loads(extra.holder_structure_json, {})
|
||||
data['asset_allocation'] = _json_loads(extra.asset_allocation_json, {})
|
||||
data['performance_evaluation'] = _json_loads(extra.performance_evaluation_json, {})
|
||||
data['fund_managers'] = _json_loads(extra.fund_managers_json, [])
|
||||
data['subscription_redemption'] = _json_loads(extra.subscription_redemption_json, {})
|
||||
data['same_type_funds'] = _json_loads(extra.same_type_funds_json, [])
|
||||
|
||||
return data
|
||||
|
||||
@app.route('/')
|
||||
def hello():
|
||||
"""测试接口是否可用"""
|
||||
@@ -57,23 +122,132 @@ def get_fund_detail(fund_code):
|
||||
fund_data = fund_api.get_fund_data(fund_code)
|
||||
|
||||
if fund_data:
|
||||
# 检查数据库是否存在记录
|
||||
cached_fund = db.query(FundDetail).filter(FundDetail.fund_code == fund_code).first()
|
||||
# 更新或新增记录
|
||||
if cached_fund:
|
||||
cached_fund.data_json = json.dumps(fund_data, ensure_ascii=False)
|
||||
cached_fund.net_worth_trend = json.dumps(fund_data.get('net_worth_trend', []), ensure_ascii=False)
|
||||
cached_fund.basic_info = json.dumps(fund_data.get('basic_info', {}), ensure_ascii=False)
|
||||
basic_info = fund_data.get('basic_info', {})
|
||||
performance = fund_data.get('performance', {})
|
||||
trend = {
|
||||
'net_worth_trend': fund_data.get('net_worth_trend', []),
|
||||
'accumulated_net_worth': fund_data.get('accumulated_net_worth', []),
|
||||
'position_trend': fund_data.get('position_trend', []),
|
||||
'total_return_trend': fund_data.get('total_return_trend', []),
|
||||
'ranking_trend': fund_data.get('ranking_trend', []),
|
||||
'ranking_percentage': fund_data.get('ranking_percentage', []),
|
||||
'scale_fluctuation': fund_data.get('scale_fluctuation', {})
|
||||
}
|
||||
estimate = fund_data.get('realtime_estimate', {})
|
||||
portfolio = fund_data.get('portfolio', {})
|
||||
extra = {
|
||||
'holder_structure': fund_data.get('holder_structure', {}),
|
||||
'asset_allocation': fund_data.get('asset_allocation', {}),
|
||||
'performance_evaluation': fund_data.get('performance_evaluation', {}),
|
||||
'fund_managers': fund_data.get('fund_managers', []),
|
||||
'subscription_redemption': fund_data.get('subscription_redemption', {}),
|
||||
'same_type_funds': fund_data.get('same_type_funds', [])
|
||||
}
|
||||
|
||||
basic_record = db.query(FundBasicInfo).filter(FundBasicInfo.fund_code == fund_code).first()
|
||||
if basic_record:
|
||||
basic_record.fund_name = basic_info.get('fund_name')
|
||||
basic_record.fund_type = basic_info.get('fund_type')
|
||||
basic_record.original_rate = basic_info.get('original_rate')
|
||||
basic_record.current_rate = basic_info.get('current_rate')
|
||||
basic_record.min_subscription_amount = basic_info.get('min_subscription_amount')
|
||||
basic_record.is_hb = basic_info.get('is_hb')
|
||||
basic_record.basic_json = _json_dumps(basic_info)
|
||||
basic_record.performance_json = _json_dumps(performance)
|
||||
else:
|
||||
fund_detail = FundDetail(
|
||||
basic_record = FundBasicInfo(
|
||||
fund_code=fund_code,
|
||||
data_json=json.dumps(fund_data, ensure_ascii=False),
|
||||
net_worth_trend=json.dumps(fund_data.get('net_worth_trend', []), ensure_ascii=False),
|
||||
basic_info=json.dumps(fund_data.get('basic_info', {}), ensure_ascii=False)
|
||||
fund_name=basic_info.get('fund_name') or fund_code,
|
||||
fund_type=basic_info.get('fund_type'),
|
||||
original_rate=basic_info.get('original_rate'),
|
||||
current_rate=basic_info.get('current_rate'),
|
||||
min_subscription_amount=basic_info.get('min_subscription_amount'),
|
||||
is_hb=basic_info.get('is_hb'),
|
||||
basic_json=_json_dumps(basic_info),
|
||||
performance_json=_json_dumps(performance)
|
||||
)
|
||||
db.add(fund_detail)
|
||||
db.add(basic_record)
|
||||
|
||||
trend_record = db.query(FundTrend).filter(FundTrend.fund_code == fund_code).first()
|
||||
if trend_record:
|
||||
trend_record.net_worth_trend_json = _json_dumps(trend['net_worth_trend'])
|
||||
trend_record.accumulated_net_worth_json = _json_dumps(trend['accumulated_net_worth'])
|
||||
trend_record.position_trend_json = _json_dumps(trend['position_trend'])
|
||||
trend_record.total_return_trend_json = _json_dumps(trend['total_return_trend'])
|
||||
trend_record.ranking_trend_json = _json_dumps(trend['ranking_trend'])
|
||||
trend_record.ranking_percentage_json = _json_dumps(trend['ranking_percentage'])
|
||||
trend_record.scale_fluctuation_json = _json_dumps(trend['scale_fluctuation'])
|
||||
else:
|
||||
trend_record = FundTrend(
|
||||
fund_code=fund_code,
|
||||
net_worth_trend_json=_json_dumps(trend['net_worth_trend']),
|
||||
accumulated_net_worth_json=_json_dumps(trend['accumulated_net_worth']),
|
||||
position_trend_json=_json_dumps(trend['position_trend']),
|
||||
total_return_trend_json=_json_dumps(trend['total_return_trend']),
|
||||
ranking_trend_json=_json_dumps(trend['ranking_trend']),
|
||||
ranking_percentage_json=_json_dumps(trend['ranking_percentage']),
|
||||
scale_fluctuation_json=_json_dumps(trend['scale_fluctuation'])
|
||||
)
|
||||
db.add(trend_record)
|
||||
|
||||
estimate_record = db.query(FundEstimate).filter(FundEstimate.fund_code == fund_code).first()
|
||||
if estimate_record:
|
||||
estimate_record.name = estimate.get('name')
|
||||
estimate_record.net_worth = estimate.get('net_worth')
|
||||
estimate_record.net_worth_date = estimate.get('net_worth_date')
|
||||
estimate_record.estimate_value = estimate.get('estimate_value')
|
||||
estimate_record.estimate_change = estimate.get('estimate_change')
|
||||
estimate_record.estimate_time = estimate.get('estimate_time')
|
||||
else:
|
||||
estimate_record = FundEstimate(
|
||||
fund_code=fund_code,
|
||||
name=estimate.get('name'),
|
||||
net_worth=estimate.get('net_worth'),
|
||||
net_worth_date=estimate.get('net_worth_date'),
|
||||
estimate_value=estimate.get('estimate_value'),
|
||||
estimate_change=estimate.get('estimate_change'),
|
||||
estimate_time=estimate.get('estimate_time')
|
||||
)
|
||||
db.add(estimate_record)
|
||||
|
||||
portfolio_record = db.query(FundPortfolio).filter(FundPortfolio.fund_code == fund_code).first()
|
||||
if portfolio_record:
|
||||
portfolio_record.stock_codes_json = _json_dumps(portfolio.get('stock_codes', []))
|
||||
portfolio_record.bond_codes_json = _json_dumps(portfolio.get('bond_codes', []))
|
||||
portfolio_record.stock_codes_new_json = _json_dumps(portfolio.get('stock_codes_new', []))
|
||||
portfolio_record.bond_codes_new_json = _json_dumps(portfolio.get('bond_codes_new', []))
|
||||
else:
|
||||
portfolio_record = FundPortfolio(
|
||||
fund_code=fund_code,
|
||||
stock_codes_json=_json_dumps(portfolio.get('stock_codes', [])),
|
||||
bond_codes_json=_json_dumps(portfolio.get('bond_codes', [])),
|
||||
stock_codes_new_json=_json_dumps(portfolio.get('stock_codes_new', [])),
|
||||
bond_codes_new_json=_json_dumps(portfolio.get('bond_codes_new', []))
|
||||
)
|
||||
db.add(portfolio_record)
|
||||
|
||||
extra_record = db.query(FundExtraData).filter(FundExtraData.fund_code == fund_code).first()
|
||||
if extra_record:
|
||||
extra_record.holder_structure_json = _json_dumps(extra['holder_structure'])
|
||||
extra_record.asset_allocation_json = _json_dumps(extra['asset_allocation'])
|
||||
extra_record.performance_evaluation_json = _json_dumps(extra['performance_evaluation'])
|
||||
extra_record.fund_managers_json = _json_dumps(extra['fund_managers'])
|
||||
extra_record.subscription_redemption_json = _json_dumps(extra['subscription_redemption'])
|
||||
extra_record.same_type_funds_json = _json_dumps(extra['same_type_funds'])
|
||||
else:
|
||||
extra_record = FundExtraData(
|
||||
fund_code=fund_code,
|
||||
holder_structure_json=_json_dumps(extra['holder_structure']),
|
||||
asset_allocation_json=_json_dumps(extra['asset_allocation']),
|
||||
performance_evaluation_json=_json_dumps(extra['performance_evaluation']),
|
||||
fund_managers_json=_json_dumps(extra['fund_managers']),
|
||||
subscription_redemption_json=_json_dumps(extra['subscription_redemption']),
|
||||
same_type_funds_json=_json_dumps(extra['same_type_funds'])
|
||||
)
|
||||
db.add(extra_record)
|
||||
|
||||
try:
|
||||
db.commit() # 提交事务
|
||||
db.commit()
|
||||
except Exception as e:
|
||||
print(f"Error saving to database: {e}")
|
||||
db.rollback()
|
||||
@@ -81,13 +255,9 @@ def get_fund_detail(fund_code):
|
||||
return jsonify(fund_data)
|
||||
|
||||
# 如果API获取失败,尝试从数据库获取缓存数据作为兜底
|
||||
cached_fund = db.query(FundDetail).filter(FundDetail.fund_code == fund_code).first()
|
||||
if cached_fund:
|
||||
try:
|
||||
data = json.loads(cached_fund.data_json)
|
||||
return jsonify(data)
|
||||
except:
|
||||
pass
|
||||
cached_data = _build_cached_response(db, fund_code)
|
||||
if cached_data:
|
||||
return jsonify(cached_data)
|
||||
|
||||
return jsonify({"error": "Fund not found"}), 404
|
||||
|
||||
@@ -98,13 +268,19 @@ def get_fund_basic(fund_code):
|
||||
return jsonify({"error": "Fund code is required"}), 400
|
||||
fund_data = fund_api.get_fund_data(fund_code)
|
||||
if fund_data and fund_data.get('basic_info'):
|
||||
# 合并 basic_info 和 performance 数据
|
||||
result = {
|
||||
**fund_data.get('basic_info', {}),
|
||||
**fund_data.get('performance', {})
|
||||
}
|
||||
return jsonify(result)
|
||||
else:
|
||||
|
||||
db = next(get_db())
|
||||
basic = db.query(FundBasicInfo).filter(FundBasicInfo.fund_code == fund_code).first()
|
||||
if basic:
|
||||
basic_info = _json_loads(basic.basic_json, {})
|
||||
performance = _json_loads(basic.performance_json, {})
|
||||
return jsonify({**basic_info, **performance})
|
||||
|
||||
return jsonify({"error": "Fund basic info not found"}), 404
|
||||
|
||||
@app.route('/api/fund/<fund_code>/trend', methods=['GET'])
|
||||
@@ -119,7 +295,15 @@ def get_fund_trend(fund_code):
|
||||
"net_worth_trend": fund_data['net_worth_trend'],
|
||||
"accumulated_net_worth": fund_data.get('accumulated_net_worth', [])
|
||||
})
|
||||
else:
|
||||
|
||||
db = next(get_db())
|
||||
trend = db.query(FundTrend).filter(FundTrend.fund_code == fund_code).first()
|
||||
if trend:
|
||||
return jsonify({
|
||||
"net_worth_trend": _json_loads(trend.net_worth_trend_json, []),
|
||||
"accumulated_net_worth": _json_loads(trend.accumulated_net_worth_json, [])
|
||||
})
|
||||
|
||||
return jsonify({"error": "Fund trend data not found"}), 404
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -10,17 +10,67 @@ class FundBasicInfo(Base):
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
fund_code = Column(String(6), unique=True, nullable=False)
|
||||
fund_name = Column(String(100), nullable=False)
|
||||
fund_name_en = Column(String(200))
|
||||
fund_type = Column(String(50))
|
||||
original_rate = Column(Float)
|
||||
current_rate = Column(Float)
|
||||
min_subscription_amount = Column(String(50))
|
||||
is_hb = Column(String(10))
|
||||
basic_json = Column(Text)
|
||||
performance_json = Column(Text)
|
||||
created_time = Column(DateTime, default=datetime.now)
|
||||
updated_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
class FundDetail(Base):
|
||||
__tablename__ = 'fund_detail'
|
||||
|
||||
class FundTrend(Base):
|
||||
__tablename__ = 'fund_trend'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
fund_code = Column(String(6), nullable=False)
|
||||
data_json = Column(Text) # 存储完整的基金数据
|
||||
net_worth_trend = Column(Text) # 单位净值走势
|
||||
basic_info = Column(Text) # 基础信息
|
||||
created_time = Column(DateTime, default=datetime.now)
|
||||
fund_code = Column(String(6), unique=True, nullable=False)
|
||||
net_worth_trend_json = Column(Text)
|
||||
accumulated_net_worth_json = Column(Text)
|
||||
position_trend_json = Column(Text)
|
||||
total_return_trend_json = Column(Text)
|
||||
ranking_trend_json = Column(Text)
|
||||
ranking_percentage_json = Column(Text)
|
||||
scale_fluctuation_json = Column(Text)
|
||||
updated_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
|
||||
class FundEstimate(Base):
|
||||
__tablename__ = 'fund_estimate'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
fund_code = Column(String(6), unique=True, nullable=False)
|
||||
name = Column(String(100))
|
||||
net_worth = Column(String(50))
|
||||
net_worth_date = Column(String(50))
|
||||
estimate_value = Column(String(50))
|
||||
estimate_change = Column(String(50))
|
||||
estimate_time = Column(String(50))
|
||||
updated_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
|
||||
class FundPortfolio(Base):
|
||||
__tablename__ = 'fund_portfolio'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
fund_code = Column(String(6), unique=True, nullable=False)
|
||||
stock_codes_json = Column(Text)
|
||||
bond_codes_json = Column(Text)
|
||||
stock_codes_new_json = Column(Text)
|
||||
bond_codes_new_json = Column(Text)
|
||||
updated_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
|
||||
|
||||
class FundExtraData(Base):
|
||||
__tablename__ = 'fund_extra_data'
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
fund_code = Column(String(6), unique=True, nullable=False)
|
||||
holder_structure_json = Column(Text)
|
||||
asset_allocation_json = Column(Text)
|
||||
performance_evaluation_json = Column(Text)
|
||||
fund_managers_json = Column(Text)
|
||||
subscription_redemption_json = Column(Text)
|
||||
same_type_funds_json = Column(Text)
|
||||
updated_time = Column(DateTime, default=datetime.now, onupdate=datetime.now)
|
||||
@@ -2,6 +2,7 @@ import requests
|
||||
import json
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
|
||||
class StockService:
|
||||
_instance = None
|
||||
@@ -20,13 +21,54 @@ class StockService:
|
||||
return
|
||||
self.stock_details = {} # Map code -> {name, market}
|
||||
self.last_update = 0
|
||||
self.cache_ttl = 24 * 3600 # 24 hours
|
||||
self.cache_ttl = 24 * 3600 * 10 # 10 days
|
||||
self.cache_file = os.path.abspath(
|
||||
os.path.join(os.path.dirname(__file__), "..", "Data", "stock_list_cache.json")
|
||||
)
|
||||
self._load_data()
|
||||
self._initialized = True
|
||||
|
||||
def _load_data(self):
|
||||
"""Fetch data from APIs in a separate thread to avoid blocking startup"""
|
||||
threading.Thread(target=self._fetch_all, daemon=True).start()
|
||||
"""Load stock data from local cache; refresh if missing or expired."""
|
||||
loaded = self._load_from_cache()
|
||||
|
||||
if not loaded or self._is_cache_expired():
|
||||
threading.Thread(target=self._refresh_cache, daemon=True).start()
|
||||
|
||||
def _load_from_cache(self):
|
||||
if not os.path.exists(self.cache_file):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(self.cache_file, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
self.last_update = data.get("last_update", 0)
|
||||
self.stock_details = data.get("stock_details", {})
|
||||
return bool(self.stock_details)
|
||||
except Exception as e:
|
||||
print(f"Error loading stock cache: {e}")
|
||||
return False
|
||||
|
||||
def _is_cache_expired(self):
|
||||
if not self.last_update:
|
||||
return True
|
||||
return (time.time() - self.last_update) > self.cache_ttl
|
||||
|
||||
def _save_to_cache(self):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(self.cache_file), exist_ok=True)
|
||||
with open(self.cache_file, "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"last_update": self.last_update,
|
||||
"stock_details": self.stock_details
|
||||
}, f, ensure_ascii=False)
|
||||
except Exception as e:
|
||||
print(f"Error saving stock cache: {e}")
|
||||
|
||||
def _refresh_cache(self):
|
||||
"""Download stock list and save to local cache."""
|
||||
self._fetch_all()
|
||||
self._save_to_cache()
|
||||
|
||||
def _fetch_all(self):
|
||||
self._fetch_hk_stocks()
|
||||
|
||||
BIN
Data/funds.db
BIN
Data/funds.db
Binary file not shown.
1
Data/stock_list_cache.json
Normal file
1
Data/stock_list_cache.json
Normal file
File diff suppressed because one or more lines are too long
@@ -15,7 +15,7 @@
|
||||
<th>时间</th>
|
||||
<th v-for="serie in series" :key="serie.name">
|
||||
<span class="legend-dot" :style="{ background: getColor(serie.name) }"></span>
|
||||
{{ serie.name }}
|
||||
{{ formatLegendName(serie.name) }}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -70,6 +70,11 @@ export default {
|
||||
return colors[name] || '#5470c6'
|
||||
}
|
||||
|
||||
const formatLegendName = (name) => {
|
||||
if (!name) return ''
|
||||
return name.replace(/比例/g, '')
|
||||
}
|
||||
|
||||
const formatValue = (value) => {
|
||||
if (value === null || value === undefined) return '--'
|
||||
return value.toFixed(2) + '%'
|
||||
@@ -86,7 +91,7 @@ export default {
|
||||
|
||||
// 准备堆叠柱状图数据
|
||||
const seriesData = series.value.map(serie => ({
|
||||
name: serie.name,
|
||||
name: formatLegendName(serie.name),
|
||||
type: 'bar',
|
||||
stack: 'total',
|
||||
barWidth: '50%',
|
||||
@@ -119,7 +124,7 @@ export default {
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
data: series.value.map(s => s.name),
|
||||
data: series.value.map(s => formatLegendName(s.name)),
|
||||
bottom: 0
|
||||
},
|
||||
grid: {
|
||||
@@ -168,6 +173,7 @@ export default {
|
||||
series,
|
||||
hasData,
|
||||
getColor,
|
||||
formatLegendName,
|
||||
formatValue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ export default {
|
||||
.portfolio-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.portfolio-header {
|
||||
@@ -189,7 +190,7 @@ export default {
|
||||
|
||||
.col-market {
|
||||
width: 60px;
|
||||
text-align: right;
|
||||
text-align: left;
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export default {
|
||||
emits: ['fund-select'],
|
||||
setup(props, { emit }) {
|
||||
const activePeriod = ref(0)
|
||||
const periods = ['近1年', '近2年', '近3年', '近5年', '今年以来']
|
||||
const periods = ['主题1', '主题2', '主题3', '主题4', '主题5']
|
||||
|
||||
const currentFunds = computed(() => {
|
||||
if (!props.sameTypeFunds || !Array.isArray(props.sameTypeFunds)) {
|
||||
|
||||
36
开发笔记.md
36
开发笔记.md
@@ -187,36 +187,10 @@
|
||||
|
||||
|
||||
|
||||
### 1.2 核心函数解释
|
||||
## 2. 后续开发计划
|
||||
|
||||
- **get_fund_basic_info(fund_code)**
|
||||
- 功能:获取基金综合信息,包括实时估值、基本资料等
|
||||
- 输入:基金代码
|
||||
- 输出:基金最近一个交易日的单位净值(dwjz)、基金名称(name)、净值日期(jzrq)、估计净值(gsz)、估计增长率%(gszzl)、估计日期(gztime)等
|
||||
1. 添加基金自选功能,便于浏览持有基金的情况(写json或者数据库)
|
||||
2. 添加基金对比功能,支持多只基金放在同一版图进行数据对比
|
||||
3. 添加模型预测功能,通过LSTM或者CNN实现对基金走势的预测
|
||||
4. 添加基金回测功能,找到合适的量化策略
|
||||
|
||||
- **get_fund_net_worth_history(fund_code, years=None)**
|
||||
- 功能:获取基金历史净值数据
|
||||
- 输入:基金代码;显示最近多少年的数据(None表示显示全部)
|
||||
- 输出:包含日期和净值的DataFrame
|
||||
|
||||
- **calculate_max_drawdown(net_worth_series)**
|
||||
- 功能:计算最大回撤
|
||||
- 输入:净值序列
|
||||
- 输出:包含最大回撤信息的字典
|
||||
|
||||
- **calculate_technical_indicators(df, window=20)**
|
||||
- 功能:计算技术指标,包含20日均值,累计收益率等
|
||||
- **plot_fund_vector_graph(fund_code, years=None, save_path=None)**
|
||||
- 功能:绘制基金历史表现矢量图
|
||||
- fund_code (str): 基金代码
|
||||
- years (int, optional): 显示最近多少年的数据,None表示显示全部数据,1表示最近1年的数据,依此类推
|
||||
- save_path (str, optional): 保存路径,例如 'fund_plot.svg';默认为 None (直接显示图表)
|
||||
|
||||
|
||||
|
||||
## 预测策略
|
||||
|
||||
LSTM方法
|
||||
|
||||
- **回溯** :滑动窗口的长度,表示我们回溯过去的周期数。
|
||||
- **前瞻** :我们想要预测未来的周期数。
|
||||
Reference in New Issue
Block a user