Compare commits
No commits in common. 'develop' and 'master' have entirely different histories.
@ -1,18 +0,0 @@
|
||||
---
|
||||
name: AlphaFutures
|
||||
description: A brief description of what this skill does
|
||||
---
|
||||
|
||||
# AlphaFutures
|
||||
|
||||
Instructions for the agent to follow when this skill is activated.
|
||||
|
||||
## When to use
|
||||
|
||||
Describe when this skill should be used.
|
||||
|
||||
## Instructions
|
||||
|
||||
1. First step
|
||||
2. Second step
|
||||
3. Additional steps as needed
|
||||
Binary file not shown.
@ -1,93 +0,0 @@
|
||||
{
|
||||
"database": {
|
||||
"mongoDB": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 10000,
|
||||
"database": "aaa",
|
||||
"username": "aaa",
|
||||
"password": "aaaa",
|
||||
"authSource": "aaa",
|
||||
"ssl": false,
|
||||
"enabled": true
|
||||
},
|
||||
"postgreSQL": {
|
||||
"host": "localhost",
|
||||
"port": 5432,
|
||||
"database": "alpha-futures",
|
||||
"username": "postgres",
|
||||
"password": "password",
|
||||
"ssl": false,
|
||||
"enabled": true
|
||||
},
|
||||
"redis": {
|
||||
"host": "localhost",
|
||||
"port": 6379,
|
||||
"password": "",
|
||||
"db": 0,
|
||||
"enabled": true
|
||||
},
|
||||
"influxDB": {
|
||||
"host": "localhost",
|
||||
"port": 8086,
|
||||
"database": "alpha-futures",
|
||||
"username": "",
|
||||
"password": "",
|
||||
"ssl": false,
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"server": {
|
||||
"port": 3007,
|
||||
"host": "0.0.0.0",
|
||||
"environment": "development",
|
||||
"debug": true,
|
||||
"timeout": 30000,
|
||||
"maxBodySize": "10mb"
|
||||
},
|
||||
"security": {
|
||||
"jwtSecret": "your-secret-key",
|
||||
"jwtExpiresIn": "7d",
|
||||
"rateLimit": {
|
||||
"windowMs": 60000,
|
||||
"max": 120
|
||||
},
|
||||
"cors": {
|
||||
"origin": "*",
|
||||
"methods": "GET, POST, PUT, DELETE, OPTIONS",
|
||||
"allowedHeaders": "Content-Type, Authorization"
|
||||
}
|
||||
},
|
||||
"dataSource": {
|
||||
"test": {
|
||||
"enabled": false,
|
||||
"timeout": 10000,
|
||||
"retries": 3,
|
||||
"refreshInterval": 60000
|
||||
},
|
||||
"tqsdk": {
|
||||
"enabled": true,
|
||||
"username": "windsdreamer",
|
||||
"password": "1qazse42W3",
|
||||
"pythonPort": 8001 ,
|
||||
"timeout": 10000,
|
||||
"retries": 10,
|
||||
"maxConnections": 20
|
||||
},
|
||||
"wind": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"apiSecret": "",
|
||||
"url": "https://api.wind.com.cn",
|
||||
"timeout": 30000,
|
||||
"retries": 3
|
||||
},
|
||||
"sina": {
|
||||
"enabled": false,
|
||||
"url": "https://finance.sina.com.cn",
|
||||
"timeout": 10000,
|
||||
"retries": 3,
|
||||
"refreshInterval": 60000
|
||||
},
|
||||
"defaultDataSource": "tqsdk"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,12 +0,0 @@
|
||||
# API适配器包初始化文件
|
||||
from qihuo_analyzer.data.api_adapters.base_adapter import BaseDataAdapter
|
||||
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
|
||||
from qihuo_analyzer.data.api_adapters.rqdata_adapter import RqDataAdapter
|
||||
from qihuo_analyzer.data.api_adapters.adapter_factory import DataAdapterFactory
|
||||
|
||||
__all__ = [
|
||||
'BaseDataAdapter',
|
||||
'TqSdkAdapter',
|
||||
'RqDataAdapter',
|
||||
'DataAdapterFactory'
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,3 +0,0 @@
|
||||
# 期货分析系统版本信息
|
||||
__version__ = "1.0.0"
|
||||
__author__ = "AI Futures Analyzer"
|
||||
Binary file not shown.
Binary file not shown.
@ -1,104 +0,0 @@
|
||||
# 核心数据模型
|
||||
import datetime
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class MarketData:
|
||||
"""市场数据模型"""
|
||||
|
||||
def __init__(self, symbol: str, kline_data: pd.DataFrame):
|
||||
self.symbol = symbol
|
||||
self.kline_data = kline_data
|
||||
self.timestamp = datetime.datetime.now()
|
||||
|
||||
def get_latest_price(self) -> float:
|
||||
"""获取最新价格"""
|
||||
return float(self.kline_data['close'].iloc[-1])
|
||||
|
||||
def get_price_range(self, period: int = 20) -> Tuple[float, float]:
|
||||
"""获取价格范围"""
|
||||
prices = self.kline_data['close'].tail(period)
|
||||
return float(prices.min()), float(prices.max())
|
||||
|
||||
|
||||
class AnalysisResult:
|
||||
"""分析结果模型"""
|
||||
|
||||
def __init__(self, symbol: str):
|
||||
self.symbol = symbol
|
||||
self.timestamp = datetime.datetime.now()
|
||||
self.trend: Optional[str] = None # bullish, bearish, neutral
|
||||
self.probability: Optional[float] = None # 胜率
|
||||
self.direction: Optional[str] = None # long, short, wait
|
||||
self.cycle: Optional[str] = None # short, medium, long
|
||||
self.atr: Optional[float] = None # 真实波动幅度
|
||||
self.adx: Optional[float] = None # 平均趋向指标
|
||||
self.support: Optional[float] = None # 支撑位
|
||||
self.resistance: Optional[float] = None # 阻力位
|
||||
self.stop_loss: Optional[float] = None # 止损位
|
||||
self.target_price: Optional[float] = None # 目标价
|
||||
self.position_size: Optional[float] = None # 建议仓位
|
||||
self.risk_ratio: Optional[float] = None # 风险比率
|
||||
self.fund_flow: Optional[Dict[str, float]] = None # 资金流向
|
||||
self.signals: Dict[str, str] = {} # 各维度信号
|
||||
|
||||
def to_dict(self) -> Dict:
|
||||
"""转换为字典"""
|
||||
return {
|
||||
'symbol': self.symbol,
|
||||
'timestamp': self.timestamp.isoformat(),
|
||||
'trend': self.trend,
|
||||
'probability': self.probability,
|
||||
'direction': self.direction,
|
||||
'cycle': self.cycle,
|
||||
'atr': self.atr,
|
||||
'adx': self.adx,
|
||||
'support': self.support,
|
||||
'resistance': self.resistance,
|
||||
'stop_loss': self.stop_loss,
|
||||
'target_price': self.target_price,
|
||||
'position_size': self.position_size,
|
||||
'risk_ratio': self.risk_ratio,
|
||||
'fund_flow': self.fund_flow,
|
||||
'signals': self.signals
|
||||
}
|
||||
|
||||
|
||||
class StrategyConfig:
|
||||
"""策略配置模型"""
|
||||
|
||||
def __init__(self):
|
||||
# 技术指标参数
|
||||
self.macd_fast = 12
|
||||
self.macd_slow = 26
|
||||
self.macd_signal = 9
|
||||
self.rsi_period = 14
|
||||
self.bollinger_period = 20
|
||||
self.bollinger_std = 2
|
||||
self.kdj_period = 9
|
||||
self.kdj_signal = 3
|
||||
self.adx_period = 14
|
||||
|
||||
# 趋势过滤参数
|
||||
self.short_ma = 20
|
||||
self.long_ma = 60
|
||||
|
||||
# 风险控制参数
|
||||
self.atr_multiplier = 2.0
|
||||
self.max_risk_percent = 0.02
|
||||
self.min_profit_loss_ratio = 1.5
|
||||
|
||||
# 资金监控参数
|
||||
self.volume_change_threshold = 0.05
|
||||
self.open_interest_change_threshold = 0.05
|
||||
|
||||
|
||||
class RiskParams:
|
||||
"""风险参数模型"""
|
||||
|
||||
def __init__(self, account_balance: float):
|
||||
self.account_balance = account_balance
|
||||
self.max_risk_amount = account_balance * 0.02
|
||||
self.max_position_percent = 0.3
|
||||
self.max_leverage = 5
|
||||
Binary file not shown.
Binary file not shown.
@ -1,12 +0,0 @@
|
||||
# API适配器包初始化文件
|
||||
from qihuo_analyzer.data.api_adapters.base_adapter import BaseDataAdapter
|
||||
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
|
||||
from qihuo_analyzer.data.api_adapters.rqdata_adapter import RqDataAdapter
|
||||
from qihuo_analyzer.data.api_adapters.adapter_factory import DataAdapterFactory
|
||||
|
||||
__all__ = [
|
||||
'BaseDataAdapter',
|
||||
'TqSdkAdapter',
|
||||
'RqDataAdapter',
|
||||
'DataAdapterFactory'
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,274 +0,0 @@
|
||||
# 风控管理模块
|
||||
import pandas as pd
|
||||
from typing import Dict, Optional, Tuple
|
||||
from qihuo_analyzer.utils.technical_analysis import calculate_atr
|
||||
from qihuo_analyzer.core.models import StrategyConfig, RiskParams
|
||||
|
||||
|
||||
class RiskManager:
|
||||
"""风险管理器"""
|
||||
|
||||
def __init__(self, config: Optional[StrategyConfig] = None):
|
||||
self.config = config or StrategyConfig()
|
||||
|
||||
def calculate_stop_loss(self, data: pd.DataFrame, entry_price: float, direction: str, atr_multiplier: Optional[float] = None) -> float:
|
||||
"""计算止损位"""
|
||||
atr_multiplier = atr_multiplier or self.config.atr_multiplier
|
||||
|
||||
# 计算ATR
|
||||
atr = calculate_atr(data).iloc[-1]
|
||||
|
||||
# 根据方向计算止损位
|
||||
if direction == 'long':
|
||||
stop_loss = entry_price - (atr * atr_multiplier)
|
||||
elif direction == 'short':
|
||||
stop_loss = entry_price + (atr * atr_multiplier)
|
||||
else:
|
||||
raise ValueError("Direction must be 'long' or 'short'")
|
||||
|
||||
return stop_loss
|
||||
|
||||
def calculate_position_size(self, account_balance: float, data: pd.DataFrame, direction: str, entry_price: float,
|
||||
contract_multiplier: float = 10, margin_rate: float = 0.1) -> Dict:
|
||||
"""计算仓位大小"""
|
||||
# 计算ATR
|
||||
atr = calculate_atr(data).iloc[-1]
|
||||
|
||||
# 计算每手风险
|
||||
if direction == 'long':
|
||||
risk_per_unit = atr * self.config.atr_multiplier * contract_multiplier
|
||||
elif direction == 'short':
|
||||
risk_per_unit = atr * self.config.atr_multiplier * contract_multiplier
|
||||
else:
|
||||
raise ValueError("Direction must be 'long' or 'short'")
|
||||
|
||||
# 计算最大风险金额
|
||||
max_risk_amount = account_balance * self.config.max_risk_percent
|
||||
|
||||
# 计算建议手数
|
||||
suggested_units = max_risk_amount / risk_per_unit
|
||||
suggested_units = max(1, int(suggested_units)) # 至少1手
|
||||
|
||||
# 计算保证金需求
|
||||
margin_per_unit = entry_price * contract_multiplier * margin_rate
|
||||
total_margin = suggested_units * margin_per_unit
|
||||
|
||||
# 计算实际风险比例
|
||||
actual_risk_percent = (risk_per_unit * suggested_units) / account_balance
|
||||
|
||||
# 计算杠杆比例
|
||||
leverage = (suggested_units * entry_price * contract_multiplier) / account_balance
|
||||
|
||||
return {
|
||||
'suggested_units': suggested_units,
|
||||
'risk_per_unit': risk_per_unit,
|
||||
'max_risk_amount': max_risk_amount,
|
||||
'margin_per_unit': margin_per_unit,
|
||||
'total_margin': total_margin,
|
||||
'actual_risk_percent': actual_risk_percent,
|
||||
'leverage': leverage,
|
||||
'atr': atr
|
||||
}
|
||||
|
||||
def calculate_profit_loss_ratio(self, entry_price: float, stop_loss: float, target_price: float, direction: str) -> float:
|
||||
"""计算盈亏比"""
|
||||
if direction == 'long':
|
||||
profit = target_price - entry_price
|
||||
loss = entry_price - stop_loss
|
||||
elif direction == 'short':
|
||||
profit = entry_price - target_price
|
||||
loss = stop_loss - entry_price
|
||||
else:
|
||||
raise ValueError("Direction must be 'long' or 'short'")
|
||||
|
||||
if loss == 0:
|
||||
return float('inf')
|
||||
|
||||
return profit / loss
|
||||
|
||||
def validate_trade(self, account_balance: float, data: pd.DataFrame, direction: str,
|
||||
entry_price: float, target_price: float, contract_multiplier: float = 10,
|
||||
margin_rate: float = 0.1) -> Dict:
|
||||
"""验证交易是否符合风控要求"""
|
||||
# 计算止损位
|
||||
stop_loss = self.calculate_stop_loss(data, entry_price, direction)
|
||||
|
||||
# 计算盈亏比
|
||||
pl_ratio = self.calculate_profit_loss_ratio(entry_price, stop_loss, target_price, direction)
|
||||
|
||||
# 计算仓位大小
|
||||
position_info = self.calculate_position_size(account_balance, data, direction, entry_price,
|
||||
contract_multiplier, margin_rate)
|
||||
|
||||
# 检查各项风控指标
|
||||
checks = {
|
||||
'profit_loss_ratio': {
|
||||
'value': pl_ratio,
|
||||
'required': self.config.min_profit_loss_ratio,
|
||||
'pass': pl_ratio >= self.config.min_profit_loss_ratio
|
||||
},
|
||||
'risk_percent': {
|
||||
'value': position_info['actual_risk_percent'] * 100,
|
||||
'required': self.config.max_risk_percent * 100,
|
||||
'pass': position_info['actual_risk_percent'] <= self.config.max_risk_percent
|
||||
},
|
||||
'leverage': {
|
||||
'value': position_info['leverage'],
|
||||
'required': 5, # 最大杠杆
|
||||
'pass': position_info['leverage'] <= 5
|
||||
},
|
||||
'margin_utilization': {
|
||||
'value': (position_info['total_margin'] / account_balance) * 100,
|
||||
'required': 30, # 最大保证金使用率
|
||||
'pass': (position_info['total_margin'] / account_balance) <= 0.3
|
||||
}
|
||||
}
|
||||
|
||||
# 综合判断
|
||||
all_passed = all(check['pass'] for check in checks.values())
|
||||
|
||||
return {
|
||||
'valid': all_passed,
|
||||
'checks': checks,
|
||||
'position_info': position_info,
|
||||
'stop_loss': stop_loss,
|
||||
'profit_loss_ratio': pl_ratio
|
||||
}
|
||||
|
||||
def generate_risk_report(self, account_balance: float, data: pd.DataFrame, direction: str,
|
||||
entry_price: float, target_price: float, contract_multiplier: float = 10,
|
||||
margin_rate: float = 0.1) -> Dict:
|
||||
"""生成风险报告"""
|
||||
# 验证交易
|
||||
validation_result = self.validate_trade(account_balance, data, direction, entry_price,
|
||||
target_price, contract_multiplier, margin_rate)
|
||||
|
||||
# 生成风险建议
|
||||
suggestions = []
|
||||
|
||||
if not validation_result['checks']['profit_loss_ratio']['pass']:
|
||||
suggestions.append(f"盈亏比不足,建议调整目标价至{self._calculate_adjusted_target(entry_price, validation_result['stop_loss'], direction):.2f}")
|
||||
|
||||
if not validation_result['checks']['risk_percent']['pass']:
|
||||
suggestions.append(f"风险比例过高,建议减少仓位至{int(validation_result['position_info']['suggested_units'] * 0.8)}手")
|
||||
|
||||
if not validation_result['checks']['leverage']['pass']:
|
||||
suggestions.append("杠杆比例过高,建议降低仓位")
|
||||
|
||||
if not validation_result['checks']['margin_utilization']['pass']:
|
||||
suggestions.append("保证金使用率过高,建议减少仓位")
|
||||
|
||||
# 计算风险回报比
|
||||
risk_return_ratio = self._calculate_risk_return_ratio(validation_result['profit_loss_ratio'],
|
||||
validation_result['position_info']['actual_risk_percent'])
|
||||
|
||||
report = {
|
||||
'account_balance': account_balance,
|
||||
'direction': direction,
|
||||
'entry_price': entry_price,
|
||||
'stop_loss': validation_result['stop_loss'],
|
||||
'target_price': target_price,
|
||||
'profit_loss_ratio': validation_result['profit_loss_ratio'],
|
||||
'position_info': validation_result['position_info'],
|
||||
'risk_metrics': {
|
||||
'risk_return_ratio': risk_return_ratio,
|
||||
'max_drawdown_estimate': self._estimate_max_drawdown(account_balance, validation_result['position_info']),
|
||||
'recovery_factor': self._calculate_recovery_factor(risk_return_ratio)
|
||||
},
|
||||
'suggestions': suggestions,
|
||||
'validation_result': validation_result
|
||||
}
|
||||
|
||||
return report
|
||||
|
||||
def _calculate_adjusted_target(self, entry_price: float, stop_loss: float, direction: str) -> float:
|
||||
"""计算调整后的目标价"""
|
||||
if direction == 'long':
|
||||
loss = entry_price - stop_loss
|
||||
required_profit = loss * self.config.min_profit_loss_ratio
|
||||
return entry_price + required_profit
|
||||
elif direction == 'short':
|
||||
loss = stop_loss - entry_price
|
||||
required_profit = loss * self.config.min_profit_loss_ratio
|
||||
return entry_price - required_profit
|
||||
else:
|
||||
raise ValueError("Direction must be 'long' or 'short'")
|
||||
|
||||
def _calculate_risk_return_ratio(self, pl_ratio: float, risk_percent: float) -> float:
|
||||
"""计算风险回报比"""
|
||||
return pl_ratio * (1 - risk_percent)
|
||||
|
||||
def _estimate_max_drawdown(self, account_balance: float, position_info: Dict) -> float:
|
||||
"""估算最大回撤"""
|
||||
max_loss = position_info['risk_per_unit'] * position_info['suggested_units']
|
||||
return (max_loss / account_balance) * 100
|
||||
|
||||
def _calculate_recovery_factor(self, risk_return_ratio: float) -> float:
|
||||
"""计算恢复因子"""
|
||||
if risk_return_ratio <= 0:
|
||||
return 0
|
||||
return risk_return_ratio * 0.8
|
||||
|
||||
def monitor_position_risk(self, current_price: float, entry_price: float, stop_loss: float,
|
||||
target_price: float, direction: str, units: int, contract_multiplier: float = 10) -> Dict:
|
||||
"""监控持仓风险"""
|
||||
# 计算当前盈亏
|
||||
if direction == 'long':
|
||||
current_profit = (current_price - entry_price) * units * contract_multiplier
|
||||
distance_to_stop = entry_price - current_price
|
||||
distance_to_target = target_price - current_price
|
||||
elif direction == 'short':
|
||||
current_profit = (entry_price - current_price) * units * contract_multiplier
|
||||
distance_to_stop = current_price - entry_price
|
||||
distance_to_target = entry_price - current_price
|
||||
else:
|
||||
raise ValueError("Direction must be 'long' or 'short'")
|
||||
|
||||
# 计算浮盈比例
|
||||
unrealized_pnl_percent = (current_profit / (entry_price * units * contract_multiplier)) * 100
|
||||
|
||||
# 计算止损触发距离
|
||||
stop_percent = (distance_to_stop / entry_price) * 100
|
||||
|
||||
# 计算目标达成距离
|
||||
target_percent = (distance_to_target / entry_price) * 100
|
||||
|
||||
# 风险状态评估
|
||||
risk_status = self._assess_risk_status(current_price, stop_loss, target_price, direction)
|
||||
|
||||
return {
|
||||
'current_price': current_price,
|
||||
'entry_price': entry_price,
|
||||
'stop_loss': stop_loss,
|
||||
'target_price': target_price,
|
||||
'current_profit': current_profit,
|
||||
'unrealized_pnl_percent': unrealized_pnl_percent,
|
||||
'distance_to_stop': distance_to_stop,
|
||||
'distance_to_target': distance_to_target,
|
||||
'stop_percent': stop_percent,
|
||||
'target_percent': target_percent,
|
||||
'risk_status': risk_status
|
||||
}
|
||||
|
||||
def _assess_risk_status(self, current_price: float, stop_loss: float, target_price: float, direction: str) -> str:
|
||||
"""评估风险状态"""
|
||||
if direction == 'long':
|
||||
if current_price <= stop_loss:
|
||||
return 'stop_loss_triggered'
|
||||
elif current_price >= target_price:
|
||||
return 'target_reached'
|
||||
elif current_price > stop_loss * 1.05:
|
||||
return 'low_risk'
|
||||
else:
|
||||
return 'medium_risk'
|
||||
elif direction == 'short':
|
||||
if current_price >= stop_loss:
|
||||
return 'stop_loss_triggered'
|
||||
elif current_price <= target_price:
|
||||
return 'target_reached'
|
||||
elif current_price < stop_loss * 0.95:
|
||||
return 'low_risk'
|
||||
else:
|
||||
return 'medium_risk'
|
||||
else:
|
||||
raise ValueError("Direction must be 'long' or 'short'")
|
||||
@ -1,226 +0,0 @@
|
||||
# 趋势分析模块
|
||||
import pandas as pd
|
||||
from typing import Dict, Tuple, Optional
|
||||
from qihuo_analyzer.utils.technical_analysis import (
|
||||
calculate_adx,
|
||||
calculate_moving_average,
|
||||
calculate_price_quantile,
|
||||
calculate_volume_price_strength
|
||||
)
|
||||
from qihuo_analyzer.core.models import StrategyConfig
|
||||
|
||||
|
||||
class TrendFilter:
|
||||
"""趋势分析过滤器"""
|
||||
|
||||
def __init__(self, config: Optional[StrategyConfig] = None):
|
||||
self.config = config or StrategyConfig()
|
||||
|
||||
def analyze_trend(self, data: pd.DataFrame) -> Dict:
|
||||
"""分析趋势"""
|
||||
result = {}
|
||||
|
||||
# 计算ADX指标
|
||||
adx_data = calculate_adx(data, self.config.adx_period)
|
||||
adx = adx_data['adx'].iloc[-1]
|
||||
plus_di = adx_data['plus_di'].iloc[-1]
|
||||
minus_di = adx_data['minus_di'].iloc[-1]
|
||||
|
||||
# 趋势强度判断
|
||||
trend_strength = self._judge_trend_strength(adx)
|
||||
trend_direction = self._judge_trend_direction(plus_di, minus_di)
|
||||
|
||||
# 计算移动平均线
|
||||
ma_data = calculate_moving_average(data, [self.config.short_ma, self.config.long_ma])
|
||||
short_ma = ma_data[f'ma{self.config.short_ma}'].iloc[-1]
|
||||
long_ma = ma_data[f'ma{self.config.long_ma}'].iloc[-1]
|
||||
|
||||
# 双均线排列判断
|
||||
ma_relationship = self._judge_ma_relationship(short_ma, long_ma)
|
||||
|
||||
# 多周期共振分析
|
||||
multi_period_analysis = self._analyze_multi_period(data)
|
||||
|
||||
# 综合趋势判断
|
||||
overall_trend = self._judge_overall_trend(trend_strength, trend_direction, ma_relationship)
|
||||
|
||||
result.update({
|
||||
'adx': adx,
|
||||
'plus_di': plus_di,
|
||||
'minus_di': minus_di,
|
||||
'trend_strength': trend_strength,
|
||||
'trend_direction': trend_direction,
|
||||
'short_ma': short_ma,
|
||||
'long_ma': long_ma,
|
||||
'ma_relationship': ma_relationship,
|
||||
'multi_period_analysis': multi_period_analysis,
|
||||
'overall_trend': overall_trend
|
||||
})
|
||||
|
||||
return result
|
||||
|
||||
def _judge_trend_strength(self, adx: float) -> str:
|
||||
"""判断趋势强度"""
|
||||
if adx > 40:
|
||||
return 'strong'
|
||||
elif adx >= 25:
|
||||
return 'medium'
|
||||
elif adx >= 20:
|
||||
return 'weak'
|
||||
else:
|
||||
return 'none'
|
||||
|
||||
def _judge_trend_direction(self, plus_di: float, minus_di: float) -> str:
|
||||
"""判断趋势方向"""
|
||||
if plus_di > minus_di:
|
||||
return 'up'
|
||||
elif plus_di < minus_di:
|
||||
return 'down'
|
||||
else:
|
||||
return 'neutral'
|
||||
|
||||
def _judge_ma_relationship(self, short_ma: float, long_ma: float) -> str:
|
||||
"""判断均线关系"""
|
||||
if short_ma > long_ma:
|
||||
return 'bullish'
|
||||
elif short_ma < long_ma:
|
||||
return 'bearish'
|
||||
else:
|
||||
return 'neutral'
|
||||
|
||||
def _analyze_multi_period(self, data: pd.DataFrame) -> Dict:
|
||||
"""多周期共振分析"""
|
||||
periods = [15, 60, 240] # 15分钟、1小时、4小时
|
||||
analysis = {}
|
||||
|
||||
for period in periods:
|
||||
# 简化处理,使用不同周期的收盘价
|
||||
if len(data) >= period:
|
||||
period_data = data.tail(period)
|
||||
ma_short = period_data['close'].rolling(window=5).mean().iloc[-1]
|
||||
ma_long = period_data['close'].rolling(window=20).mean().iloc[-1]
|
||||
|
||||
if ma_short > ma_long:
|
||||
analysis[f'{period}min'] = 'bullish'
|
||||
elif ma_short < ma_long:
|
||||
analysis[f'{period}min'] = 'bearish'
|
||||
else:
|
||||
analysis[f'{period}min'] = 'neutral'
|
||||
else:
|
||||
analysis[f'{period}min'] = 'insufficient_data'
|
||||
|
||||
# 计算共振程度
|
||||
bullish_count = sum(1 for v in analysis.values() if v == 'bullish')
|
||||
bearish_count = sum(1 for v in analysis.values() if v == 'bearish')
|
||||
|
||||
resonance = 'none'
|
||||
if bullish_count >= 2:
|
||||
resonance = 'bullish_resonance'
|
||||
elif bearish_count >= 2:
|
||||
resonance = 'bearish_resonance'
|
||||
|
||||
analysis['resonance'] = resonance
|
||||
|
||||
return analysis
|
||||
|
||||
def _judge_overall_trend(self, trend_strength: str, trend_direction: str, ma_relationship: str) -> str:
|
||||
"""综合判断趋势"""
|
||||
if trend_strength == 'none':
|
||||
return 'neutral'
|
||||
|
||||
if trend_direction == 'up' and ma_relationship == 'bullish':
|
||||
return 'strong_bullish'
|
||||
elif trend_direction == 'down' and ma_relationship == 'bearish':
|
||||
return 'strong_bearish'
|
||||
elif trend_direction == 'up' and ma_relationship == 'bearish':
|
||||
return 'weak_bullish'
|
||||
elif trend_direction == 'down' and ma_relationship == 'bullish':
|
||||
return 'weak_bearish'
|
||||
else:
|
||||
return 'neutral'
|
||||
|
||||
def calculate_win_rate(self, data: pd.DataFrame) -> float:
|
||||
"""计算胜率"""
|
||||
# 获取ADX值
|
||||
adx_data = calculate_adx(data, self.config.adx_period)
|
||||
adx = adx_data['adx'].iloc[-1]
|
||||
|
||||
# 计算价格分位
|
||||
price_quantile = calculate_price_quantile(data)
|
||||
price_score = self._calculate_price_score(price_quantile)
|
||||
|
||||
# 计算量价强度
|
||||
volume_price_strength = calculate_volume_price_strength(data)
|
||||
|
||||
# 计算趋势强度评分
|
||||
trend_strength_score = self._calculate_trend_strength_score(adx)
|
||||
|
||||
# 根据市场状态计算加权胜率
|
||||
if adx < 20: # 震荡市
|
||||
win_rate = (
|
||||
price_score * 0.25 +
|
||||
volume_price_strength * 0.6 +
|
||||
trend_strength_score * 0.15
|
||||
)
|
||||
else: # 趋势市
|
||||
# 价格分位权重随ADX递减
|
||||
price_weight = max(0.3, 0.6 - (adx - 20) * 0.0075)
|
||||
trend_adjustment = ((adx - 20) * 0.5) / 100 if adx_data['plus_di'].iloc[-1] > adx_data['minus_di'].iloc[-1] else -((adx - 20) * 0.5) / 100
|
||||
|
||||
win_rate = (
|
||||
price_score * price_weight +
|
||||
volume_price_strength * 0.4 +
|
||||
trend_strength_score * (0.6 - price_weight)
|
||||
) + trend_adjustment
|
||||
|
||||
# 确保胜率在合理范围内
|
||||
win_rate = max(0, min(100, win_rate))
|
||||
|
||||
return win_rate
|
||||
|
||||
def _calculate_price_score(self, quantile: float) -> float:
|
||||
"""计算价格分位评分"""
|
||||
if quantile < 0.2:
|
||||
return 90
|
||||
elif quantile < 0.4:
|
||||
return 75
|
||||
elif quantile < 0.6:
|
||||
return 55
|
||||
elif quantile < 0.8:
|
||||
return 40
|
||||
else:
|
||||
return 25
|
||||
|
||||
def _calculate_trend_strength_score(self, adx: float) -> float:
|
||||
"""计算趋势强度评分"""
|
||||
if adx > 40:
|
||||
return 85
|
||||
elif adx >= 25:
|
||||
return 70
|
||||
elif adx >= 20:
|
||||
return 50
|
||||
else:
|
||||
return 30
|
||||
|
||||
def judge_cycle(self, data: pd.DataFrame) -> str:
|
||||
"""判断周期"""
|
||||
multi_period_analysis = self._analyze_multi_period(data)
|
||||
adx_data = calculate_adx(data, self.config.adx_period)
|
||||
adx = adx_data['adx'].iloc[-1]
|
||||
|
||||
# 检查各周期方向一致性
|
||||
directions = [v for k, v in multi_period_analysis.items() if k.endswith('min')]
|
||||
valid_directions = [d for d in directions if d != 'insufficient_data']
|
||||
|
||||
if not valid_directions:
|
||||
return 'medium'
|
||||
|
||||
# 检查是否所有周期方向一致且不为中性
|
||||
if len(set(valid_directions)) == 1 and valid_directions[0] != 'neutral':
|
||||
# 检查是否为极强趋势
|
||||
if adx > 40:
|
||||
return 'long'
|
||||
else:
|
||||
return 'short'
|
||||
else:
|
||||
return 'medium'
|
||||
Binary file not shown.
Binary file not shown.
@ -1,70 +0,0 @@
|
||||
# 配置管理工具
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
class ConfigManager:
|
||||
"""配置管理类"""
|
||||
|
||||
_instance = None
|
||||
|
||||
def __new__(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(ConfigManager, cls).__new__(cls)
|
||||
cls._instance._load_config()
|
||||
return cls._instance
|
||||
|
||||
def _load_config(self):
|
||||
"""加载配置"""
|
||||
# 加载.env文件
|
||||
load_dotenv()
|
||||
|
||||
# API配置
|
||||
self.openai_api_key = os.getenv('OPENAI_API_KEY', '')
|
||||
self.deepseek_api_key = os.getenv('DEEPSEEK_API_KEY', '')
|
||||
self.deepseek_api_url = os.getenv('DEEPSEEK_API_URL', 'https://api.deepseek.com/v1/chat/completions')
|
||||
|
||||
# 数据库配置
|
||||
self.db_path = os.getenv('DB_PATH', './data/futures_analysis.db')
|
||||
|
||||
# 天勤TQSDK配置
|
||||
self.tqserver_host = os.getenv('TQSERVER_HOST', 'api.shinnytech.com')
|
||||
self.tqserver_port = int(os.getenv('TQSERVER_PORT', '7777'))
|
||||
|
||||
# 风险配置
|
||||
self.max_risk_percent = float(os.getenv('MAX_RISK_PERCENT', '0.02'))
|
||||
self.min_profit_loss_ratio = float(os.getenv('MIN_PROFIT_LOSS_RATIO', '1.5'))
|
||||
|
||||
# 策略配置
|
||||
self.default_atr_multiplier = float(os.getenv('DEFAULT_ATR_MULTIPLIER', '2.0'))
|
||||
self.default_adx_threshold = float(os.getenv('DEFAULT_ADX_THRESHOLD', '20'))
|
||||
|
||||
# 定时任务配置
|
||||
self.review_times = os.getenv('REVIEW_TIMES', '09:00,12:30,15:30').split(',')
|
||||
|
||||
def get_config(self) -> Dict:
|
||||
"""获取所有配置"""
|
||||
return {
|
||||
'openai_api_key': self.openai_api_key,
|
||||
'deepseek_api_key': self.deepseek_api_key,
|
||||
'deepseek_api_url': self.deepseek_api_url,
|
||||
'db_path': self.db_path,
|
||||
'tqserver_host': self.tqserver_host,
|
||||
'tqserver_port': self.tqserver_port,
|
||||
'max_risk_percent': self.max_risk_percent,
|
||||
'min_profit_loss_ratio': self.min_profit_loss_ratio,
|
||||
'default_atr_multiplier': self.default_atr_multiplier,
|
||||
'default_adx_threshold': self.default_adx_threshold,
|
||||
'review_times': self.review_times
|
||||
}
|
||||
|
||||
def update_config(self, config: Dict):
|
||||
"""更新配置"""
|
||||
for key, value in config.items():
|
||||
if hasattr(self, key):
|
||||
setattr(self, key, value)
|
||||
|
||||
|
||||
# 全局配置实例
|
||||
config_manager = ConfigManager()
|
||||
@ -1,153 +0,0 @@
|
||||
# 技术分析工具
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
def calculate_macd(data: pd.DataFrame, fast_period: int = 12, slow_period: int = 26, signal_period: int = 9) -> Dict[str, pd.Series]:
|
||||
"""计算MACD指标"""
|
||||
exp1 = data['close'].ewm(span=fast_period, adjust=False).mean()
|
||||
exp2 = data['close'].ewm(span=slow_period, adjust=False).mean()
|
||||
macd = exp1 - exp2
|
||||
signal = macd.ewm(span=signal_period, adjust=False).mean()
|
||||
histogram = macd - signal
|
||||
|
||||
return {
|
||||
'macd': macd,
|
||||
'signal': signal,
|
||||
'histogram': histogram
|
||||
}
|
||||
|
||||
|
||||
def calculate_rsi(data: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||
"""计算RSI指标"""
|
||||
delta = data['close'].diff()
|
||||
gain = (delta.where(delta > 0, 0)).rolling(window=period).mean()
|
||||
loss = (-delta.where(delta < 0, 0)).rolling(window=period).mean()
|
||||
|
||||
rs = gain / loss
|
||||
rsi = 100 - (100 / (1 + rs))
|
||||
|
||||
return rsi
|
||||
|
||||
|
||||
def calculate_bollinger_bands(data: pd.DataFrame, period: int = 20, std_dev: float = 2.0) -> Dict[str, pd.Series]:
|
||||
"""计算布林带"""
|
||||
sma = data['close'].rolling(window=period).mean()
|
||||
std = data['close'].rolling(window=period).std()
|
||||
upper_band = sma + (std * std_dev)
|
||||
lower_band = sma - (std * std_dev)
|
||||
|
||||
return {
|
||||
'sma': sma,
|
||||
'upper_band': upper_band,
|
||||
'lower_band': lower_band
|
||||
}
|
||||
|
||||
|
||||
def calculate_kdj(data: pd.DataFrame, period: int = 9, signal_period: int = 3) -> Dict[str, pd.Series]:
|
||||
"""计算KDJ指标"""
|
||||
low_min = data['low'].rolling(window=period).min()
|
||||
high_max = data['high'].rolling(window=period).max()
|
||||
|
||||
rsv = (data['close'] - low_min) / (high_max - low_min) * 100
|
||||
k = rsv.ewm(alpha=1/signal_period, adjust=False).mean()
|
||||
d = k.ewm(alpha=1/signal_period, adjust=False).mean()
|
||||
j = 3 * k - 2 * d
|
||||
|
||||
return {
|
||||
'k': k,
|
||||
'd': d,
|
||||
'j': j
|
||||
}
|
||||
|
||||
|
||||
def calculate_adx(data: pd.DataFrame, period: int = 14) -> Dict[str, pd.Series]:
|
||||
"""计算ADX指标"""
|
||||
high = data['high']
|
||||
low = data['low']
|
||||
close = data['close']
|
||||
|
||||
tr1 = high - low
|
||||
tr2 = abs(high - close.shift())
|
||||
tr3 = abs(low - close.shift())
|
||||
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||
|
||||
plus_dm = high.diff()
|
||||
minus_dm = low.diff()
|
||||
|
||||
plus_dm[plus_dm < 0] = 0
|
||||
minus_dm[minus_dm > 0] = 0
|
||||
minus_dm = abs(minus_dm)
|
||||
|
||||
atr = tr.rolling(window=period).mean()
|
||||
plus_di = (plus_dm.rolling(window=period).mean() / atr) * 100
|
||||
minus_di = (minus_dm.rolling(window=period).mean() / atr) * 100
|
||||
|
||||
dx = (abs(plus_di - minus_di) / (plus_di + minus_di)) * 100
|
||||
adx = dx.rolling(window=period).mean()
|
||||
|
||||
return {
|
||||
'adx': adx,
|
||||
'plus_di': plus_di,
|
||||
'minus_di': minus_di
|
||||
}
|
||||
|
||||
|
||||
def calculate_atr(data: pd.DataFrame, period: int = 14) -> pd.Series:
|
||||
"""计算ATR指标"""
|
||||
high = data['high']
|
||||
low = data['low']
|
||||
close = data['close']
|
||||
|
||||
tr1 = high - low
|
||||
tr2 = abs(high - close.shift())
|
||||
tr3 = abs(low - close.shift())
|
||||
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
|
||||
|
||||
atr = tr.rolling(window=period).mean()
|
||||
|
||||
return atr
|
||||
|
||||
|
||||
def calculate_moving_average(data: pd.DataFrame, periods: List[int]) -> Dict[str, pd.Series]:
|
||||
"""计算移动平均线"""
|
||||
mas = {}
|
||||
for period in periods:
|
||||
mas[f'ma{period}'] = data['close'].rolling(window=period).mean()
|
||||
|
||||
return mas
|
||||
|
||||
|
||||
def calculate_price_quantile(data: pd.DataFrame, period: int = 100) -> float:
|
||||
"""计算价格分位"""
|
||||
prices = data['close'].tail(period)
|
||||
current_price = prices.iloc[-1]
|
||||
quantile = (prices <= current_price).sum() / len(prices)
|
||||
|
||||
return quantile
|
||||
|
||||
|
||||
def calculate_volume_price_strength(data: pd.DataFrame, period: int = 20) -> float:
|
||||
"""计算量价强度"""
|
||||
df = data.tail(period).copy()
|
||||
df['price_change'] = df['close'].pct_change()
|
||||
df['volume_change'] = df['volume'].pct_change()
|
||||
|
||||
# 量价配合度
|
||||
strength = 0
|
||||
for i in range(1, len(df)):
|
||||
if (df['price_change'].iloc[i] > 0 and df['volume_change'].iloc[i] > 0) or \
|
||||
(df['price_change'].iloc[i] < 0 and df['volume_change'].iloc[i] < 0):
|
||||
strength += abs(df['price_change'].iloc[i]) * (1 + abs(df['volume_change'].iloc[i]))
|
||||
else:
|
||||
strength -= abs(df['price_change'].iloc[i]) * (1 + abs(df['volume_change'].iloc[i]))
|
||||
|
||||
# 归一化到0-100
|
||||
max_strength = abs(strength)
|
||||
if max_strength == 0:
|
||||
return 50
|
||||
|
||||
normalized_strength = (strength / max_strength + 1) / 2 * 100
|
||||
|
||||
return normalized_strength
|
||||
@ -1,4 +0,0 @@
|
||||
# Service dependencies
|
||||
Flask==2.0.1
|
||||
pandas==1.3.3
|
||||
python-dotenv==0.19.0
|
||||
@ -1 +0,0 @@
|
||||
# Service module initialization
|
||||
Binary file not shown.
Binary file not shown.
@ -1,226 +0,0 @@
|
||||
# Service main application
|
||||
from flask import Flask, request, jsonify
|
||||
import sys
|
||||
import os
|
||||
import pandas as pd
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from qihuo_analyzer.data.data_fetcher import DataFetcher
|
||||
from qihuo_analyzer.data.data_storage import DataStorage
|
||||
from qihuo_analyzer.modules.deepseek_agent import DeepseekAgent
|
||||
from qihuo_analyzer.utils.config_manager import config_manager
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
# 初始化组件
|
||||
data_fetcher = DataFetcher()
|
||||
data_storage = DataStorage()
|
||||
deepseek_agent = DeepseekAgent()
|
||||
|
||||
# 连接 API
|
||||
print("正在连接 API...")
|
||||
connect_success = data_fetcher.connect()
|
||||
if connect_success:
|
||||
print("API 连接成功,可以获取真实数据")
|
||||
else:
|
||||
print("API 连接失败,将使用模拟数据")
|
||||
|
||||
# 健康检查接口
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health_check():
|
||||
return jsonify({'status': 'ok', 'message': 'Service is running'})
|
||||
|
||||
# 合约数据获取接口
|
||||
@app.route('/api/contracts', methods=['GET'])
|
||||
def get_contracts():
|
||||
try:
|
||||
exchange = request.args.get('exchange', '')
|
||||
symbol = request.args.get('symbol', '')
|
||||
|
||||
contracts = data_fetcher.get_contracts(exchange=exchange, symbol=symbol)
|
||||
return jsonify({'status': 'success', 'data': contracts})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# K线数据获取接口
|
||||
@app.route('/api/kline', methods=['GET'])
|
||||
def get_kline():
|
||||
try:
|
||||
symbol = request.args.get('symbol', '')
|
||||
duration = request.args.get('duration', '1m')
|
||||
limit = int(request.args.get('limit', 100))
|
||||
|
||||
if not symbol:
|
||||
return jsonify({'status': 'error', 'message': 'Symbol is required'}), 400
|
||||
|
||||
# 尝试从数据库获取,如果没有则从数据源获取
|
||||
df = data_storage.get_kline_data(symbol, duration, limit)
|
||||
|
||||
if df.empty:
|
||||
# 从数据源获取
|
||||
df = data_fetcher.get_kline_data(symbol, duration, limit)
|
||||
# 保存到数据库
|
||||
data_storage.save_kline_data(symbol, duration, df)
|
||||
|
||||
# 转换为字典格式
|
||||
kline_data = []
|
||||
for idx, row in df.iterrows():
|
||||
kline_data.append({
|
||||
'datetime': idx.isoformat(),
|
||||
'open': float(row['open']),
|
||||
'high': float(row['high']),
|
||||
'low': float(row['low']),
|
||||
'close': float(row['close']),
|
||||
'volume': int(row['volume']),
|
||||
'open_interest': int(row['open_interest'])
|
||||
})
|
||||
|
||||
return jsonify({'status': 'success', 'data': kline_data})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# DeepSeek 分析接口
|
||||
@app.route('/api/analyze', methods=['POST'])
|
||||
def analyze():
|
||||
try:
|
||||
data = request.get_json()
|
||||
symbol = data.get('symbol', '')
|
||||
duration = data.get('duration', '1m')
|
||||
analysis_type = data.get('analysis_type', 'technical')
|
||||
|
||||
if not symbol:
|
||||
return jsonify({'status': 'error', 'message': 'Symbol is required'}), 400
|
||||
|
||||
# 获取K线数据
|
||||
df = data_fetcher.get_kline_data(symbol, duration, 1000)
|
||||
|
||||
# 保存到数据库
|
||||
data_storage.save_kline_data(symbol, duration, df)
|
||||
|
||||
# 执行分析
|
||||
analysis_result = deepseek_agent.analyze_market(symbol, df)
|
||||
|
||||
# 保存分析结果
|
||||
data_storage.save_analysis_result(analysis_result)
|
||||
|
||||
return jsonify({'status': 'success', 'data': analysis_result})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# 交易建议接口
|
||||
@app.route('/api/recommendations', methods=['GET'])
|
||||
def get_recommendations():
|
||||
try:
|
||||
symbol = request.args.get('symbol', '')
|
||||
status = request.args.get('status', '')
|
||||
|
||||
if not symbol:
|
||||
return jsonify({'status': 'error', 'message': 'Symbol is required'}), 400
|
||||
|
||||
df = data_storage.get_trade_recommendations(symbol, status)
|
||||
|
||||
# 转换为字典格式
|
||||
recommendations = []
|
||||
for _, row in df.iterrows():
|
||||
recommendations.append({
|
||||
'id': int(row['id']),
|
||||
'symbol': row['symbol'],
|
||||
'timestamp': row['timestamp'],
|
||||
'direction': row['direction'],
|
||||
'entry_price': float(row['entry_price']) if not pd.isna(row['entry_price']) else None,
|
||||
'stop_loss': float(row['stop_loss']) if not pd.isna(row['stop_loss']) else None,
|
||||
'target_price': float(row['target_price']) if not pd.isna(row['target_price']) else None,
|
||||
'position_size': float(row['position_size']) if not pd.isna(row['position_size']) else None,
|
||||
'execution_plan': row['execution_plan'],
|
||||
'risk_tips': row['risk_tips'],
|
||||
'status': row['status'],
|
||||
'created_at': row['created_at']
|
||||
})
|
||||
|
||||
return jsonify({'status': 'success', 'data': recommendations})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# 风险监控接口
|
||||
@app.route('/api/risk', methods=['POST'])
|
||||
def monitor_risk():
|
||||
try:
|
||||
data = request.get_json()
|
||||
symbol = data.get('symbol', '')
|
||||
current_price = data.get('current_price', 0)
|
||||
entry_price = data.get('entry_price', 0)
|
||||
stop_loss = data.get('stop_loss', 0)
|
||||
target_price = data.get('target_price', 0)
|
||||
|
||||
if not symbol:
|
||||
return jsonify({'status': 'error', 'message': 'Symbol is required'}), 400
|
||||
|
||||
# 计算当前利润
|
||||
current_profit = current_price - entry_price
|
||||
|
||||
# 评估风险状态
|
||||
risk_status = 'normal'
|
||||
if abs(current_profit) > (entry_price * 0.05):
|
||||
risk_status = 'high'
|
||||
|
||||
# 保存风险监控数据
|
||||
risk_data = {
|
||||
'symbol': symbol,
|
||||
'current_price': current_price,
|
||||
'entry_price': entry_price,
|
||||
'stop_loss': stop_loss,
|
||||
'target_price': target_price,
|
||||
'current_profit': current_profit,
|
||||
'risk_status': risk_status
|
||||
}
|
||||
|
||||
data_storage.save_risk_monitoring(risk_data)
|
||||
|
||||
return jsonify({'status': 'success', 'data': risk_data})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
# 分析历史接口
|
||||
@app.route('/api/analysis/history', methods=['GET'])
|
||||
def get_analysis_history():
|
||||
try:
|
||||
symbol = request.args.get('symbol', '')
|
||||
limit = int(request.args.get('limit', 100))
|
||||
|
||||
if not symbol:
|
||||
return jsonify({'status': 'error', 'message': 'Symbol is required'}), 400
|
||||
|
||||
df = data_storage.get_analysis_results(symbol, limit)
|
||||
|
||||
# 转换为字典格式
|
||||
history = []
|
||||
for _, row in df.iterrows():
|
||||
history.append({
|
||||
'id': int(row['id']),
|
||||
'symbol': row['symbol'],
|
||||
'timestamp': row['timestamp'],
|
||||
'trend': row['trend'],
|
||||
'probability': float(row['probability']) if not pd.isna(row['probability']) else None,
|
||||
'direction': row['direction'],
|
||||
'cycle': row['cycle'],
|
||||
'atr': float(row['atr']) if not pd.isna(row['atr']) else None,
|
||||
'adx': float(row['adx']) if not pd.isna(row['adx']) else None,
|
||||
'support': float(row['support']) if not pd.isna(row['support']) else None,
|
||||
'resistance': float(row['resistance']) if not pd.isna(row['resistance']) else None,
|
||||
'stop_loss': float(row['stop_loss']) if not pd.isna(row['stop_loss']) else None,
|
||||
'target_price': float(row['target_price']) if not pd.isna(row['target_price']) else None,
|
||||
'position_size': float(row['position_size']) if not pd.isna(row['position_size']) else None,
|
||||
'risk_ratio': float(row['risk_ratio']) if not pd.isna(row['risk_ratio']) else None,
|
||||
'fund_flow': row['fund_flow'],
|
||||
'signals': row['signals'],
|
||||
'created_at': row['created_at']
|
||||
})
|
||||
|
||||
return jsonify({'status': 'success', 'data': history})
|
||||
except Exception as e:
|
||||
return jsonify({'status': 'error', 'message': str(e)}), 500
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=True)
|
||||
Binary file not shown.
@ -1,183 +0,0 @@
|
||||
# Service API tests
|
||||
import unittest
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
# 添加项目根目录到 Python 路径
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# 直接导入 app 模块
|
||||
from service.app import app
|
||||
|
||||
class ServiceAPITest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
# 创建测试客户端
|
||||
self.client = app.test_client()
|
||||
self.client.testing = True
|
||||
|
||||
@patch('service.app.DataFetcher')
|
||||
def test_health_check(self, mock_data_fetcher):
|
||||
"""测试健康检查接口"""
|
||||
response = self.client.get('/health')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'ok')
|
||||
self.assertEqual(data['message'], 'Service is running')
|
||||
|
||||
@patch('service.app.DataFetcher')
|
||||
def test_get_contracts(self, mock_data_fetcher):
|
||||
"""测试合约数据获取接口"""
|
||||
# 配置 mock
|
||||
mock_fetcher_instance = MagicMock()
|
||||
mock_data_fetcher.return_value = mock_fetcher_instance
|
||||
|
||||
# 模拟 get_contracts 方法
|
||||
mock_fetcher_instance.get_contracts.return_value = [
|
||||
{'symbol': 'CU2603', 'product': 'CU', 'product_name': '铜', 'exchange': 'SHFE', 'month': '2603'},
|
||||
{'symbol': 'AL2603', 'product': 'AL', 'product_name': '铝', 'exchange': 'SHFE', 'month': '2603'}
|
||||
]
|
||||
|
||||
# 测试获取所有合约
|
||||
response = self.client.get('/api/contracts')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'success')
|
||||
self.assertIsInstance(data['data'], list)
|
||||
self.assertGreater(len(data['data']), 0)
|
||||
|
||||
@patch('service.app.DataStorage')
|
||||
@patch('service.app.DataFetcher')
|
||||
def test_get_kline(self, mock_data_fetcher, mock_data_storage):
|
||||
"""测试K线数据获取接口"""
|
||||
# 配置 mock
|
||||
mock_fetcher_instance = MagicMock()
|
||||
mock_data_fetcher.return_value = mock_fetcher_instance
|
||||
|
||||
mock_storage_instance = MagicMock()
|
||||
mock_data_storage.return_value = mock_storage_instance
|
||||
|
||||
# 模拟数据
|
||||
mock_df = MagicMock()
|
||||
mock_df.empty = False
|
||||
mock_df.iterrows.return_value = [(MagicMock(isoformat=lambda: '2026-02-22T00:00:00'), \
|
||||
{'open': 35000, 'high': 35100, 'low': 34900, 'close': 35050, 'volume': 1000, 'open_interest': 10000})]
|
||||
|
||||
mock_storage_instance.get_kline_data.return_value = mock_df
|
||||
mock_fetcher_instance.get_kline_data.return_value = mock_df
|
||||
mock_storage_instance.save_kline_data.return_value = True
|
||||
|
||||
# 测试获取K线数据
|
||||
response = self.client.get('/api/kline?symbol=CU2603&duration=1m&limit=10')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'success')
|
||||
self.assertIsInstance(data['data'], list)
|
||||
self.assertGreater(len(data['data']), 0)
|
||||
|
||||
@patch('service.app.DataStorage')
|
||||
@patch('service.app.DeepseekAgent')
|
||||
@patch('service.app.DataFetcher')
|
||||
def test_analyze(self, mock_data_fetcher, mock_deepseek_agent, mock_data_storage):
|
||||
"""测试DeepSeek分析接口"""
|
||||
# 配置 mock
|
||||
mock_fetcher_instance = MagicMock()
|
||||
mock_data_fetcher.return_value = mock_fetcher_instance
|
||||
|
||||
mock_agent_instance = MagicMock()
|
||||
mock_deepseek_agent.return_value = mock_agent_instance
|
||||
|
||||
mock_storage_instance = MagicMock()
|
||||
mock_data_storage.return_value = mock_storage_instance
|
||||
|
||||
# 模拟数据
|
||||
mock_df = MagicMock()
|
||||
mock_df.empty = False
|
||||
mock_fetcher_instance.get_kline_data.return_value = mock_df
|
||||
|
||||
# 模拟分析结果
|
||||
mock_agent_instance.analyze_market.return_value = {
|
||||
'symbol': 'CU2603',
|
||||
'timestamp': '2026-02-22T00:00:00',
|
||||
'trend': 'up',
|
||||
'probability': 0.8,
|
||||
'direction': 'buy'
|
||||
}
|
||||
|
||||
mock_storage_instance.save_kline_data.return_value = True
|
||||
mock_storage_instance.save_analysis_result.return_value = True
|
||||
|
||||
# 测试分析接口
|
||||
test_data = {
|
||||
'symbol': 'CU2603',
|
||||
'duration': '1m',
|
||||
'analysis_type': 'technical'
|
||||
}
|
||||
response = self.client.post('/api/analyze', json=test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'success')
|
||||
self.assertIn('data', data)
|
||||
|
||||
@patch('service.app.DataStorage')
|
||||
def test_get_recommendations(self, mock_data_storage):
|
||||
"""测试交易建议接口"""
|
||||
# 配置 mock
|
||||
mock_storage_instance = MagicMock()
|
||||
mock_data_storage.return_value = mock_storage_instance
|
||||
|
||||
# 模拟数据
|
||||
mock_df = MagicMock()
|
||||
mock_storage_instance.get_trade_recommendations.return_value = mock_df
|
||||
|
||||
# 测试获取交易建议
|
||||
response = self.client.get('/api/recommendations?symbol=CU2603')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'success')
|
||||
self.assertIsInstance(data['data'], list)
|
||||
|
||||
@patch('service.app.DataStorage')
|
||||
def test_monitor_risk(self, mock_data_storage):
|
||||
"""测试风险监控接口"""
|
||||
# 配置 mock
|
||||
mock_storage_instance = MagicMock()
|
||||
mock_data_storage.return_value = mock_storage_instance
|
||||
mock_storage_instance.save_risk_monitoring.return_value = True
|
||||
|
||||
# 测试风险监控
|
||||
test_data = {
|
||||
'symbol': 'CU2603',
|
||||
'current_price': 36000,
|
||||
'entry_price': 35000,
|
||||
'stop_loss': 34500,
|
||||
'target_price': 37000
|
||||
}
|
||||
response = self.client.post('/api/risk', json=test_data)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'success')
|
||||
self.assertIn('data', data)
|
||||
self.assertEqual(data['data']['symbol'], 'CU2603')
|
||||
|
||||
@patch('service.app.DataStorage')
|
||||
def test_get_analysis_history(self, mock_data_storage):
|
||||
"""测试分析历史接口"""
|
||||
# 配置 mock
|
||||
mock_storage_instance = MagicMock()
|
||||
mock_data_storage.return_value = mock_storage_instance
|
||||
|
||||
# 模拟数据
|
||||
mock_df = MagicMock()
|
||||
mock_storage_instance.get_analysis_results.return_value = mock_df
|
||||
|
||||
# 测试获取分析历史
|
||||
response = self.client.get('/api/analysis/history?symbol=CU2603&limit=10')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.data)
|
||||
self.assertEqual(data['status'], 'success')
|
||||
self.assertIsInstance(data['data'], list)
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@ -1,122 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# 测试通过配置文件切换数据源
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 确保能导入项目模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
# 从配置文件加载配置
|
||||
from config import (
|
||||
DATA_ADAPTER_TYPE,
|
||||
TQSDK_USERNAME,
|
||||
TQSDK_PASSWORD,
|
||||
RQDATA_USERNAME,
|
||||
RQDATA_PASSWORD,
|
||||
DEBUG
|
||||
)
|
||||
|
||||
# 设置环境变量
|
||||
def setup_environment():
|
||||
"""根据配置文件设置环境变量"""
|
||||
print("=== 设置环境变量 ===")
|
||||
|
||||
# 设置数据源类型
|
||||
os.environ["DATA_ADAPTER_TYPE"] = DATA_ADAPTER_TYPE
|
||||
print(f"数据源类型: {DATA_ADAPTER_TYPE}")
|
||||
|
||||
# 设置TQSDK账号
|
||||
if TQSDK_USERNAME:
|
||||
os.environ["TQSDK_USERNAME"] = TQSDK_USERNAME
|
||||
os.environ["TQSDK_PASSWORD"] = TQSDK_PASSWORD
|
||||
print("TQSDK账号: 已配置")
|
||||
else:
|
||||
print("TQSDK账号: 未配置(将使用模拟数据)")
|
||||
|
||||
# 设置RQData账号
|
||||
if RQDATA_USERNAME:
|
||||
os.environ["RQDATA_USERNAME"] = RQDATA_USERNAME
|
||||
os.environ["RQDATA_PASSWORD"] = RQDATA_PASSWORD
|
||||
print("RQData账号: 已配置")
|
||||
else:
|
||||
print("RQData账号: 未配置(将使用模拟数据)")
|
||||
|
||||
# 设置调试模式
|
||||
if DEBUG:
|
||||
os.environ["DEBUG"] = "True"
|
||||
print("调试模式: 开启")
|
||||
else:
|
||||
print("调试模式: 关闭")
|
||||
|
||||
print("环境变量设置完成!\n")
|
||||
|
||||
# 测试数据源连接
|
||||
def test_data_source():
|
||||
"""测试数据源连接和基本功能"""
|
||||
print("=== 测试数据源功能 ===")
|
||||
|
||||
try:
|
||||
from qihuo_analyzer.data.data_fetcher import DataFetcher
|
||||
|
||||
# 创建数据获取器实例
|
||||
print("创建DataFetcher实例...")
|
||||
fetcher = DataFetcher()
|
||||
|
||||
# 查看使用的适配器
|
||||
adapter_name = fetcher.adapter.__class__.__name__
|
||||
print(f"当前使用的适配器: {adapter_name}")
|
||||
|
||||
# 测试获取品种列表
|
||||
print("\n测试获取品种列表...")
|
||||
symbols = fetcher.get_all_symbols()
|
||||
if symbols:
|
||||
print(f"成功获取 {len(symbols)} 个品种")
|
||||
print("前10个品种:", symbols[:10])
|
||||
else:
|
||||
print("获取品种列表失败")
|
||||
|
||||
# 测试获取K线数据
|
||||
print("\n测试获取K线数据...")
|
||||
test_symbol = symbols[0] if symbols else "CU2603"
|
||||
kline_data = fetcher.get_kline_data(
|
||||
symbol=test_symbol,
|
||||
duration="1h", # 1小时
|
||||
count=50
|
||||
)
|
||||
|
||||
if kline_data is not None:
|
||||
print(f"成功获取 {len(kline_data)} 条K线数据")
|
||||
print("数据示例:")
|
||||
print(kline_data.head())
|
||||
else:
|
||||
print("获取K线数据失败")
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"测试失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
# 主函数
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("开始测试通过配置文件切换数据源...\n")
|
||||
|
||||
# 1. 设置环境变量
|
||||
setup_environment()
|
||||
|
||||
# 2. 测试数据源
|
||||
success = test_data_source()
|
||||
|
||||
if success:
|
||||
print("\n✓ 数据源切换测试成功!")
|
||||
print(f"✓ 当前使用的数据源类型: {DATA_ADAPTER_TYPE}")
|
||||
else:
|
||||
print("\n✗ 数据源切换测试失败!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@ -1,123 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# 测试枚举合约数据
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 确保能导入项目模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from qihuo_analyzer.data.data_fetcher import DataFetcher
|
||||
|
||||
def test_enum_symbols():
|
||||
"""测试枚举合约数据"""
|
||||
print("=== 测试枚举合约数据 ===")
|
||||
|
||||
# 创建DataFetcher实例
|
||||
fetcher = DataFetcher()
|
||||
|
||||
# 测试获取所有品种列表
|
||||
print("\n1. 测试获取所有品种列表:")
|
||||
symbols = fetcher.get_all_symbols()
|
||||
print(f"成功获取 {len(symbols)} 个品种")
|
||||
print("所有品种:", symbols)
|
||||
|
||||
# 测试获取按交易所划分的品种列表
|
||||
print("\n2. 测试获取按交易所划分的品种列表:")
|
||||
symbols_by_exchange = fetcher.get_all_symbols_by_exchange()
|
||||
for exchange, products in symbols_by_exchange.items():
|
||||
print(f"\n交易所: {exchange}")
|
||||
print(f"品种数量: {len(products)}")
|
||||
for product, contracts in products.items():
|
||||
print(f" {product}: {contracts[:3]}... (共{len(contracts)}个合约)")
|
||||
|
||||
# 测试用户指定的品种是否都存在
|
||||
print("\n3. 测试用户指定的品种是否都存在:")
|
||||
specified_products = [
|
||||
"金", "银", "铜", "镍", "锡", "玻璃", "烧碱", "纯碱", "焦煤",
|
||||
"螺纹钢", "氧化铝", "甲醇", "PVC", "燃油", "原油", "铝", "棕榈油",
|
||||
"碳酸锂", "工业硅", "橡胶", "合成橡胶", "锌", "20号胶", "多晶硅",
|
||||
"中证1000", "中证500", "低硫燃油", "上证50"
|
||||
]
|
||||
|
||||
# 品种中文名称映射
|
||||
product_name_map = {
|
||||
'AU': '金',
|
||||
'AG': '银',
|
||||
'CU': '铜',
|
||||
'NI': '镍',
|
||||
'SN': '锡',
|
||||
'FG': '玻璃',
|
||||
'LY': '烧碱',
|
||||
'SA': '纯碱',
|
||||
'JM': '焦煤',
|
||||
'RB': '螺纹钢',
|
||||
'ALO': '氧化铝',
|
||||
'MA': '甲醇',
|
||||
'V': 'PVC',
|
||||
'FU': '燃油',
|
||||
'SC': '原油',
|
||||
'AL': '铝',
|
||||
'P': '棕榈油',
|
||||
'LI': '碳酸锂',
|
||||
'SI': '工业硅',
|
||||
'RU': '橡胶',
|
||||
'BR': '合成橡胶',
|
||||
'ZN': '锌',
|
||||
'NR': '20号胶',
|
||||
'SP': '多晶硅',
|
||||
'IM': '中证1000',
|
||||
'IC': '中证500',
|
||||
'LU': '低硫燃油',
|
||||
'IH': '上证50'
|
||||
}
|
||||
|
||||
# 检查每个指定的品种
|
||||
found_products = []
|
||||
missing_products = []
|
||||
|
||||
for product_code, product_name in product_name_map.items():
|
||||
# 检查是否在模拟品种列表中
|
||||
mock_symbols = fetcher._get_mock_all_symbols()
|
||||
product_found = any(symbol.startswith(product_code) for symbol in mock_symbols)
|
||||
|
||||
# 检查是否在按交易所划分的列表中
|
||||
exchange_found = False
|
||||
for exchange, products in symbols_by_exchange.items():
|
||||
if product_code in products:
|
||||
exchange_found = True
|
||||
break
|
||||
|
||||
if product_found or exchange_found:
|
||||
found_products.append(product_name)
|
||||
print(f"✓ 找到: {product_name} ({product_code})")
|
||||
else:
|
||||
missing_products.append(product_name)
|
||||
print(f"✗ 缺失: {product_name} ({product_code})")
|
||||
|
||||
print(f"\n4. 检查结果:")
|
||||
print(f"找到的品种: {len(found_products)}/{len(specified_products)}")
|
||||
print(f"缺失的品种: {len(missing_products)}/{len(specified_products)}")
|
||||
|
||||
if missing_products:
|
||||
print(f"缺失的品种: {missing_products}")
|
||||
else:
|
||||
print("✓ 所有用户指定的品种都已找到!")
|
||||
|
||||
# 测试获取K线数据
|
||||
print("\n5. 测试获取K线数据:")
|
||||
test_symbols = ['AU2603', 'AG2603', 'CU2603', 'NI2603', 'SI2603']
|
||||
for symbol in test_symbols:
|
||||
try:
|
||||
data = fetcher.get_kline_data(symbol, "1h", 10)
|
||||
if data is not None:
|
||||
print(f"✓ 成功获取 {symbol} 的K线数据 ({len(data)}条)")
|
||||
else:
|
||||
print(f"✗ 无法获取 {symbol} 的K线数据")
|
||||
except Exception as e:
|
||||
print(f"✗ 获取 {symbol} 数据失败: {e}")
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_enum_symbols()
|
||||
@ -1,90 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# 测试铁矿石合约代码修复
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 确保能导入项目模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
|
||||
from qihuo_analyzer.data.data_fetcher import DataFetcher
|
||||
|
||||
def test_iron_ore_contract():
|
||||
"""测试铁矿石合约代码处理"""
|
||||
print("=== 测试铁矿石合约代码修复 ===")
|
||||
|
||||
# 测试TQSDK适配器的合约代码转换
|
||||
print("\n1. 测试TQSDK适配器合约代码转换:")
|
||||
adapter = TqSdkAdapter()
|
||||
|
||||
test_symbols = [
|
||||
"I2603", # 铁矿石
|
||||
"J2603", # 焦炭
|
||||
"JM2603", # 焦煤
|
||||
"CU2603", # 铜
|
||||
"AL2603", # 铝
|
||||
"AU2603", # 黄金
|
||||
"AG2603", # 白银
|
||||
"RB2603", # 螺纹钢
|
||||
"P2603", # 棕榈油
|
||||
"V2603", # PVC
|
||||
"MA2603", # 甲醇
|
||||
"FG2603", # 玻璃
|
||||
"SA2603", # 纯碱
|
||||
"LY2603", # 烧碱
|
||||
"LI2603", # 碳酸锂
|
||||
"SI2603", # 工业硅
|
||||
"SP2603", # 多晶硅
|
||||
"RU2603", # 橡胶
|
||||
"NR2603", # 20号胶
|
||||
"FU2603", # 燃油
|
||||
"SC2603", # 原油
|
||||
"LU2603", # 低硫燃油
|
||||
"ALO2603",# 氧化铝
|
||||
"BR2603", # 合成橡胶
|
||||
]
|
||||
|
||||
for symbol in test_symbols:
|
||||
try:
|
||||
tq_symbol = adapter._convert_symbol(symbol)
|
||||
print(f"✓ {symbol} → {tq_symbol}")
|
||||
except Exception as e:
|
||||
print(f"✗ {symbol} → 转换失败: {e}")
|
||||
|
||||
# 测试DataFetcher获取铁矿石数据
|
||||
print("\n2. 测试DataFetcher获取铁矿石数据:")
|
||||
fetcher = DataFetcher()
|
||||
|
||||
try:
|
||||
# 测试获取铁矿石K线数据
|
||||
print("测试获取 I2603 的K线数据...")
|
||||
kline_data = fetcher.get_kline_data("I2603", "1h", 10)
|
||||
if kline_data is not None:
|
||||
print(f"✓ 成功获取 {len(kline_data)} 条铁矿石K线数据")
|
||||
print("数据示例:")
|
||||
print(kline_data.head())
|
||||
else:
|
||||
print("✗ 无法获取铁矿石K线数据")
|
||||
except Exception as e:
|
||||
print(f"✗ 获取铁矿石数据失败: {e}")
|
||||
|
||||
# 测试其他单字符品种
|
||||
print("\n3. 测试其他单字符品种:")
|
||||
single_char_symbols = ["I2603", "J2603", "P2603", "C2603", "L2603"]
|
||||
|
||||
for symbol in single_char_symbols:
|
||||
try:
|
||||
print(f"测试获取 {symbol} 的K线数据...")
|
||||
kline_data = fetcher.get_kline_data(symbol, "1h", 5)
|
||||
if kline_data is not None:
|
||||
print(f"✓ 成功获取 {symbol} 的K线数据")
|
||||
else:
|
||||
print(f"✗ 无法获取 {symbol} 的K线数据")
|
||||
except Exception as e:
|
||||
print(f"✗ 获取 {symbol} 数据失败: {e}")
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_iron_ore_contract()
|
||||
@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# 测试TQSDK适配器的_get_mock_all_symbols方法
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 确保能导入项目模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
|
||||
|
||||
def test_mock_all_symbols():
|
||||
"""测试_get_mock_all_symbols方法是否返回exchange_map中映射的所有品种"""
|
||||
print("=== 测试TQSDK适配器_get_mock_all_symbols方法 ===")
|
||||
|
||||
# 创建TQSDK适配器实例
|
||||
adapter = TqSdkAdapter()
|
||||
|
||||
# 获取模拟品种列表
|
||||
mock_symbols = adapter._get_mock_all_symbols()
|
||||
|
||||
print(f"\n1. 测试结果:")
|
||||
print(f"模拟品种列表长度: {len(mock_symbols)}")
|
||||
print(f"exchange_map长度: {len(adapter.exchange_map)}")
|
||||
print(f"两者长度是否一致: {len(mock_symbols) == len(adapter.exchange_map)}")
|
||||
|
||||
print("\n2. 模拟品种列表:")
|
||||
print(sorted(mock_symbols))
|
||||
|
||||
print("\n3. exchange_map中的品种:")
|
||||
print(sorted(adapter.exchange_map.keys()))
|
||||
|
||||
# 验证所有exchange_map中的品种都在模拟列表中
|
||||
print("\n4. 验证所有exchange_map中的品种都在模拟列表中:")
|
||||
missing_symbols = []
|
||||
for product_code in adapter.exchange_map:
|
||||
expected_symbol = f"{product_code}2603"
|
||||
if expected_symbol not in mock_symbols:
|
||||
missing_symbols.append(expected_symbol)
|
||||
|
||||
if missing_symbols:
|
||||
print(f"✗ 缺失的品种: {missing_symbols}")
|
||||
else:
|
||||
print("✓ 所有exchange_map中的品种都在模拟列表中")
|
||||
|
||||
# 验证模拟列表中的品种都在exchange_map中
|
||||
print("\n5. 验证模拟列表中的品种都在exchange_map中:")
|
||||
invalid_symbols = []
|
||||
for symbol in mock_symbols:
|
||||
# 提取品种代码
|
||||
if len(symbol) == 5:
|
||||
# 3字符品种代码
|
||||
product_code = symbol[:3]
|
||||
elif len(symbol) == 4:
|
||||
# 2字符或1字符品种代码
|
||||
# 先尝试2字符
|
||||
product_code = symbol[:2]
|
||||
if product_code not in adapter.exchange_map:
|
||||
# 尝试1字符
|
||||
product_code = symbol[:1]
|
||||
else:
|
||||
product_code = symbol[:2]
|
||||
|
||||
if product_code not in adapter.exchange_map:
|
||||
invalid_symbols.append(symbol)
|
||||
|
||||
if invalid_symbols:
|
||||
print(f"✗ 无效的品种: {invalid_symbols}")
|
||||
else:
|
||||
print("✓ 模拟列表中的所有品种都在exchange_map中")
|
||||
|
||||
# 测试get_all_symbols方法
|
||||
print("\n6. 测试get_all_symbols方法:")
|
||||
all_symbols = adapter.get_all_symbols()
|
||||
print(f"get_all_symbols返回长度: {len(all_symbols)}")
|
||||
print(f"与模拟列表长度是否一致: {len(all_symbols) == len(mock_symbols)}")
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_mock_all_symbols()
|
||||
@ -1,102 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# 测试TQSDK合约代码格式修复
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# 确保能导入项目模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from qihuo_analyzer.data.api_adapters.tqsdk_adapter import TqSdkAdapter
|
||||
from qihuo_analyzer.data.data_fetcher import DataFetcher
|
||||
|
||||
def test_tq_symbol_format():
|
||||
"""测试TQSDK合约代码格式"""
|
||||
print("=== 测试TQSDK合约代码格式修复 ===")
|
||||
|
||||
# 测试TQSDK适配器的合约代码转换
|
||||
print("\n1. 测试TQSDK适配器合约代码转换:")
|
||||
adapter = TqSdkAdapter()
|
||||
|
||||
test_symbols = [
|
||||
"TA2603", # PTA
|
||||
"CF2603", # 棉花
|
||||
"SR2603", # 白糖
|
||||
"MA2603", # 甲醇
|
||||
"ZC2603", # 动力煤
|
||||
"FG2603", # 玻璃
|
||||
"RM2603", # 菜籽粕
|
||||
"OI2603", # 菜籽油
|
||||
"SA2603", # 纯碱
|
||||
"LY2603", # 烧碱
|
||||
"CU2603", # 铜
|
||||
"AL2603", # 铝
|
||||
"ZN2603", # 锌
|
||||
"PB2603", # 铅
|
||||
"NI2603", # 镍
|
||||
"SN2603", # 锡
|
||||
"AU2603", # 黄金
|
||||
"AG2603", # 白银
|
||||
"RB2603", # 螺纹钢
|
||||
"HC2603", # 热轧卷板
|
||||
"BU2603", # 沥青
|
||||
"RU2603", # 橡胶
|
||||
"FU2603", # 燃油
|
||||
"I2603", # 铁矿石
|
||||
"J2603", # 焦炭
|
||||
"JM2603", # 焦煤
|
||||
"A2603", # 大豆
|
||||
"B2603", # 豆粕
|
||||
"M2603", # 豆粕
|
||||
"Y2603", # 豆油
|
||||
"P2603", # 棕榈油
|
||||
"C2603", # 玉米
|
||||
"CS2603", # 玉米淀粉
|
||||
"L2603", # 聚乙烯
|
||||
"V2603", # 聚氯乙烯
|
||||
"PP2603", # 聚丙烯
|
||||
]
|
||||
|
||||
for symbol in test_symbols:
|
||||
try:
|
||||
tq_symbol = adapter._convert_symbol(symbol)
|
||||
print(f"✓ {symbol} → {tq_symbol}")
|
||||
except Exception as e:
|
||||
print(f"✗ {symbol} → 转换失败: {e}")
|
||||
|
||||
# 测试DataFetcher获取PTA数据
|
||||
print("\n2. 测试DataFetcher获取PTA数据:")
|
||||
fetcher = DataFetcher()
|
||||
|
||||
try:
|
||||
# 测试获取PTA K线数据
|
||||
print("测试获取 TA2603 的K线数据...")
|
||||
kline_data = fetcher.get_kline_data("TA2603", "1h", 10)
|
||||
if kline_data is not None:
|
||||
print(f"✓ 成功获取 {len(kline_data)} 条PTA K线数据")
|
||||
print("数据示例:")
|
||||
print(kline_data.head())
|
||||
else:
|
||||
print("✗ 无法获取PTA K线数据")
|
||||
except Exception as e:
|
||||
print(f"✗ 获取PTA数据失败: {e}")
|
||||
|
||||
# 测试获取其他郑州商品交易所数据
|
||||
print("\n3. 测试获取其他郑州商品交易所数据:")
|
||||
czce_symbols = ["CF2603", "SR2603", "MA2603", "FG2603"]
|
||||
|
||||
for symbol in czce_symbols:
|
||||
try:
|
||||
print(f"测试获取 {symbol} 的K线数据...")
|
||||
kline_data = fetcher.get_kline_data(symbol, "1h", 5)
|
||||
if kline_data is not None:
|
||||
print(f"✓ 成功获取 {symbol} 的K线数据")
|
||||
else:
|
||||
print(f"✗ 无法获取 {symbol} 的K线数据")
|
||||
except Exception as e:
|
||||
print(f"✗ 获取 {symbol} 数据失败: {e}")
|
||||
|
||||
print("\n=== 测试完成 ===")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_tq_symbol_format()
|
||||
Binary file not shown.
@ -1,83 +0,0 @@
|
||||
# 认证模块
|
||||
|
||||
from flask import Flask, request, redirect, url_for, flash
|
||||
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
import sqlite3
|
||||
import os
|
||||
|
||||
# 初始化Flask-Login
|
||||
login_manager = LoginManager()
|
||||
|
||||
class User(UserMixin):
|
||||
def __init__(self, id, username, password_hash):
|
||||
self.id = id
|
||||
self.username = username
|
||||
self.password_hash = password_hash
|
||||
|
||||
def check_password(self, password):
|
||||
return check_password_hash(self.password_hash, password)
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
"""根据用户ID加载用户对象"""
|
||||
conn = get_db_connection()
|
||||
user = conn.execute('SELECT id, username, password_hash FROM users WHERE id = ?', (user_id,)).fetchone()
|
||||
conn.close()
|
||||
if user:
|
||||
return User(user['id'], user['username'], user['password_hash'])
|
||||
return None
|
||||
|
||||
def get_db_connection():
|
||||
"""获取数据库连接"""
|
||||
db_path = os.path.join(os.path.dirname(__file__), 'data', 'futures_analysis.db')
|
||||
conn = sqlite3.connect(db_path)
|
||||
conn.row_factory = sqlite3.Row
|
||||
return conn
|
||||
|
||||
def init_db():
|
||||
"""初始化数据库,创建用户表"""
|
||||
conn = get_db_connection()
|
||||
conn.execute('''
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL
|
||||
)
|
||||
''')
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
def register_user(username, password):
|
||||
"""注册新用户"""
|
||||
conn = get_db_connection()
|
||||
try:
|
||||
# 检查用户名是否已存在
|
||||
existing_user = conn.execute('SELECT id FROM users WHERE username = ?', (username,)).fetchone()
|
||||
if existing_user:
|
||||
return False, "用户名已存在"
|
||||
|
||||
# 生成密码哈希
|
||||
password_hash = generate_password_hash(password)
|
||||
|
||||
# 插入新用户
|
||||
conn.execute('INSERT INTO users (username, password_hash) VALUES (?, ?)', (username, password_hash))
|
||||
conn.commit()
|
||||
return True, "注册成功"
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
return False, str(e)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
def login_user_by_credentials(username, password):
|
||||
"""通过用户名和密码登录用户"""
|
||||
conn = get_db_connection()
|
||||
user = conn.execute('SELECT id, username, password_hash FROM users WHERE username = ?', (username,)).fetchone()
|
||||
conn.close()
|
||||
|
||||
if user and check_password_hash(user['password_hash'], password):
|
||||
user_obj = User(user['id'], user['username'], user['password_hash'])
|
||||
login_user(user_obj)
|
||||
return True, "登录成功"
|
||||
return False, "用户名或密码错误"
|
||||
Binary file not shown.
@ -1,198 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - AI期货分析系统</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #f0f2f5;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #f9fafb;
|
||||
--text-primary: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-tertiary: #999999;
|
||||
--border-color: #d9d9d9;
|
||||
--header-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
--card-hover-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
--button-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: all 0.3s ease;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 30px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--button-bg);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.links {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.flash-message {
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flash-message.error {
|
||||
background-color: rgba(255, 77, 79, 0.1);
|
||||
color: #ff4d4f;
|
||||
border: 1px solid rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.flash-message.success {
|
||||
background-color: rgba(82, 196, 26, 0.1);
|
||||
color: #52c41a;
|
||||
border: 1px solid rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>登录</h1>
|
||||
<p>欢迎使用AI期货分析系统</p>
|
||||
</div>
|
||||
|
||||
<!-- 显示flash消息 -->
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="flash-message {% if '成功' in message %}success{% else %}error{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">登录</button>
|
||||
</form>
|
||||
|
||||
<div class="links">
|
||||
<p>还没有账号? <a href="{{ url_for('register') }}">立即注册</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,198 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>注册 - AI期货分析系统</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/antd@5.12.8/dist/reset.css" rel="stylesheet">
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg-primary: #f0f2f5;
|
||||
--bg-secondary: #ffffff;
|
||||
--bg-tertiary: #f9fafb;
|
||||
--text-primary: #333333;
|
||||
--text-secondary: #666666;
|
||||
--text-tertiary: #999999;
|
||||
--border-color: #d9d9d9;
|
||||
--header-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
--card-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||
--card-hover-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
|
||||
--button-bg: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: all 0.3s ease;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
padding: 30px;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 12px;
|
||||
box-shadow: var(--card-shadow);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: all 0.3s ease;
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
border-color: #1890ff;
|
||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--button-bg);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.9;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-tertiary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-secondary);
|
||||
}
|
||||
|
||||
.links {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.links a {
|
||||
color: #1890ff;
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.links a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.flash-message {
|
||||
padding: 12px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.flash-message.error {
|
||||
background-color: rgba(255, 77, 79, 0.1);
|
||||
color: #ff4d4f;
|
||||
border: 1px solid rgba(255, 77, 79, 0.3);
|
||||
}
|
||||
|
||||
.flash-message.success {
|
||||
background-color: rgba(82, 196, 26, 0.1);
|
||||
color: #52c41a;
|
||||
border: 1px solid rgba(82, 196, 26, 0.3);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<h1>注册</h1>
|
||||
<p>创建一个新账号以使用AI期货分析系统</p>
|
||||
</div>
|
||||
|
||||
<!-- 显示flash消息 -->
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="flash-message {% if '成功' in message %}success{% else %}error{% endif %}">
|
||||
{{ message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">注册</button>
|
||||
</form>
|
||||
|
||||
<div class="links">
|
||||
<p>已有账号? <a href="{{ url_for('login') }}">立即登录</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,49 +0,0 @@
|
||||
self.exchange_map = {
|
||||
'AU': 'SHFE', # 黄金 - 上海期货交易所
|
||||
'AG': 'SHFE', # 白银 - 上海期货交易所
|
||||
'CU': 'SHFE', # 铜 - 上海期货交易所
|
||||
'NI': 'SHFE', # 镍 - 上海期货交易所
|
||||
'SN': 'SHFE', # 锡 - 上海期货交易所
|
||||
'FG': 'CZCE', # 玻璃 - 郑州商品交易所
|
||||
'LY': 'CZCE', # 烧碱 - 郑州商品交易所
|
||||
'SA': 'CZCE', # 纯碱 - 郑州商品交易所
|
||||
'JM': 'DCE', # 焦煤 - 大连商品交易所
|
||||
'RB': 'SHFE', # 螺纹钢 - 上海期货交易所
|
||||
'ALO': 'SHFE', # 氧化铝 - 上海期货交易所
|
||||
'MA': 'CZCE', # 甲醇 - 郑州商品交易所
|
||||
'V': 'DCE', # PVC - 大连商品交易所
|
||||
'FU': 'SHFE', # 燃油 - 上海期货交易所
|
||||
'SC': 'INE', # 原油 - 上海国际能源交易中心
|
||||
'AL': 'SHFE', # 铝 - 上海期货交易所
|
||||
'P': 'DCE', # 棕榈油 - 大连商品交易所
|
||||
'LI': 'SHFE', # 碳酸锂 - 上海期货交易所
|
||||
'SI': 'GEM', # 工业硅 - 广州期货交易所
|
||||
'RU': 'SHFE', # 橡胶 - 上海期货交易所
|
||||
'BR': 'DCE', # 合成橡胶 - 大连商品交易所
|
||||
'ZN': 'SHFE', # 锌 - 上海期货交易所
|
||||
'NR': 'SHFE', # 20号胶 - 上海期货交易所
|
||||
'SP': 'GEM', # 多晶硅 - 广州期货交易所
|
||||
'IM': 'CFFEX', # 中证1000 - 中国金融期货交易所
|
||||
'IC': 'CFFEX', # 中证500 - 中国金融期货交易所
|
||||
'LU': 'INE', # 低硫燃油 - 上海国际能源交易中心
|
||||
'IH': 'CFFEX', # 上证50 - 中国金融期货交易所
|
||||
'HC': 'SHFE', # 热轧卷板 - 上海期货交易所
|
||||
'BU': 'SHFE', # 沥青 - 上海期货交易所
|
||||
'PB': 'SHFE', # 铅 - 上海期货交易所
|
||||
'I': 'DCE', # 铁矿石 - 大连商品交易所
|
||||
'J': 'DCE', # 焦炭 - 大连商品交易所
|
||||
'A': 'DCE', # 大豆 - 大连商品交易所
|
||||
'B': 'DCE', # 豆粕 - 大连商品交易所
|
||||
'M': 'DCE', # 豆粕 - 大连商品交易所
|
||||
'Y': 'DCE', # 豆油 - 大连商品交易所
|
||||
'C': 'DCE', # 玉米 - 大连商品交易所
|
||||
'CS': 'DCE', # 玉米淀粉 - 大连商品交易所
|
||||
'L': 'DCE', # 聚乙烯 - 大连商品交易所
|
||||
'PP': 'DCE', # 聚丙烯 - 大连商品交易所
|
||||
'TA': 'CZCE', # PTA - 郑州商品交易所
|
||||
'CF': 'CZCE', # 棉花 - 郑州商品交易所
|
||||
'SR': 'CZCE', # 白糖 - 郑州商品交易所
|
||||
'ZC': 'CZCE', # 动力煤 - 郑州商品交易所
|
||||
'RM': 'CZCE', # 菜籽粕 - 郑州商品交易所
|
||||
'OI': 'CZCE', # 菜籽油 - 郑州商品交易所
|
||||
}
|
||||
Loading…
Reference in new issue