fix: 修复刷新问题;修复不能修改品种合约问题

alphaFuthures
Lxy 7 days ago
parent e1bc7c8ca3
commit 46edd861b5

@ -7,7 +7,7 @@ import shutil
from pathlib import Path from pathlib import Path
from typing import Optional from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Body from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Body, Request
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from pydantic import BaseModel from pydantic import BaseModel
@ -48,12 +48,12 @@ def get_config():
@router.post("/upload") @router.post("/upload")
def upload_config( async def upload_config(
file: Optional[UploadFile] = File(None), file: Optional[UploadFile] = File(None),
json_config: Optional[dict] = Body(None, embed=False), request: Request = None,
): ):
""" """
上传品种配置文件JSON格式 上传品种配置文件(JSON格式)
格式示例: 格式示例:
{ {
"futures": {"沪银": "AG2606", "沪金": "AU2606"}, "futures": {"沪银": "AG2606", "沪金": "AU2606"},
@ -64,12 +64,14 @@ def upload_config(
try: try:
if file: if file:
content = file.file.read() content = await file.read()
data = json.loads(content) data = json.loads(content)
elif json_config:
data = json_config
else: else:
raise HTTPException(status_code=400, detail="请提供配置文件或JSON数据") # 直接从请求体读取 JSON
body = await request.body()
if not body:
raise HTTPException(status_code=400, detail="请提供配置文件或JSON数据")
data = json.loads(body)
if not isinstance(data, dict): if not isinstance(data, dict):
raise HTTPException(status_code=400, detail="配置文件必须是 JSON 对象") raise HTTPException(status_code=400, detail="配置文件必须是 JSON 对象")

@ -44,6 +44,9 @@ def get_futures_list(db: Session = Depends(get_db)):
for name, symbol_code in futures_config.items(): for name, symbol_code in futures_config.items():
cached = get_cached_data(db, symbol_code, "futures") cached = get_cached_data(db, symbol_code, "futures")
if cached and cached.get("timeframes"): if cached and cached.get("timeframes"):
# 优先使用缓存中的 current_price 字段
current_price = cached.get("current_price")
all_candles = [] all_candles = []
for period, candles in cached.get("timeframes", {}).items(): for period, candles in cached.get("timeframes", {}).items():
all_candles.extend(candles) all_candles.extend(candles)
@ -55,22 +58,26 @@ def get_futures_list(db: Session = Depends(get_db)):
high_price = float(latest_candle.get("high", 0)) high_price = float(latest_candle.get("high", 0))
low_price = float(latest_candle.get("low", 0)) low_price = float(latest_candle.get("low", 0))
change = close_price - open_price # 如果 current_price 为空,则使用收盘价
if not current_price:
current_price = close_price
change = current_price - open_price
change_pct = (change / open_price * 100) if open_price > 0 else 0 change_pct = (change / open_price * 100) if open_price > 0 else 0
futures_data.append({ futures_data.append({
"symbol": symbol_code, "symbol": symbol_code,
"name": name, "name": name,
"price": close_price, "price": current_price,
"change": round(change, 2), "change": round(change, 2),
"changePct": round(change_pct, 2), "changePct": round(change_pct, 2),
"suggestion": _get_suggestion(close_price, open_price, change_pct), "suggestion": _get_suggestion(current_price, open_price, change_pct),
"suggestionType": "up" if change >= 0 else "down", "suggestionType": "up" if change >= 0 else "down",
"periods": _get_period_trends(all_candles), "periods": _get_period_trends(all_candles),
"successRate": _calc_success_rate(all_candles), "successRate": _calc_success_rate(all_candles),
"trendScore": _calc_trend_score(all_candles), "trendScore": _calc_trend_score(all_candles),
"resistance": round(2 * ((high_price + low_price + close_price) / 3) - low_price, 2), "resistance": round(2 * ((high_price + low_price + current_price) / 3) - low_price, 2),
"support": round(2 * ((high_price + low_price + close_price) / 3) - high_price, 2), "support": round(2 * ((high_price + low_price + current_price) / 3) - high_price, 2),
"open": open_price, "open": open_price,
"high": high_price, "high": high_price,
"low": low_price, "low": low_price,
@ -106,37 +113,46 @@ def get_futures_detail(symbol: str, db: Session = Depends(get_db)):
if not cached: if not cached:
raise HTTPException(status_code=404, detail=f"未找到 {symbol} 的缓存数据") raise HTTPException(status_code=404, detail=f"未找到 {symbol} 的缓存数据")
# 优先使用缓存中的 current_price 字段
current_price = cached.get("current_price")
# 从所有K线数据中获取最新的K线用于计算指标
all_candles = [] all_candles = []
for period, candles in cached.get("timeframes", {}).items(): for period, candles in cached.get("timeframes", {}).items():
all_candles.extend(candles) all_candles.extend(candles)
if not all_candles: if not all_candles:
raise HTTPException(status_code=404, detail=f"未找到 {symbol} 的K线数据") raise HTTPException(status_code=404, detail=f"未找到 {symbol} 的K线数据")
# 如果 current_price 为空,则从K线数据中获取最新收盘价
if not current_price:
current_price = float(all_candles[-1].get("close", 0))
# 使用最新一条K线数据计算指标
latest_candle = all_candles[-1] latest_candle = all_candles[-1]
open_price = float(latest_candle.get("open", 0)) open_price = float(latest_candle.get("open", 0))
close_price = float(latest_candle.get("close", 0)) close_price = float(latest_candle.get("close", current_price))
high_price = float(latest_candle.get("high", 0)) high_price = float(latest_candle.get("high", 0))
low_price = float(latest_candle.get("low", 0)) low_price = float(latest_candle.get("low", 0))
change = close_price - open_price change = current_price - open_price
change_pct = (change / open_price * 100) if open_price > 0 else 0 change_pct = (change / open_price * 100) if open_price > 0 else 0
# Pivot Point 公式计算关键点位 # Pivot Point 公式计算关键点位
pp = (high_price + low_price + close_price) / 3 pp = (high_price + low_price + current_price) / 3
r1 = round(2 * pp - low_price, 2) r1 = round(2 * pp - low_price, 2)
r2 = round(pp + (high_price - low_price), 2) r2 = round(pp + (high_price - low_price), 2)
s1 = round(2 * pp - high_price, 2) s1 = round(2 * pp - high_price, 2)
s2 = round(pp - (high_price - low_price), 2) s2 = round(pp - (high_price - low_price), 2)
suggestion = _get_suggestion(close_price, open_price, change_pct) suggestion = _get_suggestion(current_price, open_price, change_pct)
suggestion_type = "up" if change >= 0 else "down" suggestion_type = "up" if change >= 0 else "down"
trend_score = _calc_trend_score(all_candles) trend_score = _calc_trend_score(all_candles)
data = { data = {
"symbol": symbol, "symbol": symbol,
"name": _get_futures_name(symbol), "name": _get_futures_name(symbol),
"price": close_price, "price": current_price,
"change": round(change, 2), "change": round(change, 2),
"changePct": round(change_pct, 2), "changePct": round(change_pct, 2),
"suggestion": suggestion, "suggestion": suggestion,
@ -146,7 +162,7 @@ def get_futures_detail(symbol: str, db: Session = Depends(get_db)):
"high": high_price, "high": high_price,
"low": low_price, "low": low_price,
"volume": sum(float(c.get("volume", 0)) for c in all_candles), "volume": sum(float(c.get("volume", 0)) for c in all_candles),
"entryPrice": round(close_price * 0.995, 2) if change >= 0 else round(close_price * 1.005, 2), "entryPrice": round(current_price * 0.995, 2) if change >= 0 else round(current_price * 1.005, 2),
"targetPrice": r1 if change >= 0 else s1, "targetPrice": r1 if change >= 0 else s1,
"stopLoss": s1 if change >= 0 else r1, "stopLoss": s1 if change >= 0 else r1,
"riskLevel": "" if trend_score >= 80 else "" if trend_score >= 60 else "", "riskLevel": "" if trend_score >= 80 else "" if trend_score >= 60 else "",

@ -1176,6 +1176,42 @@
</div> </div>
</div> </div>
<!-- Edit Symbol Modal -->
<div class="modal-overlay" id="editSymbolModal">
<div class="modal">
<div class="modal-header">
<div class="modal-title">修改变种</div>
<button class="modal-close" onclick="closeEditSymbolModal()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 24px; height: 24px;">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div class="modal-body">
<input type="hidden" id="editSymbolType">
<input type="hidden" id="editSymbolOldName">
<input type="hidden" id="editSymbolOldCode">
<div class="form-group">
<label class="form-label">品种名称</label>
<input class="form-input" id="editSymbolName" placeholder="如: 原油">
</div>
<div class="form-group">
<label class="form-label">合约代码</label>
<input class="form-input" id="editSymbolCode" placeholder="如: SC2609">
</div>
</div>
<div class="modal-footer">
<button class="btn btn-outline" onclick="closeEditSymbolModal()">取消</button>
<button class="btn btn-primary" onclick="saveEditSymbol()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width: 16px; height: 16px;"><path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
保存
</button>
</div>
</div>
</div>
<!-- Edit Task Modal --> <!-- Edit Task Modal -->
<div class="modal-overlay" id="editTaskModal"> <div class="modal-overlay" id="editTaskModal">
<div class="modal"> <div class="modal">
@ -1453,22 +1489,46 @@
} }
} }
async function editSymbol(type, oldCode, oldName) { function openEditSymbolModal(type, code, name) {
const newName = prompt('请输入新的品种名称:', oldName); document.getElementById('editSymbolType').value = type;
if (!newName || newName === oldName) return; document.getElementById('editSymbolOldName').value = name;
document.getElementById('editSymbolOldCode').value = code;
document.getElementById('editSymbolName').value = name;
document.getElementById('editSymbolCode').value = code;
document.getElementById('editSymbolModal').classList.add('active');
}
const newCode = prompt('请输入新的品种代码:', oldCode); function closeEditSymbolModal() {
if (!newCode) return; document.getElementById('editSymbolModal').classList.remove('active');
}
const symbols = type === 'futures' ? (currentConfig.futures || {}) : (currentConfig.stock || {}); async function saveEditSymbol() {
delete symbols[oldName]; const type = document.getElementById('editSymbolType').value;
symbols[newName] = newCode; const oldName = document.getElementById('editSymbolOldName').value;
const oldCode = document.getElementById('editSymbolOldCode').value;
const newName = document.getElementById('editSymbolName').value.trim();
const newCode = document.getElementById('editSymbolCode').value.trim();
if (!newName || !newCode) {
return showToast('请填写品种名称和合约代码', 'error');
}
// 深拷贝配置,避免直接修改 currentConfig
const fullConfig = { const fullConfig = {
futures: currentConfig.futures || {}, futures: JSON.parse(JSON.stringify(currentConfig.futures || {})),
stock: currentConfig.stock || {} stock: JSON.parse(JSON.stringify(currentConfig.stock || {}))
}; };
fullConfig[type] = symbols;
const symbols = fullConfig[type];
if (newName !== oldName && symbols[newName]) {
return showToast('品种名称已存在', 'error');
}
delete symbols[oldName];
symbols[newName] = newCode;
console.log('保存配置:', JSON.stringify(fullConfig, null, 2));
try { try {
const res = await fetch(`${API}/config/upload`, { const res = await fetch(`${API}/config/upload`, {
@ -1477,18 +1537,25 @@
body: JSON.stringify(fullConfig), body: JSON.stringify(fullConfig),
}); });
const data = await res.json();
if (res.ok) { if (res.ok) {
showToast('品种修改成功', 'success'); showToast('品种修改成功', 'success');
closeEditSymbolModal();
loadConfig(); loadConfig();
} else { } else {
const data = await res.json(); console.error('修改失败响应:', data);
showToast(data.detail || '修改失败', 'error'); showToast(data.detail || '修改失败', 'error');
} }
} catch (e) { } catch (e) {
console.error('修改异常:', e);
showToast(`修改失败: ${e.message}`, 'error'); showToast(`修改失败: ${e.message}`, 'error');
} }
} }
async function editSymbol(type, code, name) {
openEditSymbolModal(type, code, name);
}
async function deleteSymbol(type, code) { async function deleteSymbol(type, code) {
if (!confirm('确定删除此品种?')) return; if (!confirm('确定删除此品种?')) return;

@ -22,8 +22,6 @@
"20号胶": "NR2607", "20号胶": "NR2607",
"螺纹钢": "RB2610", "螺纹钢": "RB2610",
"铁矿石": "I2609", "铁矿石": "I2609",
"焦煤": "JM2606",
"焦炭": "J2606",
"PTA": "TA2609", "PTA": "TA2609",
"棕榈油": "P2609", "棕榈油": "P2609",
"豆粕": "M2609", "豆粕": "M2609",
@ -31,6 +29,9 @@
"棉花": "CF2609", "棉花": "CF2609",
"甲醇": "MA2609", "甲醇": "MA2609",
"尿素": "UR2609", "尿素": "UR2609",
"中证1000": "IM2606" "中证1000": "IM2606",
} "焦煤": "JM2609",
"焦炭": "J2609"
},
"stock": {}
} }

Binary file not shown.

Binary file not shown.
Loading…
Cancel
Save