Add A share to display
This commit is contained in:
+232
-50
@@ -13,11 +13,13 @@ import time
|
||||
import traceback
|
||||
from pytz import timezone
|
||||
import talib.abstract as ta
|
||||
import numpy as np
|
||||
# 添加父目录到系统路径
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
from ChanLun import ChanLun
|
||||
from ChanEnum import Chan_BI_DIR, Chan_SEG_DIR, Chan_KLC_FX, Chan_FX_TYPE
|
||||
from cn_stock_data import ChinaStockData
|
||||
|
||||
# 添加买卖点枚举类型
|
||||
class TRADE_POINT_TYPE:
|
||||
@@ -35,6 +37,9 @@ exchange = ccxt.binance({
|
||||
'enableRateLimit': True,
|
||||
})
|
||||
|
||||
# 初始化A股数据获取器
|
||||
china_stock = ChinaStockData()
|
||||
|
||||
# 时间周期映射
|
||||
TIMEFRAMES = {
|
||||
'1m': '1分钟',
|
||||
@@ -54,8 +59,32 @@ SYMBOLS = [
|
||||
'ADA/USDT:USDT', 'DOGE/USDT:USDT', 'AVAX/USDT:USDT', 'DOT/USDT:USDT', 'MATIC/USDT:USDT'
|
||||
]
|
||||
|
||||
# A股热门股票
|
||||
A_STOCK_SYMBOLS = china_stock.get_popular_stocks()
|
||||
|
||||
def detect_symbol_type(symbol):
|
||||
"""检测交易对类型:crypto 或 a_stock"""
|
||||
if '/' in symbol and 'USDT' in symbol:
|
||||
return 'crypto'
|
||||
elif len(symbol) == 6 and symbol.isdigit():
|
||||
return 'a_stock'
|
||||
else:
|
||||
return 'unknown'
|
||||
|
||||
def get_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=None):
|
||||
"""获取K线数据,支持分页加载确保获取指定时间范围内的所有数据"""
|
||||
"""获取K线数据,支持加密货币和A股"""
|
||||
symbol_type = detect_symbol_type(symbol)
|
||||
|
||||
if symbol_type == 'crypto':
|
||||
return get_crypto_kl_data(symbol, timeframe, limit, start_time, end_time)
|
||||
elif symbol_type == 'a_stock':
|
||||
return get_a_stock_kl_data(symbol, timeframe, limit, start_time, end_time)
|
||||
else:
|
||||
print(f"未知的交易对类型: {symbol}")
|
||||
return None
|
||||
|
||||
def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=None):
|
||||
"""获取加密货币K线数据,支持分页加载确保获取指定时间范围内的所有数据"""
|
||||
try:
|
||||
# 初始化参数
|
||||
since = None
|
||||
@@ -73,42 +102,73 @@ def get_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=None):
|
||||
except ValueError:
|
||||
print(f"无效的结束时间: {end_time}")
|
||||
|
||||
# 根据时间周期调整每次请求的数据量
|
||||
batch_size = 1000 # 默认批次大小
|
||||
if timeframe in ['1m', '3m', '5m']:
|
||||
batch_size = 500 # 分钟级数据减少批次大小
|
||||
elif timeframe in ['15m', '30m', '1h']:
|
||||
batch_size = 1000
|
||||
else:
|
||||
batch_size = 1500 # 日线及以上可以获取更多
|
||||
|
||||
# 初始化存储所有K线数据的列表
|
||||
all_ohlcv = []
|
||||
|
||||
# 初始化当前查询的开始时间
|
||||
current_since = since
|
||||
|
||||
# 添加请求计数和最大限制
|
||||
request_count = 0
|
||||
max_requests = 50 # 最大请求次数,防止无限循环
|
||||
|
||||
print(f"开始分批获取加密货币数据: {symbol}, {timeframe}")
|
||||
|
||||
# 分页加载数据
|
||||
while True:
|
||||
print(f"获取数据: {symbol}, {timeframe}, limit={limit}, since={current_since}")
|
||||
while request_count < max_requests:
|
||||
request_count += 1
|
||||
|
||||
# 获取当前页的数据
|
||||
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=current_since, limit=limit)
|
||||
print(f"批次 {request_count}: 获取数据 since={current_since}, limit={batch_size}")
|
||||
|
||||
# 如果没有获取到数据,结束循环
|
||||
if not ohlcv or len(ohlcv) == 0:
|
||||
break
|
||||
try:
|
||||
# 获取当前页的数据
|
||||
ohlcv = exchange.fetch_ohlcv(symbol, timeframe, since=current_since, limit=batch_size)
|
||||
|
||||
# 将获取到的数据添加到总列表中
|
||||
all_ohlcv.extend(ohlcv)
|
||||
|
||||
# 获取最后一条数据的时间戳
|
||||
last_timestamp = ohlcv[-1][0]
|
||||
|
||||
# 如果已达到结束时间,结束循环
|
||||
if until and last_timestamp >= until:
|
||||
break
|
||||
# 如果没有获取到数据,结束循环
|
||||
if not ohlcv or len(ohlcv) == 0:
|
||||
print(f"批次 {request_count}: 未获取到数据,结束")
|
||||
break
|
||||
|
||||
# 将获取到的数据添加到总列表中
|
||||
all_ohlcv.extend(ohlcv)
|
||||
print(f"批次 {request_count}: 获取到 {len(ohlcv)} 条记录")
|
||||
|
||||
# 如果获取的数据条数小于限制数,说明已经获取完所有数据
|
||||
if len(ohlcv) < limit:
|
||||
break
|
||||
# 获取最后一条数据的时间戳
|
||||
last_timestamp = ohlcv[-1][0]
|
||||
|
||||
# 更新下一页的开始时间(加1毫秒避免重复)
|
||||
current_since = last_timestamp + 1
|
||||
# 如果已达到结束时间,结束循环
|
||||
if until and last_timestamp >= until:
|
||||
print(f"批次 {request_count}: 已达到结束时间,结束")
|
||||
break
|
||||
|
||||
# 如果获取的数据条数小于限制数,说明已经获取完所有数据
|
||||
if len(ohlcv) < batch_size:
|
||||
print(f"批次 {request_count}: 数据不足批次大小,已获取完所有数据")
|
||||
break
|
||||
|
||||
# 更新下一页的开始时间(加1毫秒避免重复)
|
||||
current_since = last_timestamp + 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"批次 {request_count} 获取失败: {e}")
|
||||
# 如果单个批次失败,继续尝试下一个批次
|
||||
if current_since:
|
||||
# 尝试增加时间跳过可能的问题时间点
|
||||
current_since += 60000 # 跳过1分钟
|
||||
else:
|
||||
break
|
||||
|
||||
# 防止API请求过于频繁
|
||||
time.sleep(0.5) # 等待0.5秒
|
||||
time.sleep(0.3) # 减少到0.3秒提高效率
|
||||
|
||||
# 数据为空的情况
|
||||
if not all_ohlcv or len(all_ohlcv) == 0:
|
||||
@@ -128,33 +188,76 @@ def get_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=None):
|
||||
|
||||
# 按时间排序
|
||||
df = df.sort_values('timestamp')
|
||||
"""
|
||||
chan = ChanLun()
|
||||
klc_list = chan.get_klc_list(df)
|
||||
klc_index = 0
|
||||
df_copy = df.copy()
|
||||
ret_df = pd.DataFrame(columns=['timestamp', 'open', 'high', 'low', 'close', 'volume', 'date'])
|
||||
for index in range(0, len(df)):
|
||||
if klc_index >= len(klc_list):
|
||||
klc_index = len(klc_list) - 1
|
||||
klc = klc_list[klc_index]
|
||||
if index == klc.start_klu.index:
|
||||
ret_df.loc[klc_index] = df_copy.loc[index]
|
||||
klc_index += 1
|
||||
"""
|
||||
|
||||
# 限制数据条数的逻辑 - 优先考虑时间范围
|
||||
if start_time and end_time:
|
||||
# 如果指定了明确的时间范围,返回该时间范围内的所有数据
|
||||
print(f"用户指定了时间范围,返回完整数据 {len(df)} 条记录")
|
||||
if len(df) > 10000: # 防止数据量过大,设置一个合理的上限
|
||||
print(f"警告:数据量过大({len(df)}条),为保证性能将限制为最新的10000条记录")
|
||||
df = df.tail(10000).reset_index(drop=True)
|
||||
elif limit and len(df) > limit:
|
||||
# 如果没有指定明确时间范围,使用默认的limit限制
|
||||
print(f"未指定明确时间范围,应用默认限制,返回最新的 {limit} 条记录")
|
||||
df = df.tail(limit).reset_index(drop=True)
|
||||
|
||||
df = add_indicators(df)
|
||||
|
||||
# 如果过滤后没有数据,返回None
|
||||
if len(df) == 0:
|
||||
print("过滤后无数据")
|
||||
return None
|
||||
|
||||
print(f"获取到总共 {len(df)} 条数据")
|
||||
print(f"成功获取加密货币数据: {len(df)} 条记录 (共 {request_count} 个批次)")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取数据错误: {e}")
|
||||
print(f"获取加密货币数据错误: {e}")
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def get_a_stock_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=None):
|
||||
"""获取A股K线数据"""
|
||||
try:
|
||||
# 处理时间戳参数转换为日期字符串
|
||||
start_date = None
|
||||
end_date = None
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
# 如果用户指定了时间范围,优先获取该范围内的所有数据
|
||||
actual_limit = limit
|
||||
if start_date and end_date:
|
||||
print(f"用户指定了时间范围 {start_date} 到 {end_date},将获取该范围内的所有数据")
|
||||
actual_limit = None # 不限制数据条数,获取完整时间范围数据
|
||||
|
||||
# 调用A股数据获取器
|
||||
df = china_stock.get_kl_data(symbol, timeframe, start_date, end_date, actual_limit)
|
||||
|
||||
if df is None:
|
||||
print(f"未获取到A股数据: {symbol}")
|
||||
return None
|
||||
|
||||
print(f"获取到A股数据: {len(df)} 条记录")
|
||||
return df
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取A股数据错误: {e}")
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def add_indicators(df):
|
||||
fast = 8
|
||||
slow = 16
|
||||
@@ -178,12 +281,17 @@ def add_indicators(df):
|
||||
df['ma250'] = df['ma250'].fillna(0)
|
||||
df['rsi'] = df['rsi'].fillna(0)
|
||||
df['avg_volume'] = df['volume'].rolling(10).mean()
|
||||
# 计算量比
|
||||
# 计算量比,避免产生Infinity值
|
||||
df['volume_ratio'] = df['volume'] / df['avg_volume']
|
||||
# 填充缺失值(前N根K线)
|
||||
df['volume_ratio'] = df['volume_ratio'].fillna(1.0)
|
||||
df['avg_volume'] = df['avg_volume'].fillna(0)
|
||||
|
||||
# 处理Infinity和-Infinity值
|
||||
df['volume_ratio'] = df['volume_ratio'].replace([float('inf'), float('-inf')], 1.0)
|
||||
|
||||
return df
|
||||
|
||||
def calculate_macd(df):
|
||||
"""计算MACD指标"""
|
||||
exp1 = df['close'].ewm(span=12, adjust=False).mean()
|
||||
@@ -287,14 +395,10 @@ def identify_trade_points(bi_list, seg_list, zs_list):
|
||||
|
||||
# 辅助函数,转换缠论方向枚举为整数
|
||||
def convert_direction(direction):
|
||||
"""将缠论方向枚举转换为整数"""
|
||||
if direction == Chan_BI_DIR.UP:
|
||||
"""转换方向枚举为数字"""
|
||||
if direction == Chan_BI_DIR.UP or direction == Chan_SEG_DIR.UP:
|
||||
return 1
|
||||
elif direction == Chan_BI_DIR.DOWN:
|
||||
return -1
|
||||
elif direction == Chan_SEG_DIR.UP:
|
||||
return 1
|
||||
elif direction == Chan_SEG_DIR.DOWN:
|
||||
elif direction == Chan_BI_DIR.DOWN or direction == Chan_SEG_DIR.DOWN:
|
||||
return -1
|
||||
else:
|
||||
return 0
|
||||
@@ -380,10 +484,34 @@ def is_smaller_or_equal_timeframe(tf1, tf2):
|
||||
# 返回tf1是否小于等于tf2
|
||||
return tf1_value <= tf2_value
|
||||
|
||||
def clean_dataframe_for_json(df):
|
||||
"""清理DataFrame中的NaN值,确保JSON序列化正常"""
|
||||
# 创建副本以避免修改原数据
|
||||
df_clean = df.copy()
|
||||
|
||||
# 将NaN、inf、-inf替换为None
|
||||
df_clean = df_clean.replace([np.nan, np.inf, -np.inf], None)
|
||||
|
||||
# 处理数值列,确保值为有限数字或None
|
||||
numeric_columns = df_clean.select_dtypes(include=[np.number]).columns
|
||||
for col in numeric_columns:
|
||||
# 确保所有数值都是有限的
|
||||
df_clean[col] = df_clean[col].apply(lambda x: x if (x is not None and np.isfinite(x)) else None)
|
||||
|
||||
# 处理时间列,确保格式正确
|
||||
datetime_columns = df_clean.select_dtypes(include=['datetime64']).columns
|
||||
for col in datetime_columns:
|
||||
df_clean[col] = df_clean[col].dt.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
return df_clean
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
"""主页"""
|
||||
return render_template('index.html', timeframes=TIMEFRAMES, symbols=SYMBOLS)
|
||||
return render_template('index.html',
|
||||
timeframes=TIMEFRAMES,
|
||||
symbols=SYMBOLS,
|
||||
a_stock_symbols=A_STOCK_SYMBOLS)
|
||||
|
||||
@app.route('/api/analyze')
|
||||
def analyze():
|
||||
@@ -448,7 +576,7 @@ def analyze():
|
||||
|
||||
# 添加主周期分析结果到返回数据
|
||||
result.update({
|
||||
'kline_data': df.to_dict('records'),
|
||||
'kline_data': clean_dataframe_for_json(df).to_dict('records'),
|
||||
'bi_list': [{
|
||||
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
||||
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
|
||||
@@ -522,7 +650,7 @@ def analyze():
|
||||
} for bi in element_analysis['bi_list'] if bi.end_klc]
|
||||
|
||||
# 添加小周期K线数据
|
||||
result['element_kline_data'] = element_df.to_dict('records')
|
||||
result['element_kline_data'] = clean_dataframe_for_json(element_df).to_dict('records')
|
||||
|
||||
result['element_seg_list'] = [{
|
||||
'start_time': seg.start_bi.start_klc.end_time if isinstance(seg.start_bi.start_klc.end_time, str) else seg.start_bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
||||
@@ -584,5 +712,59 @@ def get_symbols():
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
@app.route('/api/a_stocks')
|
||||
def get_a_stocks():
|
||||
"""获取A股股票列表"""
|
||||
try:
|
||||
stock_list = china_stock.get_stock_list()
|
||||
return jsonify(stock_list)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
@app.route('/api/popular_a_stocks')
|
||||
def get_popular_a_stocks():
|
||||
"""获取热门A股股票"""
|
||||
try:
|
||||
return jsonify(china_stock.get_popular_stocks())
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
@app.route('/api/sectors')
|
||||
def get_sectors():
|
||||
"""获取所有行业分类"""
|
||||
try:
|
||||
sectors = china_stock.get_all_sectors()
|
||||
return jsonify(sectors)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
@app.route('/api/stocks_by_sector')
|
||||
def get_stocks_by_sector():
|
||||
"""根据行业获取股票"""
|
||||
try:
|
||||
sector = request.args.get('sector')
|
||||
if sector:
|
||||
stocks = china_stock.get_stock_by_sector(sector)
|
||||
return jsonify(stocks)
|
||||
else:
|
||||
# 返回所有行业的股票分组
|
||||
all_sectors = china_stock.get_stock_by_sector()
|
||||
return jsonify(all_sectors)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
@app.route('/api/search_stock')
|
||||
def search_stock():
|
||||
"""搜索股票 - 增强版"""
|
||||
try:
|
||||
keyword = request.args.get('keyword', '')
|
||||
if not keyword:
|
||||
return jsonify({'error': '搜索关键词不能为空'})
|
||||
|
||||
results = china_stock.search_stock(keyword)
|
||||
return jsonify(results)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)})
|
||||
|
||||
if __name__ == '__main__':
|
||||
app.run(debug=True, host='0.0.0.0', port=8123)
|
||||
Reference in New Issue
Block a user