Remove files
This commit is contained in:
+236
-19
@@ -225,17 +225,35 @@ def get_a_stock_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time
|
||||
|
||||
if start_time:
|
||||
try:
|
||||
# 尝试解析时间戳(毫秒)
|
||||
start_timestamp = int(start_time)
|
||||
start_date = datetime.fromtimestamp(start_timestamp / 1000).strftime('%Y-%m-%d')
|
||||
except:
|
||||
start_date = start_time
|
||||
except (ValueError, TypeError):
|
||||
# 如果不是时间戳,尝试解析datetime-local格式 (YYYY-MM-DDTHH:MM)
|
||||
try:
|
||||
if 'T' in str(start_time):
|
||||
# datetime-local格式:2025-05-19T06:07
|
||||
start_date = str(start_time).split('T')[0] # 只取日期部分
|
||||
else:
|
||||
start_date = str(start_time)
|
||||
except:
|
||||
start_date = start_time
|
||||
|
||||
if end_time:
|
||||
try:
|
||||
# 尝试解析时间戳(毫秒)
|
||||
end_timestamp = int(end_time)
|
||||
end_date = datetime.fromtimestamp(end_timestamp / 1000).strftime('%Y-%m-%d')
|
||||
except:
|
||||
end_date = end_time
|
||||
except (ValueError, TypeError):
|
||||
# 如果不是时间戳,尝试解析datetime-local格式
|
||||
try:
|
||||
if 'T' in str(end_time):
|
||||
# datetime-local格式:2025-05-26T06:07
|
||||
end_date = str(end_time).split('T')[0] # 只取日期部分
|
||||
else:
|
||||
end_date = str(end_time)
|
||||
except:
|
||||
end_date = end_time
|
||||
|
||||
# 如果用户指定了时间范围,优先获取该范围内的所有数据
|
||||
actual_limit = limit
|
||||
@@ -328,22 +346,51 @@ def analyze_chan(df):
|
||||
klc_fx_info = []
|
||||
for klc in klc_list:
|
||||
if hasattr(klc, 'klc_fx_type') and klc.klc_fx_type != Chan_KLC_FX.UNKNOWN:
|
||||
# 计算分型强度
|
||||
#fx_strength = klc.calculate_fx_strength()
|
||||
fx_strength = klc.cal_fx_strength()
|
||||
fx_strength_level = klc.get_fx_strength_level()
|
||||
is_strong_fx = klc.is_strong_fx()
|
||||
if fx_strength < 1:
|
||||
try:
|
||||
# 计算分型强度
|
||||
fx_strength = 0
|
||||
klc_fx_info.append({
|
||||
'time': klc.end_time,
|
||||
'price': klc.low if klc.fx == Chan_FX_TYPE.BOTTOM else klc.high,
|
||||
'fx_type': str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""),
|
||||
'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM,
|
||||
'fx_strength': fx_strength, # 分型强度分数 (0-100)
|
||||
'fx_strength_level': fx_strength_level, # 分型强度等级 (极强/强/中等/弱/极弱)
|
||||
'is_strong_fx': is_strong_fx # 是否为强分型
|
||||
})
|
||||
fx_strength_level = ""
|
||||
is_strong_fx = False
|
||||
|
||||
# 尝试调用分型强度计算方法
|
||||
if hasattr(klc, 'cal_fx_strength'):
|
||||
fx_strength = klc.cal_fx_strength()
|
||||
elif hasattr(klc, 'calculate_fx_strength'):
|
||||
fx_strength = klc.calculate_fx_strength()
|
||||
|
||||
# 尝试获取分型强度等级
|
||||
if hasattr(klc, 'get_fx_strength_level'):
|
||||
fx_strength_level = klc.get_fx_strength_level()
|
||||
|
||||
# 尝试判断是否为强分型
|
||||
if hasattr(klc, 'is_strong_fx'):
|
||||
is_strong_fx = klc.is_strong_fx()
|
||||
|
||||
# 如果分型强度小于1,设为0
|
||||
if fx_strength < 1:
|
||||
fx_strength = 0
|
||||
|
||||
klc_fx_info.append({
|
||||
'time': klc.end_time,
|
||||
'price': klc.low if klc.fx == Chan_FX_TYPE.BOTTOM else klc.high,
|
||||
'fx_type': str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""),
|
||||
'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM,
|
||||
'fx_strength': fx_strength, # 分型强度分数 (0-100)
|
||||
'fx_strength_level': fx_strength_level, # 分型强度等级 (极强/强/中等/弱/极弱)
|
||||
'is_strong_fx': is_strong_fx # 是否为强分型
|
||||
})
|
||||
except Exception as e:
|
||||
print(f"处理KLC分型信息时出错: {e}")
|
||||
# 如果出错,仍然添加基本信息,但分型强度为0
|
||||
klc_fx_info.append({
|
||||
'time': klc.end_time,
|
||||
'price': klc.low if klc.fx == Chan_FX_TYPE.BOTTOM else klc.high,
|
||||
'fx_type': str(klc.klc_fx_type).replace("Chan_KLC_FX.", ""),
|
||||
'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM,
|
||||
'fx_strength': 0,
|
||||
'fx_strength_level': "",
|
||||
'is_strong_fx': False
|
||||
})
|
||||
|
||||
return {
|
||||
'klc_list': klc_list,
|
||||
@@ -766,5 +813,175 @@ def search_stock():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
@app.route('/api/filter_stocks', methods=['POST'])
|
||||
def filter_stocks():
|
||||
"""筛选满足条件的A股股票"""
|
||||
try:
|
||||
data = request.get_json()
|
||||
start_time = data.get('start_time')
|
||||
end_time = data.get('end_time')
|
||||
timeframe = data.get('timeframe', '1d')
|
||||
fx_strength_threshold = data.get('fx_strength_threshold', 1.0)
|
||||
|
||||
if not start_time or not end_time:
|
||||
return jsonify({'error': '开始时间和结束时间不能为空'})
|
||||
|
||||
# 获取所有A股股票列表,如果失败则使用热门股票作为备用
|
||||
stock_list = []
|
||||
data_source = ""
|
||||
try:
|
||||
print("正在获取完整股票列表...")
|
||||
stock_list = china_stock.get_stock_list()
|
||||
if stock_list and len(stock_list) > 0:
|
||||
print(f"成功获取完整股票列表: {len(stock_list)} 只股票")
|
||||
data_source = "完整股票列表"
|
||||
else:
|
||||
raise Exception("获取到的股票列表为空")
|
||||
except Exception as e:
|
||||
print(f"获取完整股票列表失败: {e}")
|
||||
print("使用热门股票列表作为备用...")
|
||||
try:
|
||||
popular_stocks = china_stock.get_popular_stocks()
|
||||
stock_list = [{'symbol': stock['symbol'], 'name': stock['name']} for stock in popular_stocks]
|
||||
print(f"使用热门股票列表: {len(stock_list)} 只股票")
|
||||
data_source = "热门股票列表"
|
||||
except Exception as e2:
|
||||
print(f"获取热门股票列表也失败: {e2}")
|
||||
# 检查是否是网络连接问题
|
||||
if "timeout" in str(e).lower() or "connection" in str(e).lower() or "network" in str(e).lower():
|
||||
return jsonify({
|
||||
'error': '网络连接超时,无法获取股票数据。请检查网络连接后重试。',
|
||||
'error_type': 'network_error',
|
||||
'suggestion': '请确保网络连接正常,或稍后重试。'
|
||||
})
|
||||
else:
|
||||
return jsonify({'error': f'无法获取股票列表: {str(e)}'})
|
||||
|
||||
if not stock_list:
|
||||
return jsonify({
|
||||
'error': '无法获取股票列表,请检查网络连接后重试',
|
||||
'error_type': 'network_error',
|
||||
'suggestion': '请确保网络连接正常,或稍后重试。'
|
||||
})
|
||||
|
||||
results = []
|
||||
processed_count = 0
|
||||
total_count = len(stock_list)
|
||||
failed_count = 0
|
||||
|
||||
print(f"开始筛选股票,总数: {total_count}, 时间范围: {start_time} 到 {end_time}, 周期: {timeframe}")
|
||||
|
||||
for stock in stock_list:
|
||||
try:
|
||||
symbol = stock['symbol']
|
||||
name = stock['name']
|
||||
processed_count += 1
|
||||
|
||||
# 每处理20只股票打印一次进度
|
||||
if processed_count % 20 == 0:
|
||||
print(f"已处理 {processed_count}/{total_count} 只股票,成功: {len(results)}, 失败: {failed_count}")
|
||||
|
||||
# 获取股票K线数据
|
||||
df = get_a_stock_kl_data(symbol, timeframe, start_time=start_time, end_time=end_time)
|
||||
|
||||
if df is None or len(df) < 3:
|
||||
failed_count += 1
|
||||
# 如果连续失败太多,可能是网络问题
|
||||
if failed_count > 10 and len(results) == 0:
|
||||
print(f"连续失败 {failed_count} 次,可能是网络问题")
|
||||
return jsonify({
|
||||
'error': '网络连接不稳定,无法获取股票数据。请检查网络连接后重试。',
|
||||
'error_type': 'network_error',
|
||||
'processed_count': processed_count,
|
||||
'failed_count': failed_count
|
||||
})
|
||||
continue
|
||||
|
||||
# 进行缠论分析
|
||||
analysis_result = analyze_chan(df)
|
||||
|
||||
if not analysis_result or 'klc_fx_info' not in analysis_result:
|
||||
continue
|
||||
|
||||
klc_fx_info = analysis_result['klc_fx_info']
|
||||
|
||||
# 检查最近2个KLC是否有满足条件的分型
|
||||
recent_klcs = klc_fx_info[-2:] if len(klc_fx_info) >= 2 else klc_fx_info
|
||||
|
||||
for klc_info in recent_klcs:
|
||||
fx_strength = klc_info.get('fx_strength', 0)
|
||||
fx_type = klc_info.get('fx_type', 'UNKNOWN')
|
||||
|
||||
# 检查是否满足条件:分型强度>=阈值 且 分型类型不为UNKNOWN
|
||||
if fx_strength >= fx_strength_threshold and fx_type != 'UNKNOWN':
|
||||
# 获取当前价格(最新收盘价)
|
||||
current_price = df['close'].iloc[-1] if len(df) > 0 else None
|
||||
fx_price = klc_info.get('price', 0)
|
||||
|
||||
# 计算涨跌幅
|
||||
change_percent = 0
|
||||
if current_price and fx_price and fx_price > 0:
|
||||
change_percent = ((current_price - fx_price) / fx_price) * 100
|
||||
|
||||
# 格式化分型类型显示
|
||||
fx_type_display = format_fx_type(fx_type)
|
||||
|
||||
results.append({
|
||||
'symbol': symbol,
|
||||
'name': name,
|
||||
'fx_time': klc_info.get('time', ''),
|
||||
'fx_type': fx_type_display,
|
||||
'fx_strength': fx_strength,
|
||||
'fx_price': fx_price,
|
||||
'current_price': current_price,
|
||||
'change_percent': change_percent
|
||||
})
|
||||
break # 找到一个满足条件的就跳出循环
|
||||
|
||||
except Exception as e:
|
||||
print(f"处理股票 {symbol} 时出错: {str(e)}")
|
||||
failed_count += 1
|
||||
continue
|
||||
|
||||
print(f"筛选完成,共找到 {len(results)} 只满足条件的股票")
|
||||
|
||||
# 按分型强度降序排列
|
||||
results.sort(key=lambda x: x['fx_strength'], reverse=True)
|
||||
|
||||
return jsonify({
|
||||
'results': results,
|
||||
'total_processed': processed_count,
|
||||
'total_found': len(results),
|
||||
'failed_count': failed_count,
|
||||
'data_source': data_source,
|
||||
'message': f'使用{data_source}进行筛选,共处理{processed_count}只股票,找到{len(results)}只满足条件的股票'
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"筛选股票时发生错误: {str(e)}")
|
||||
# 检查是否是网络连接问题
|
||||
if "timeout" in str(e).lower() or "connection" in str(e).lower() or "network" in str(e).lower():
|
||||
return jsonify({
|
||||
'error': '网络连接超时,请检查网络连接后重试。',
|
||||
'error_type': 'network_error',
|
||||
'suggestion': '请确保网络连接正常,或稍后重试。'
|
||||
})
|
||||
else:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
def format_fx_type(fx_type):
|
||||
"""格式化分型类型显示"""
|
||||
fx_type_map = {
|
||||
'TOP1': '顶分型1',
|
||||
'TOP2': '顶分型2',
|
||||
'TOP3': '顶分型3',
|
||||
'BOTTOM1': '底分型1',
|
||||
'BOTTOM2': '底分型2',
|
||||
'BOTTOM3': '底分型3',
|
||||
'TOP': '顶分型',
|
||||
'BOTTOM': '底分型'
|
||||
}
|
||||
return fx_type_map.get(fx_type, fx_type)
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=8123)
|
||||
+46
-13
@@ -21,26 +21,59 @@ class ChinaStockData:
|
||||
def get_stock_list(self):
|
||||
"""获取A股股票列表"""
|
||||
try:
|
||||
# 获取沪深A股实时行情
|
||||
stock_info = ak.stock_zh_a_spot_em()
|
||||
import requests
|
||||
# 设置较短的超时时间,避免长时间等待
|
||||
import akshare as ak
|
||||
|
||||
print("正在获取A股股票列表...")
|
||||
|
||||
# 尝试获取沪深A股实时行情,设置超时时间
|
||||
try:
|
||||
# 临时设置requests的默认超时
|
||||
original_timeout = getattr(requests, 'timeout', None)
|
||||
requests.timeout = 10 # 10秒超时
|
||||
|
||||
stock_info = ak.stock_zh_a_spot_em()
|
||||
|
||||
# 恢复原始超时设置
|
||||
if original_timeout:
|
||||
requests.timeout = original_timeout
|
||||
else:
|
||||
delattr(requests, 'timeout')
|
||||
|
||||
except Exception as network_error:
|
||||
print(f"网络请求失败: {network_error}")
|
||||
# 网络失败时返回空列表,让调用方使用备用方案
|
||||
return []
|
||||
|
||||
if stock_info is None or len(stock_info) == 0:
|
||||
print("获取到的股票数据为空")
|
||||
return []
|
||||
|
||||
# 增加到前2000只股票,提供更多选择
|
||||
stock_list = []
|
||||
for index, row in stock_info.head(2000).iterrows():
|
||||
# 过滤掉ST股票和停牌股票
|
||||
stock_name = str(row['名称'])
|
||||
if 'ST' not in stock_name and '*' not in stock_name:
|
||||
stock_list.append({
|
||||
'symbol': row['代码'],
|
||||
'name': row['名称'],
|
||||
'price': float(row['最新价']) if pd.notna(row['最新价']) else 0.0,
|
||||
'change_pct': float(row['涨跌幅']) if pd.notna(row['涨跌幅']) else 0.0,
|
||||
'volume': float(row['成交量']) if pd.notna(row['成交量']) else 0.0,
|
||||
'amount': float(row['成交额']) if pd.notna(row['成交额']) else 0.0
|
||||
})
|
||||
try:
|
||||
# 过滤掉ST股票和停牌股票
|
||||
stock_name = str(row['名称'])
|
||||
if 'ST' not in stock_name and '*' not in stock_name:
|
||||
stock_list.append({
|
||||
'symbol': row['代码'],
|
||||
'name': row['名称'],
|
||||
'price': float(row['最新价']) if pd.notna(row['最新价']) else 0.0,
|
||||
'change_pct': float(row['涨跌幅']) if pd.notna(row['涨跌幅']) else 0.0,
|
||||
'volume': float(row['成交量']) if pd.notna(row['成交量']) else 0.0,
|
||||
'amount': float(row['成交额']) if pd.notna(row['成交额']) else 0.0
|
||||
})
|
||||
except Exception as row_error:
|
||||
print(f"处理股票数据行时出错: {row_error}")
|
||||
continue
|
||||
|
||||
# 按成交金额排序,优先显示活跃股票
|
||||
stock_list.sort(key=lambda x: x['amount'], reverse=True)
|
||||
print(f"成功获取 {len(stock_list)} 只股票")
|
||||
return stock_list
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取股票列表失败: {e}")
|
||||
return []
|
||||
|
||||
+448
-1
@@ -74,9 +74,51 @@
|
||||
}
|
||||
.data-container {
|
||||
margin-top: 10px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
.nav-tabs {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
background-color: white;
|
||||
border-radius: 8px 8px 0 0;
|
||||
padding: 10px 10px 0 10px;
|
||||
}
|
||||
.nav-tabs .nav-link {
|
||||
border-radius: 6px 6px 0 0;
|
||||
margin-right: 5px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
.nav-tabs .nav-link:hover {
|
||||
background-color: #f8f9fa;
|
||||
border-color: #dee2e6;
|
||||
}
|
||||
.nav-tabs .nav-link.active {
|
||||
background-color: #0d6efd;
|
||||
color: white;
|
||||
border-color: #0d6efd;
|
||||
}
|
||||
/* 特别突出显示股票筛选tab */
|
||||
#stock-filter-tab {
|
||||
background-color: #28a745 !important;
|
||||
color: white !important;
|
||||
border-color: #28a745 !important;
|
||||
font-weight: bold !important;
|
||||
box-shadow: 0 2px 4px rgba(40, 167, 69, 0.3) !important;
|
||||
}
|
||||
#stock-filter-tab:hover {
|
||||
background-color: #218838 !important;
|
||||
border-color: #1e7e34 !important;
|
||||
}
|
||||
#stock-filter-tab.active {
|
||||
background-color: #155724 !important;
|
||||
border-color: #155724 !important;
|
||||
}
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
@@ -478,6 +520,9 @@
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="trade-points-tab" data-bs-toggle="tab" data-bs-target="#trade-points" type="button" role="tab">买卖点</button>
|
||||
</li>
|
||||
<li class="nav-item" role="presentation">
|
||||
<button class="nav-link" id="stock-filter-tab" data-bs-toggle="tab" data-bs-target="#stock-filter" type="button" role="tab">股票筛选</button>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="data-source-info alert alert-info py-2 mt-1 mb-2" style="display:none;">
|
||||
<small id="dataSourceText"></small>
|
||||
@@ -592,6 +637,81 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="stock-filter" role="tabpanel">
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<h5>A股强分型筛选</h5>
|
||||
<p class="text-muted">筛选最近2个K线合并(KLC)中有一个满足分型强度≥1.0且分型类型不为UNKNOWN的A股股票</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<label for="filterStartTime" class="form-label">开始时间:</label>
|
||||
<input type="datetime-local" id="filterStartTime" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="filterEndTime" class="form-label">结束时间:</label>
|
||||
<input type="datetime-local" id="filterEndTime" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="filterTimeframe" class="form-label">时间周期:</label>
|
||||
<select id="filterTimeframe" class="form-select">
|
||||
<option value="5m">5分钟</option>
|
||||
<option value="15m">15分钟</option>
|
||||
<option value="30m">30分钟</option>
|
||||
<option value="1h">1小时</option>
|
||||
<option value="4h">4小时</option>
|
||||
<option value="1d" selected>1日</option>
|
||||
<option value="1w">1周</option>
|
||||
<option value="1M">1月</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label for="fxStrengthThreshold" class="form-label">分型强度阈值:</label>
|
||||
<input type="number" id="fxStrengthThreshold" class="form-control" value="1.0" min="0" max="100" step="0.1">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<button class="btn btn-primary" onclick="filterStocks()">
|
||||
<i class="bi bi-search"></i> 开始筛选
|
||||
</button>
|
||||
<button class="btn btn-secondary ms-2" onclick="exportFilterResults()">
|
||||
<i class="bi bi-download"></i> 导出结果
|
||||
</button>
|
||||
<span id="filterProgress" class="ms-3" style="display:none;">
|
||||
<i class="bi bi-hourglass-split"></i> 正在筛选中...
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="table-container">
|
||||
<table id="stockFilterTable" class="display compact" style="width:100%">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>股票代码</th>
|
||||
<th>股票名称</th>
|
||||
<th>分型时间</th>
|
||||
<th>分型类型</th>
|
||||
<th>分型强度</th>
|
||||
<th>分型价格</th>
|
||||
<th>当前价格</th>
|
||||
<th>涨跌幅(%)</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -841,6 +961,12 @@
|
||||
updateChartDisplay();
|
||||
});
|
||||
|
||||
// 添加K线周期切换事件监听器
|
||||
$('input[name="klinePeriod"]').change(function() {
|
||||
console.log('K线周期切换:', $(this).attr('id'), $(this).is(':checked'));
|
||||
updateChartDisplay();
|
||||
});
|
||||
|
||||
// 当选择不同的元素时间周期时
|
||||
$('#elementTimeframe').change(function() {
|
||||
const elementTimeframe = $(this).val();
|
||||
@@ -1198,7 +1324,7 @@
|
||||
}
|
||||
|
||||
// 检查是否使用小周期K线数据
|
||||
const useElementPeriod = $('#useElementPeriod').is(':checked') &&
|
||||
const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
|
||||
currentData.element_kline_data &&
|
||||
Array.isArray(currentData.element_kline_data);
|
||||
|
||||
@@ -4004,6 +4130,52 @@
|
||||
updateTables(currentData);
|
||||
}
|
||||
});
|
||||
|
||||
// 页面加载完成后初始化
|
||||
$(document).ready(function() {
|
||||
// 设置默认的筛选时间(最近7天)
|
||||
const now = new Date();
|
||||
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
$('#filterEndTime').val(now.toISOString().slice(0, 16));
|
||||
$('#filterStartTime').val(weekAgo.toISOString().slice(0, 16));
|
||||
|
||||
// 初始化股票筛选表格
|
||||
initStockFilterTable();
|
||||
|
||||
// 突出显示股票筛选tab
|
||||
setTimeout(function() {
|
||||
const stockFilterTab = $('#stock-filter-tab');
|
||||
if (stockFilterTab.length > 0) {
|
||||
console.log('股票筛选tab已找到,开始突出显示');
|
||||
|
||||
// 添加闪烁效果来吸引注意
|
||||
stockFilterTab.addClass('animate__animated animate__pulse');
|
||||
|
||||
// 滚动到tab区域
|
||||
$('html, body').animate({
|
||||
scrollTop: $('.data-container').offset().top - 100
|
||||
}, 1000);
|
||||
|
||||
// 添加提示信息
|
||||
const alertDiv = $(`
|
||||
<div class="alert alert-info alert-dismissible fade show" role="alert" style="position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px;">
|
||||
<strong>新功能!</strong> 股票筛选功能已添加,请查看绿色的"股票筛选"标签页。
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`);
|
||||
$('body').append(alertDiv);
|
||||
|
||||
// 10秒后自动隐藏提示
|
||||
setTimeout(() => {
|
||||
alertDiv.alert('close');
|
||||
}, 10000);
|
||||
|
||||
} else {
|
||||
console.error('未找到股票筛选tab');
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
|
||||
// 自动刷新相关变量
|
||||
@@ -5103,6 +5275,281 @@
|
||||
window.astockStatusInterval = setInterval(updateAStockTradingStatus, 30000);
|
||||
console.log('A股交易时间状态更新器已启动');
|
||||
}
|
||||
|
||||
// 股票筛选相关函数
|
||||
let stockFilterTable = null;
|
||||
|
||||
// 初始化股票筛选表格
|
||||
function initStockFilterTable() {
|
||||
// 简单的表格初始化,不使用DataTable
|
||||
console.log('初始化股票筛选表格');
|
||||
}
|
||||
|
||||
// 筛选股票
|
||||
function filterStocks() {
|
||||
const startTime = $('#filterStartTime').val();
|
||||
const endTime = $('#filterEndTime').val();
|
||||
const timeframe = $('#filterTimeframe').val();
|
||||
const threshold = parseFloat($('#fxStrengthThreshold').val());
|
||||
|
||||
if (!startTime || !endTime) {
|
||||
alert('请选择开始时间和结束时间');
|
||||
return;
|
||||
}
|
||||
|
||||
if (new Date(startTime) >= new Date(endTime)) {
|
||||
alert('开始时间必须早于结束时间');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示进度指示器
|
||||
$('#filterProgress').show();
|
||||
$('#stockFilterTable tbody').empty();
|
||||
|
||||
// 发送筛选请求
|
||||
$.ajax({
|
||||
url: '/api/filter_stocks',
|
||||
method: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify({
|
||||
start_time: startTime,
|
||||
end_time: endTime,
|
||||
timeframe: timeframe,
|
||||
fx_strength_threshold: threshold
|
||||
}),
|
||||
timeout: 300000, // 5分钟超时
|
||||
success: function(response) {
|
||||
$('#filterProgress').hide();
|
||||
|
||||
if (response.error) {
|
||||
// 根据错误类型显示不同的错误信息
|
||||
if (response.error_type === 'network_error') {
|
||||
showNetworkErrorAlert(response.error, response.suggestion);
|
||||
} else {
|
||||
alert('筛选失败: ' + response.error);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 更新表格数据
|
||||
updateStockFilterTable(response.results);
|
||||
|
||||
// 显示统计信息
|
||||
const totalCount = response.results.length;
|
||||
const processedCount = response.total_processed || 0;
|
||||
const failedCount = response.failed_count || 0;
|
||||
const dataSource = response.data_source || '未知';
|
||||
|
||||
let message = `筛选完成!使用${dataSource},共处理 ${processedCount} 只股票,找到 ${totalCount} 只满足条件的股票`;
|
||||
if (failedCount > 0) {
|
||||
message += `,${failedCount} 只股票数据获取失败`;
|
||||
}
|
||||
|
||||
// 显示成功提示
|
||||
showSuccessAlert(message);
|
||||
},
|
||||
error: function(xhr, status, error) {
|
||||
$('#filterProgress').hide();
|
||||
console.error('筛选请求失败:', error, status, xhr);
|
||||
|
||||
// 根据错误类型显示不同的错误信息
|
||||
if (status === 'timeout') {
|
||||
showNetworkErrorAlert(
|
||||
'请求超时,可能是网络连接不稳定或数据量较大',
|
||||
'请检查网络连接,或尝试缩小时间范围后重试'
|
||||
);
|
||||
} else if (xhr.responseJSON && xhr.responseJSON.error_type === 'network_error') {
|
||||
showNetworkErrorAlert(xhr.responseJSON.error, xhr.responseJSON.suggestion);
|
||||
} else {
|
||||
alert('筛选请求失败: ' + (xhr.responseJSON?.error || error || '未知错误'));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示网络错误提示
|
||||
function showNetworkErrorAlert(errorMessage, suggestion) {
|
||||
const alertDiv = $(`
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
<h6><i class="fas fa-exclamation-triangle"></i> 网络连接问题</h6>
|
||||
<p><strong>错误信息:</strong>${errorMessage}</p>
|
||||
<p><strong>建议:</strong>${suggestion}</p>
|
||||
<hr>
|
||||
<p class="mb-0">
|
||||
<small>
|
||||
<i class="fas fa-info-circle"></i>
|
||||
如果问题持续存在,请检查网络连接或联系管理员
|
||||
</small>
|
||||
</p>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`);
|
||||
$('#stock-filter .container-fluid').prepend(alertDiv);
|
||||
|
||||
// 10秒后自动隐藏提示
|
||||
setTimeout(() => {
|
||||
alertDiv.alert('close');
|
||||
}, 10000);
|
||||
}
|
||||
|
||||
// 显示成功提示
|
||||
function showSuccessAlert(message) {
|
||||
const alertDiv = $(`
|
||||
<div class="alert alert-success alert-dismissible fade show" role="alert">
|
||||
<i class="fas fa-check-circle"></i> ${message}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`);
|
||||
$('#stock-filter .container-fluid').prepend(alertDiv);
|
||||
|
||||
// 5秒后自动隐藏提示
|
||||
setTimeout(() => {
|
||||
alertDiv.alert('close');
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// 更新股票筛选表格
|
||||
function updateStockFilterTable(results) {
|
||||
const tbody = $('#stockFilterTable tbody');
|
||||
tbody.empty();
|
||||
|
||||
if (!results || results.length === 0) {
|
||||
tbody.append('<tr><td colspan="9" class="text-center">没有找到满足条件的股票</td></tr>');
|
||||
return;
|
||||
}
|
||||
|
||||
// 按分型强度降序排列
|
||||
results.sort((a, b) => b.fx_strength - a.fx_strength);
|
||||
|
||||
// 添加新数据
|
||||
results.forEach(function(stock) {
|
||||
const strengthClass = getStrengthClass(stock.fx_strength);
|
||||
const changeClass = stock.change_percent >= 0 ? 'text-danger' : 'text-success';
|
||||
const changeSign = stock.change_percent >= 0 ? '+' : '';
|
||||
|
||||
const row = `
|
||||
<tr>
|
||||
<td>${stock.symbol}</td>
|
||||
<td>${stock.name}</td>
|
||||
<td>${stock.fx_time}</td>
|
||||
<td>${stock.fx_type}</td>
|
||||
<td><span class="${strengthClass}">${stock.fx_strength.toFixed(2)}</span></td>
|
||||
<td>${stock.fx_price.toFixed(2)}</td>
|
||||
<td>${stock.current_price ? stock.current_price.toFixed(2) : '-'}</td>
|
||||
<td><span class="${changeClass}">${changeSign}${(stock.change_percent || 0).toFixed(2)}%</span></td>
|
||||
<td><button class="btn btn-sm btn-outline-primary" onclick="analyzeStock('${stock.symbol}')">分析</button></td>
|
||||
</tr>
|
||||
`;
|
||||
tbody.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
// 获取分型强度对应的CSS类
|
||||
function getStrengthClass(strength) {
|
||||
if (strength >= 2.0) return 'text-danger fw-bold';
|
||||
else if (strength >= 1.5) return 'text-warning fw-bold';
|
||||
else if (strength >= 1.0) return 'text-info';
|
||||
else return '';
|
||||
}
|
||||
|
||||
// 分析特定股票
|
||||
function analyzeStock(symbol) {
|
||||
// 切换到主分析页面
|
||||
$('#stock-filter-tab').removeClass('active');
|
||||
$('#kline-tab').addClass('active');
|
||||
$('#stock-filter').removeClass('show active');
|
||||
$('#kline').addClass('show active');
|
||||
|
||||
// 切换数据源为A股
|
||||
$('#dataSource').val('a_stock');
|
||||
$('#dataSource').trigger('change');
|
||||
|
||||
// 等待数据源切换完成后设置股票代码
|
||||
setTimeout(() => {
|
||||
$('#astockSymbol').val(symbol);
|
||||
// 触发分析
|
||||
updateChart();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
// 导出筛选结果
|
||||
function exportFilterResults() {
|
||||
const tbody = $('#stockFilterTable tbody tr');
|
||||
|
||||
if (tbody.length === 0 || (tbody.length === 1 && tbody.find('td').length === 1)) {
|
||||
alert('没有可导出的数据');
|
||||
return;
|
||||
}
|
||||
|
||||
// 创建CSV内容
|
||||
const headers = ['股票代码', '股票名称', '分型时间', '分型类型', '分型强度', '分型价格', '当前价格', '涨跌幅(%)'];
|
||||
let csvContent = headers.join(',') + '\n';
|
||||
|
||||
tbody.each(function() {
|
||||
const cells = $(this).find('td');
|
||||
if (cells.length > 1) { // 排除"没有数据"的行
|
||||
const row = [];
|
||||
cells.slice(0, 8).each(function() { // 只取前8列,排除操作列
|
||||
row.push($(this).text().trim());
|
||||
});
|
||||
csvContent += row.join(',') + '\n';
|
||||
}
|
||||
});
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob(['\ufeff' + csvContent], { type: 'text/csv;charset=utf-8;' });
|
||||
const link = document.createElement('a');
|
||||
const url = URL.createObjectURL(blob);
|
||||
link.setAttribute('href', url);
|
||||
link.setAttribute('download', `股票筛选结果_${new Date().toISOString().slice(0, 10)}.csv`);
|
||||
link.style.visibility = 'hidden';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
$(document).ready(function() {
|
||||
// 设置默认的筛选时间(最近7天)
|
||||
const now = new Date();
|
||||
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
$('#filterEndTime').val(now.toISOString().slice(0, 16));
|
||||
$('#filterStartTime').val(weekAgo.toISOString().slice(0, 16));
|
||||
|
||||
// 初始化股票筛选表格
|
||||
initStockFilterTable();
|
||||
|
||||
// 突出显示股票筛选tab
|
||||
setTimeout(function() {
|
||||
const stockFilterTab = $('#stock-filter-tab');
|
||||
if (stockFilterTab.length > 0) {
|
||||
console.log('股票筛选tab已找到,开始突出显示');
|
||||
|
||||
// 滚动到tab区域
|
||||
$('html, body').animate({
|
||||
scrollTop: $('.data-container').offset().top - 100
|
||||
}, 1000);
|
||||
|
||||
// 添加提示信息
|
||||
const alertDiv = $(`
|
||||
<div class="alert alert-info alert-dismissible fade show" role="alert" style="position: fixed; top: 20px; right: 20px; z-index: 9999; max-width: 400px;">
|
||||
<strong>新功能!</strong> 股票筛选功能已添加,请查看绿色的"股票筛选"标签页。
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
`);
|
||||
$('body').append(alertDiv);
|
||||
|
||||
// 10秒后自动隐藏提示
|
||||
setTimeout(() => {
|
||||
alertDiv.alert('close');
|
||||
}, 10000);
|
||||
|
||||
} else {
|
||||
console.error('未找到股票筛选tab');
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user