update
This commit is contained in:
parent
632a6c2049
commit
a05fd69916
@ -55,6 +55,31 @@ services:
|
|||||||
depends_on:
|
depends_on:
|
||||||
- trading-web
|
- trading-web
|
||||||
|
|
||||||
|
# 加密货币扫描定时任务服务
|
||||||
|
trading-crypto-scanner:
|
||||||
|
build: .
|
||||||
|
container_name: trading-ai-crypto-scanner
|
||||||
|
volumes:
|
||||||
|
- ./config:/app/config
|
||||||
|
- ./logs:/app/logs
|
||||||
|
- ./crontab:/app/crontab
|
||||||
|
environment:
|
||||||
|
- PYTHONPATH=/app
|
||||||
|
- TZ=Asia/Shanghai
|
||||||
|
- OPERATION_KEY=${OPERATION_KEY:-9257}
|
||||||
|
- LOG_LEVEL=${LOG_LEVEL:-INFO}
|
||||||
|
# MySQL连接配置
|
||||||
|
- MYSQL_HOST=${MYSQL_HOST:-cd-cynosdbmysql-grp-7kdd8qe4.sql.tencentcdb.com}
|
||||||
|
- MYSQL_PORT=${MYSQL_PORT:-26558}
|
||||||
|
- MYSQL_USER=${MYSQL_USER:-root}
|
||||||
|
- MYSQL_PASSWORD=${MYSQL_PASSWORD:-gUjjmQpu6c7V0hMF}
|
||||||
|
- MYSQL_DATABASE=${MYSQL_DATABASE:-tradingai}
|
||||||
|
# 运行定时加密货币扫描
|
||||||
|
command: ["bash", "/app/start_crypto_scanner.sh"]
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- trading-web
|
||||||
|
|
||||||
# # Nginx反向代理(可选)
|
# # Nginx反向代理(可选)
|
||||||
# nginx:
|
# nginx:
|
||||||
# image: nginx:alpine
|
# image: nginx:alpine
|
||||||
|
|||||||
13
main.py
13
main.py
@ -155,7 +155,7 @@ def main():
|
|||||||
max_stocks = int(parts[2]) if len(parts) > 2 and parts[2].isdigit() else 10
|
max_stocks = int(parts[2]) if len(parts) > 2 and parts[2].isdigit() else 10
|
||||||
execute_task(system['executor'], rule, max_stocks)
|
execute_task(system['executor'], rule, max_stocks)
|
||||||
else:
|
else:
|
||||||
print("请提供股票池规则,如: task tushare_hot 15")
|
print("请提供股票池规则,如: task index_components 100")
|
||||||
|
|
||||||
elif command.lower() == 'schedule':
|
elif command.lower() == 'schedule':
|
||||||
show_schedule_examples(system['scheduler'], system['executor'])
|
show_schedule_examples(system['scheduler'], system['executor'])
|
||||||
@ -206,16 +206,16 @@ def scan_single_stock(strategy, stock_code):
|
|||||||
|
|
||||||
|
|
||||||
def scan_market(executor, max_stocks):
|
def scan_market(executor, max_stocks):
|
||||||
"""扫描市场热门股票"""
|
"""扫描主力股票池"""
|
||||||
print(f"\n🌍 扫描市场热门股票 (前{max_stocks}只)")
|
print(f"\n🎯 扫描主力股票池 (沪深300+中证500+创业板指,最多{max_stocks}只)")
|
||||||
print("-" * 50)
|
print("-" * 50)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = executor.execute_task(
|
result = executor.execute_task(
|
||||||
task_id=f"market_scan_{max_stocks}",
|
task_id=f"market_scan_{max_stocks}",
|
||||||
strategy_id="kline_pattern",
|
strategy_id="kline_pattern",
|
||||||
stock_pool_rule="tushare_hot",
|
stock_pool_rule="index_components", # 使用主力股票池
|
||||||
stock_pool_params={"limit": max_stocks * 2},
|
stock_pool_params={},
|
||||||
max_stocks=max_stocks,
|
max_stocks=max_stocks,
|
||||||
send_notification=False
|
send_notification=False
|
||||||
)
|
)
|
||||||
@ -251,7 +251,8 @@ def show_stock_pools(pool_manager):
|
|||||||
print(f" 🎯 {rule_id}: {rule_name}")
|
print(f" 🎯 {rule_id}: {rule_name}")
|
||||||
|
|
||||||
print(f"\n💡 使用方法: task <规则名> <股票数量>")
|
print(f"\n💡 使用方法: task <规则名> <股票数量>")
|
||||||
print(f" 示例: task tushare_hot 20")
|
print(f" 示例: task index_components 100 # 主力股票池")
|
||||||
|
print(f" 示例: task tushare_hot 50 # 同花顺热榜")
|
||||||
|
|
||||||
|
|
||||||
def execute_task(executor, rule, max_stocks):
|
def execute_task(executor, rule, max_stocks):
|
||||||
|
|||||||
@ -95,21 +95,21 @@ def create_strategy_system():
|
|||||||
|
|
||||||
|
|
||||||
def scan_market():
|
def scan_market():
|
||||||
"""执行市场扫描 - 分析所有同花顺热榜股票"""
|
"""执行市场扫描 - 分析主力股票池(沪深300+中证500+创业板指)"""
|
||||||
logger.info(f"🚀 开始市场扫描任务 - 扫描全部同花顺热榜股票")
|
logger.info(f"🚀 开始市场扫描任务 - 扫描主力股票池(沪深300+中证500+创业板指)")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# 初始化系统
|
# 初始化系统
|
||||||
executor = create_strategy_system()
|
executor = create_strategy_system()
|
||||||
|
|
||||||
# 执行扫描任务 - 不限制数量,分析所有股票
|
# 执行扫描任务 - 使用主力股票池(沪深300+中证500+创业板指)
|
||||||
task_id = f"market_scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
task_id = f"market_scan_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
||||||
result = executor.execute_task(
|
result = executor.execute_task(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
strategy_id="kline_pattern",
|
strategy_id="kline_pattern",
|
||||||
stock_pool_rule="tushare_hot",
|
stock_pool_rule="index_components", # 使用主力股票池
|
||||||
stock_pool_params={}, # 不限制数量
|
stock_pool_params={}, # 使用默认指数组合
|
||||||
max_stocks=None, # 取消数量限制
|
max_stocks=None, # 分析所有成分股
|
||||||
send_notification=True # 启用通知
|
send_notification=True # 启用通知
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -125,6 +125,61 @@ class CustomStockListRule(StockPoolRule):
|
|||||||
return "自定义股票列表"
|
return "自定义股票列表"
|
||||||
|
|
||||||
|
|
||||||
|
class IndexComponentsRule(StockPoolRule):
|
||||||
|
"""指数成分股规则(主力股票池)"""
|
||||||
|
|
||||||
|
def get_stocks(self, fetcher: TushareFetcher, index_codes: List[str] = None, **kwargs) -> List[str]:
|
||||||
|
"""
|
||||||
|
获取指数成分股
|
||||||
|
|
||||||
|
Args:
|
||||||
|
fetcher: 数据获取器
|
||||||
|
index_codes: 指数代码列表,默认使用主力股票池
|
||||||
|
**kwargs: 其他参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
股票代码列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if index_codes is None:
|
||||||
|
# 默认使用主力股票池:沪深300 + 中证500 + 创业板指
|
||||||
|
index_codes = ['000300.SH', '000905.SH', '399006.SZ']
|
||||||
|
|
||||||
|
logger.info(f"🎯 获取主力股票池: {index_codes}")
|
||||||
|
|
||||||
|
# 获取指数成分股数据
|
||||||
|
components_df = fetcher.get_index_components(index_codes=index_codes, merge=True)
|
||||||
|
|
||||||
|
if not components_df.empty and 'stock_code' in components_df.columns:
|
||||||
|
stocks = components_df['stock_code'].tolist()
|
||||||
|
|
||||||
|
# 统计每个指数的成分股数量
|
||||||
|
if 'index_name' in components_df.columns:
|
||||||
|
index_stats = components_df.groupby('index_name').size().to_dict()
|
||||||
|
stats_str = ", ".join([f"{name}:{count}只" for name, count in index_stats.items()])
|
||||||
|
logger.info(f"✅ 主力股票池构建成功: 总计{len(stocks)}只股票 ({stats_str})")
|
||||||
|
else:
|
||||||
|
logger.info(f"✅ 主力股票池构建成功: {len(stocks)}只股票")
|
||||||
|
|
||||||
|
# 去重并返回
|
||||||
|
unique_stocks = list(dict.fromkeys(stocks)) # 保持顺序的去重
|
||||||
|
|
||||||
|
if len(unique_stocks) != len(stocks):
|
||||||
|
logger.info(f"🔄 去重后: {len(unique_stocks)}只股票")
|
||||||
|
|
||||||
|
return unique_stocks
|
||||||
|
else:
|
||||||
|
logger.error("指数成分股数据为空")
|
||||||
|
return []
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取指数成分股失败: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def get_rule_name(self) -> str:
|
||||||
|
return "主力股票池(沪深300+中证500+创业板指)"
|
||||||
|
|
||||||
|
|
||||||
class StockPoolManager:
|
class StockPoolManager:
|
||||||
"""股票池管理器"""
|
"""股票池管理器"""
|
||||||
|
|
||||||
@ -144,6 +199,7 @@ class StockPoolManager:
|
|||||||
self.register_rule("tushare_hot", TushareHotStocksRule())
|
self.register_rule("tushare_hot", TushareHotStocksRule())
|
||||||
self.register_rule("combined_hot", CombinedHotStocksRule())
|
self.register_rule("combined_hot", CombinedHotStocksRule())
|
||||||
self.register_rule("leading_stocks", LeadingStocksRule())
|
self.register_rule("leading_stocks", LeadingStocksRule())
|
||||||
|
self.register_rule("index_components", IndexComponentsRule()) # 新增主力股票池
|
||||||
|
|
||||||
def register_rule(self, rule_name: str, rule: StockPoolRule):
|
def register_rule(self, rule_name: str, rule: StockPoolRule):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -976,6 +976,167 @@ class TushareFetcher:
|
|||||||
logger.warning(f"使用默认股票池,包含{len(df)}只股票")
|
logger.warning(f"使用默认股票池,包含{len(df)}只股票")
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
def get_index_components(self, index_codes: List[str] = None, merge: bool = True) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
获取指数成分股
|
||||||
|
|
||||||
|
Args:
|
||||||
|
index_codes: 指数代码列表,默认['000300.SH', '000905.SH', '399006.SZ']
|
||||||
|
merge: 是否合并多个指数的成分股
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
指数成分股DataFrame,包含stock_code, stock_name, index_code等字段
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if index_codes is None:
|
||||||
|
# 默认使用沪深300 + 中证500 + 创业板指
|
||||||
|
index_codes = [
|
||||||
|
'000300.SH', # 沪深300
|
||||||
|
'000905.SH', # 中证500
|
||||||
|
'399006.SZ' # 创业板指
|
||||||
|
]
|
||||||
|
|
||||||
|
logger.info(f"📊 获取指数成分股: {index_codes}")
|
||||||
|
|
||||||
|
all_components = []
|
||||||
|
|
||||||
|
for index_code in index_codes:
|
||||||
|
try:
|
||||||
|
if self.pro:
|
||||||
|
# 获取指数成分股
|
||||||
|
components = self.pro.index_weight(
|
||||||
|
index_code=index_code,
|
||||||
|
trade_date=None # 获取最新的成分股
|
||||||
|
)
|
||||||
|
|
||||||
|
if not components.empty:
|
||||||
|
# 添加指数标识
|
||||||
|
components['index_code'] = index_code
|
||||||
|
components['index_name'] = self._get_index_name(index_code)
|
||||||
|
all_components.append(components)
|
||||||
|
logger.info(f"✅ {index_code} 成分股: {len(components)}只")
|
||||||
|
else:
|
||||||
|
logger.warning(f"⚠️ {index_code} 成分股数据为空")
|
||||||
|
else:
|
||||||
|
logger.error("获取指数成分股需要TuShare Pro权限")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取{index_code}成分股失败: {e}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not all_components:
|
||||||
|
logger.error("所有指数成分股获取失败,使用备用股票池")
|
||||||
|
return self._get_fallback_index_components()
|
||||||
|
|
||||||
|
# 合并所有指数成分股
|
||||||
|
if merge:
|
||||||
|
combined_df = pd.concat(all_components, ignore_index=True)
|
||||||
|
|
||||||
|
# 去重(同一只股票可能在多个指数中)
|
||||||
|
unique_stocks = combined_df.drop_duplicates(subset=['con_code'], keep='first')
|
||||||
|
|
||||||
|
# 统一字段名
|
||||||
|
result = unique_stocks.rename(columns={
|
||||||
|
'con_code': 'stock_code',
|
||||||
|
'con_name': 'stock_name'
|
||||||
|
}).copy()
|
||||||
|
|
||||||
|
logger.info(f"🎯 主力股票池构建完成: 总计{len(result)}只股票")
|
||||||
|
|
||||||
|
else:
|
||||||
|
result = all_components
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取指数成分股失败: {e}")
|
||||||
|
return self._get_fallback_index_components()
|
||||||
|
|
||||||
|
def _get_index_name(self, index_code: str) -> str:
|
||||||
|
"""获取指数名称"""
|
||||||
|
index_names = {
|
||||||
|
'000300.SH': '沪深300',
|
||||||
|
'000905.SH': '中证500',
|
||||||
|
'399006.SZ': '创业板指',
|
||||||
|
'000852.SH': '中证1000',
|
||||||
|
'399001.SZ': '深证成指',
|
||||||
|
'000001.SH': '上证指数',
|
||||||
|
'000688.SH': '科创50'
|
||||||
|
}
|
||||||
|
return index_names.get(index_code, index_code)
|
||||||
|
|
||||||
|
def _get_fallback_index_components(self) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
备用指数成分股(当主接口失败时使用)
|
||||||
|
包含主要的沪深300、中证500、创业板指代表性股票
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 构建一个包含各行业龙头的股票池
|
||||||
|
fallback_stocks = [
|
||||||
|
# 沪深300权重股
|
||||||
|
{'stock_code': '600519.SH', 'stock_name': '贵州茅台', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '000858.SZ', 'stock_name': '五粮液', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '601318.SH', 'stock_name': '中国平安', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '600036.SH', 'stock_name': '招商银行', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '000001.SZ', 'stock_name': '平安银行', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '002415.SZ', 'stock_name': '海康威视', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '600887.SH', 'stock_name': '伊利股份', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '000002.SZ', 'stock_name': '万科A', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '600276.SH', 'stock_name': '恒瑞医药', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '601166.SH', 'stock_name': '兴业银行', 'index_code': '000300.SH'},
|
||||||
|
|
||||||
|
# 中证500代表股
|
||||||
|
{'stock_code': '002594.SZ', 'stock_name': 'BYD', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '000876.SZ', 'stock_name': '新希望', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '002304.SZ', 'stock_name': '洋河股份', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '000063.SZ', 'stock_name': '中兴通讯', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '600031.SH', 'stock_name': '三一重工', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '000895.SZ', 'stock_name': '双汇发展', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '600009.SH', 'stock_name': '上海机场', 'index_code': '000905.SH'},
|
||||||
|
|
||||||
|
# 创业板指代表股
|
||||||
|
{'stock_code': '300750.SZ', 'stock_name': '宁德时代', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '300059.SZ', 'stock_name': '东方财富', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '300014.SZ', 'stock_name': '亿纬锂能', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '300015.SZ', 'stock_name': '爱尔眼科', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '300142.SZ', 'stock_name': '沃森生物', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '300274.SZ', 'stock_name': '阳光电源', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '300316.SZ', 'stock_name': '晶盛机电', 'index_code': '399006.SZ'},
|
||||||
|
]
|
||||||
|
|
||||||
|
# 添加更多各行业代表股
|
||||||
|
additional_stocks = [
|
||||||
|
# 科技股
|
||||||
|
{'stock_code': '600570.SH', 'stock_name': '恒生电子', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '002230.SZ', 'stock_name': '科大讯飞', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '300433.SZ', 'stock_name': '蓝思科技', 'index_code': '399006.SZ'},
|
||||||
|
|
||||||
|
# 新能源
|
||||||
|
{'stock_code': '002129.SZ', 'stock_name': '中环股份', 'index_code': '000905.SH'},
|
||||||
|
{'stock_code': '300207.SZ', 'stock_name': '欣旺达', 'index_code': '399006.SZ'},
|
||||||
|
|
||||||
|
# 医药
|
||||||
|
{'stock_code': '300760.SZ', 'stock_name': '迈瑞医疗', 'index_code': '399006.SZ'},
|
||||||
|
{'stock_code': '000661.SZ', 'stock_name': '长春高新', 'index_code': '000300.SH'},
|
||||||
|
|
||||||
|
# 消费
|
||||||
|
{'stock_code': '603288.SH', 'stock_name': '海天味业', 'index_code': '000300.SH'},
|
||||||
|
{'stock_code': '002311.SZ', 'stock_name': '海大集团', 'index_code': '000905.SH'},
|
||||||
|
]
|
||||||
|
|
||||||
|
fallback_stocks.extend(additional_stocks)
|
||||||
|
|
||||||
|
df = pd.DataFrame(fallback_stocks)
|
||||||
|
df['index_name'] = df['index_code'].apply(self._get_index_name)
|
||||||
|
df['source'] = '备用指数成分股'
|
||||||
|
|
||||||
|
logger.warning(f"使用备用指数成分股,包含{len(df)}只股票")
|
||||||
|
return df
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"构建备用指数成分股失败: {e}")
|
||||||
|
return pd.DataFrame()
|
||||||
|
|
||||||
def get_market_overview(self) -> dict:
|
def get_market_overview(self) -> dict:
|
||||||
"""
|
"""
|
||||||
获取市场概况
|
获取市场概况
|
||||||
|
|||||||
@ -268,8 +268,8 @@ class CryptoKLinePatternStrategy(KLinePatternStrategy):
|
|||||||
# 检测形态(返回已确认信号和形态形成)
|
# 检测形态(返回已确认信号和形态形成)
|
||||||
confirmed_signals, formed_patterns = self.detect_pattern(df_with_features)
|
confirmed_signals, formed_patterns = self.detect_pattern(df_with_features)
|
||||||
|
|
||||||
# 过滤一周内的信号和形态
|
# 过滤3天内的信号,7天内的形态
|
||||||
recent_signals = self._filter_recent_signals(confirmed_signals, days=7)
|
recent_signals = self._filter_recent_signals(confirmed_signals, days=3)
|
||||||
recent_patterns = self._filter_recent_signals(formed_patterns, days=7)
|
recent_patterns = self._filter_recent_signals(formed_patterns, days=7)
|
||||||
|
|
||||||
# 处理确认信号格式
|
# 处理确认信号格式
|
||||||
|
|||||||
@ -410,9 +410,9 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
检测潜在的K线形态(等待回踩确认)
|
检测潜在的K线形态(等待回踩确认)
|
||||||
|
|
||||||
新形态设计:强势上涨 → 多空博弈 → 突破确认
|
新形态设计:强势上涨 → 多空博弈 → 突破确认
|
||||||
形态A:2根连续阳线 + 博弈K线(阴线/十字星) + 突破阳线
|
形态A:2根连续阳线 + 1根阴线 + 突破阳线
|
||||||
形态B:1根高实体阳线(实体≥60%) + 博弈K线(阴线/十字星) + 突破阳线
|
形态B:1根高实体阳线(实体≥60%) + 1根博弈K线(实体≤40%) + 突破阳线
|
||||||
|
|
||||||
重要约束:全程价格不能跌破EMA10支撑
|
重要约束:全程价格不能跌破EMA10支撑
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -427,74 +427,57 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
if df.empty or len(df) < 3:
|
if df.empty or len(df) < 3:
|
||||||
return potential_patterns
|
return potential_patterns
|
||||||
|
|
||||||
# 从第4个数据点开始检测(需要至少5根K线:强势2根+博弈2根+突破1根)
|
# 从第3个数据点开始检测(需要至少4根K线:强势2根+博弈1根+突破1根)
|
||||||
for i in range(3, len(df)):
|
for i in range(2, len(df)):
|
||||||
# 检查是否有足够的K线进行突破确认
|
# 检查是否有足够的K线进行突破确认
|
||||||
if i >= len(df) - 3:
|
if i >= len(df) - 3:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
pattern_type = None
|
pattern_type = None
|
||||||
base_klines = []
|
base_klines = []
|
||||||
battle_klines = []
|
battle_kline = None
|
||||||
battle_period_high = 0
|
battle_period_high = 0
|
||||||
|
|
||||||
# 形态A:2根连续阳线 + 2-3根博弈K线
|
# 形态A:2根连续阳线 + 1根阴线
|
||||||
if i >= 4: # 至少需要5根历史K线
|
if i >= 2: # 至少需要3根历史K线(索引0,1,2)
|
||||||
# 检查强势阶段:前2根是否为连续阳线
|
# 检查强势阶段:前2根是否为连续阳线
|
||||||
strong_start = i - 3 # 强势阶段开始位置
|
k1 = df.iloc[i-2].to_dict()
|
||||||
k1, k2 = df.iloc[strong_start:strong_start+2].to_dict('records')
|
k2 = df.iloc[i-1].to_dict()
|
||||||
|
|
||||||
if k1['is_yang'] and k2['is_yang']:
|
|
||||||
# 检查博弈阶段:接下来的2根K线
|
|
||||||
battle_start = strong_start + 2
|
|
||||||
battle_klines_data = df.iloc[battle_start:i+1]
|
|
||||||
|
|
||||||
# 博弈阶段条件:平均实体比例≤40%,没有明显方向性
|
|
||||||
battle_entity_ratios = battle_klines_data['entity_ratio'].tolist()
|
|
||||||
avg_battle_entity = sum(battle_entity_ratios) / len(battle_entity_ratios)
|
|
||||||
|
|
||||||
# 检查博弈阶段是否跌破EMA10
|
|
||||||
battle_min_low = battle_klines_data['low'].min()
|
|
||||||
battle_ema10_values = battle_klines_data['ema10'].dropna()
|
|
||||||
if len(battle_ema10_values) > 0:
|
|
||||||
battle_min_ema10 = battle_ema10_values.min()
|
|
||||||
if battle_min_low < battle_min_ema10:
|
|
||||||
continue # 博弈阶段跌破EMA10,跳过此形态
|
|
||||||
|
|
||||||
if avg_battle_entity <= 0.4: # 博弈阶段整体实体较小
|
|
||||||
pattern_type = '强势形态A(双阳+博弈)'
|
|
||||||
base_klines = [k1, k2]
|
|
||||||
battle_klines = battle_klines_data.to_dict('records')
|
|
||||||
battle_period_high = battle_klines_data['high'].max()
|
|
||||||
|
|
||||||
# 形态B:1根高实体阳线 + 2-3根博弈K线
|
if k1['is_yang'] and k2['is_yang']:
|
||||||
if pattern_type is None and i >= 3: # 至少需要4根历史K线
|
# 检查博弈阶段:当前K线(索引i)必须是阴线
|
||||||
|
k_battle = df.iloc[i]
|
||||||
|
|
||||||
|
# 博弈K线条件:必须是阴线
|
||||||
|
if k_battle['is_yin']:
|
||||||
|
# 检查博弈阴线是否跌破EMA10
|
||||||
|
if pd.notna(k_battle.get('ema10')) and k_battle['low'] < k_battle.get('ema10', 0):
|
||||||
|
pass # 博弈阴线跌破EMA10,跳过形态A,继续尝试形态B
|
||||||
|
else:
|
||||||
|
pattern_type = '强势形态A(双阳+阴线)'
|
||||||
|
base_klines = [k1, k2]
|
||||||
|
battle_kline = k_battle.to_dict() if hasattr(k_battle, 'to_dict') else dict(k_battle)
|
||||||
|
battle_period_high = k_battle['high']
|
||||||
|
|
||||||
|
# 形态B:1根高实体阳线 + 1根博弈K线
|
||||||
|
if pattern_type is None and i >= 1: # 至少需要2根历史K线(索引0,1)
|
||||||
# 检查强势阶段:前1根是否为高实体阳线
|
# 检查强势阶段:前1根是否为高实体阳线
|
||||||
strong_start = i - 2 # 强势阶段开始位置
|
k1 = df.iloc[i-1].to_dict()
|
||||||
k1 = df.iloc[strong_start].to_dict()
|
|
||||||
|
|
||||||
if k1['is_yang'] and k1['entity_ratio'] >= 0.6:
|
if k1['is_yang'] and k1['entity_ratio'] >= 0.6:
|
||||||
# 检查博弈阶段:接下来的2根K线
|
# 检查博弈阶段:当前K线(索引i)
|
||||||
battle_start = strong_start + 1
|
k_battle = df.iloc[i]
|
||||||
battle_klines_data = df.iloc[battle_start:i+1]
|
|
||||||
|
# 博弈K线条件:实体比例≤40%
|
||||||
# 博弈阶段条件:平均实体比例≤40%
|
if k_battle['entity_ratio'] <= 0.4:
|
||||||
battle_entity_ratios = battle_klines_data['entity_ratio'].tolist()
|
# 检查博弈K线是否跌破EMA10
|
||||||
avg_battle_entity = sum(battle_entity_ratios) / len(battle_entity_ratios)
|
if pd.notna(k_battle.get('ema10')) and k_battle['low'] < k_battle.get('ema10', 0):
|
||||||
|
pass # 博弈K线跌破EMA10,跳过此形态
|
||||||
# 检查博弈阶段是否跌破EMA10
|
else:
|
||||||
battle_min_low = battle_klines_data['low'].min()
|
pattern_type = '强势形态B(高实体+博弈)'
|
||||||
battle_ema10_values = battle_klines_data['ema10'].dropna()
|
base_klines = [k1]
|
||||||
if len(battle_ema10_values) > 0:
|
battle_kline = k_battle.to_dict() if hasattr(k_battle, 'to_dict') else dict(k_battle)
|
||||||
battle_min_ema10 = battle_ema10_values.min()
|
battle_period_high = k_battle['high']
|
||||||
if battle_min_low < battle_min_ema10:
|
|
||||||
continue # 博弈阶段跌破EMA10,跳过此形态
|
|
||||||
|
|
||||||
if avg_battle_entity <= 0.4: # 博弈阶段整体实体较小
|
|
||||||
pattern_type = '强势形态B(高实体+博弈)'
|
|
||||||
base_klines = [k1]
|
|
||||||
battle_klines = battle_klines_data.to_dict('records')
|
|
||||||
battle_period_high = battle_klines_data['high'].max()
|
|
||||||
|
|
||||||
# 如果没有匹配的基础形态,继续下一次循环
|
# 如果没有匹配的基础形态,继续下一次循环
|
||||||
if pattern_type is None:
|
if pattern_type is None:
|
||||||
@ -511,14 +494,14 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
breakout_idx = i + breakout_offset
|
breakout_idx = i + breakout_offset
|
||||||
if breakout_idx >= len(df):
|
if breakout_idx >= len(df):
|
||||||
break # 超出数据范围
|
break # 超出数据范围
|
||||||
|
|
||||||
k_breakout = df.iloc[breakout_idx]
|
k_breakout = df.iloc[breakout_idx]
|
||||||
|
|
||||||
# 突破K线必须是大阳线(实体比例≥55%)
|
# 突破K线必须是大阳线(实体比例≥55%)
|
||||||
if not k_breakout['is_yang'] or k_breakout['entity_ratio'] < 0.55:
|
if not k_breakout['is_yang'] or k_breakout['entity_ratio'] < 0.55:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 检查是否突破博弈阶段最高价(收盘价>最高价)
|
# 检查是否突破博弈K线最高价(收盘价>最高价)
|
||||||
if k_breakout['close'] <= battle_period_high:
|
if k_breakout['close'] <= battle_period_high:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@ -559,13 +542,13 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
'pattern_type': f'{pattern_type}+突破(待确认)',
|
'pattern_type': f'{pattern_type}+突破(待确认)',
|
||||||
'pattern_subtype': pattern_type, # 记录形态子类型
|
'pattern_subtype': pattern_type, # 记录形态子类型
|
||||||
'base_klines': base_klines, # 强势上涨K线
|
'base_klines': base_klines, # 强势上涨K线
|
||||||
'battle_klines': battle_klines, # 博弈阶段K线(2-3根)
|
'battle_klines': [battle_kline], # 博弈K线(1根)
|
||||||
'breakout_kline': k_breakout.to_dict() if hasattr(k_breakout, 'to_dict') else dict(k_breakout), # 突破阳线
|
'breakout_kline': k_breakout.to_dict() if hasattr(k_breakout, 'to_dict') else dict(k_breakout), # 突破阳线
|
||||||
'breakout_position': breakout_offset, # 博弈后第几根K线突破(1-3)
|
'breakout_position': breakout_offset, # 博弈后第几根K线突破(1-3)
|
||||||
'final_yang_entity_ratio': k_breakout['entity_ratio'],
|
'final_yang_entity_ratio': k_breakout['entity_ratio'],
|
||||||
'breakout_price': k_breakout['close'],
|
'breakout_price': k_breakout['close'],
|
||||||
'reference_high': battle_period_high, # 博弈阶段最高价
|
'reference_high': battle_period_high, # 博弈K线最高价
|
||||||
'yin_high': battle_period_high, # 保持兼容性,使用博弈阶段最高价
|
'yin_high': battle_period_high, # 保持兼容性,使用博弈K线最高价
|
||||||
'breakout_amount': k_breakout['close'] - battle_period_high,
|
'breakout_amount': k_breakout['close'] - battle_period_high,
|
||||||
'breakout_pct': (k_breakout['close'] - battle_period_high) / battle_period_high * 100 if battle_period_high > 0 else 0,
|
'breakout_pct': (k_breakout['close'] - battle_period_high) / battle_period_high * 100 if battle_period_high > 0 else 0,
|
||||||
'ema20_price': k_breakout.get('ema20', 0),
|
'ema20_price': k_breakout.get('ema20', 0),
|
||||||
@ -573,8 +556,8 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
'turnover_ratio': turnover_ratio,
|
'turnover_ratio': turnover_ratio,
|
||||||
'confirmation_pending': True, # 标记为待确认
|
'confirmation_pending': True, # 标记为待确认
|
||||||
'pattern_trigger_date': breakout_date,
|
'pattern_trigger_date': breakout_date,
|
||||||
'battle_period_days': len(battle_klines), # 博弈阶段天数
|
'battle_period_days': 1, # 博弈阶段固定为1天
|
||||||
'avg_battle_entity': avg_battle_entity # 博弈阶段平均实体比例
|
'avg_battle_entity': battle_kline['entity_ratio'] # 博弈K线实体比例
|
||||||
}
|
}
|
||||||
|
|
||||||
potential_patterns.append(potential_pattern)
|
potential_patterns.append(potential_pattern)
|
||||||
@ -586,12 +569,12 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
logger.debug(f"🏷️ 形态类型: {pattern_type}")
|
logger.debug(f"🏷️ 形态类型: {pattern_type}")
|
||||||
logger.debug(f"📅 模式时间: {potential_pattern['date']}")
|
logger.debug(f"📅 模式时间: {potential_pattern['date']}")
|
||||||
logger.debug(f"💰 突破价格: {potential_pattern['breakout_price']:.2f}元")
|
logger.debug(f"💰 突破价格: {potential_pattern['breakout_price']:.2f}元")
|
||||||
logger.debug(f"🎯 博弈阶段最高价: {battle_period_high:.2f}元")
|
logger.debug(f"🎯 博弈K线最高价: {battle_period_high:.2f}元")
|
||||||
logger.debug(f"📊 博弈阶段: {len(battle_klines)}根K线,平均实体{avg_battle_entity:.1%}")
|
logger.debug(f"📊 博弈K线: 实体比例{battle_kline['entity_ratio']:.1%}")
|
||||||
logger.debug(f"📍 突破位置: 博弈后第{breakout_offset}根K线")
|
logger.debug(f"📍 突破位置: 博弈后第{breakout_offset}根K线")
|
||||||
logger.debug(f"🛡️ EMA10支撑: 全程保持在EMA10上方")
|
logger.debug(f"🛡️ EMA10支撑: 全程保持在EMA10上方")
|
||||||
logger.debug(f"⏰ 观察窗口: {self.pullback_confirmation_days}天内")
|
logger.debug(f"⏰ 观察窗口: {self.pullback_confirmation_days}天内")
|
||||||
logger.debug(f"📋 要求: 先创新高再回踩博弈阶段最高价才产生信号")
|
logger.debug(f"📋 要求: 先创新高再回踩博弈K线最高价才产生信号")
|
||||||
logger.debug("🔍" + "="*60)
|
logger.debug("🔍" + "="*60)
|
||||||
|
|
||||||
return potential_patterns
|
return potential_patterns
|
||||||
@ -1055,8 +1038,8 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
# 传入stock_code以支持1h级别回踩确认
|
# 传入stock_code以支持1h级别回踩确认
|
||||||
confirmed_signals, formed_patterns = self.detect_pattern(df_with_features, stock_code)
|
confirmed_signals, formed_patterns = self.detect_pattern(df_with_features, stock_code)
|
||||||
|
|
||||||
# 过滤一周内的信号和形态
|
# 过滤3天内的信号,7天内的形态
|
||||||
recent_signals = self._filter_recent_signals(confirmed_signals, days=7)
|
recent_signals = self._filter_recent_signals(confirmed_signals, days=3)
|
||||||
recent_patterns = self._filter_recent_signals(formed_patterns, days=7)
|
recent_patterns = self._filter_recent_signals(formed_patterns, days=7)
|
||||||
|
|
||||||
# 处理确认信号格式
|
# 处理确认信号格式
|
||||||
@ -1196,23 +1179,24 @@ class KLinePatternStrategy(BaseStrategy):
|
|||||||
{trend_desc}
|
{trend_desc}
|
||||||
新形态设计:强势上涨 → 多空博弈 → 突破确认
|
新形态设计:强势上涨 → 多空博弈 → 突破确认
|
||||||
|
|
||||||
【形态A:双阳+博弈】
|
【形态A:双阳+阴线】
|
||||||
1. 强势阶段:2根连续阳线
|
1. 强势阶段:2根连续阳线(不限制实体长短)
|
||||||
2. 博弈阶段:2-3根K线,平均实体≤40%(阴线/十字星/小阳线)
|
2. 博弈阶段:1根阴K线(阴线记录最高价)
|
||||||
3. 突破确认:博弈后1-3根K线内,大阳线(实体≥55%)突破博弈阶段最高价
|
3. 突破确认:阴线后1-3根K线内,大阳线(实体≥55%)突破阴K线最高价
|
||||||
|
|
||||||
【形态B:高实体+博弈】
|
【形态B:高实体+博弈】
|
||||||
1. 强势阶段:1根高实体阳线(实体≥60%)
|
1. 强势阶段:1根高实体阳线(实体≥60%)
|
||||||
2. 博弈阶段:2-3根K线,平均实体≤40%(阴线/十字星/小阳线)
|
2. 博弈阶段:1根K线,实体≤40%(阴线/十字星/小阳线)
|
||||||
3. 突破确认:博弈后1-3根K线内,大阳线(实体≥55%)突破博弈阶段最高价
|
3. 突破确认:博弈后1-3根K线内,大阳线(实体≥55%)突破博弈K线最高价
|
||||||
|
|
||||||
【严格约束条件】
|
【严格约束条件】
|
||||||
- EMA5 > EMA10 > EMA20(三重多头排列)全程检查
|
- EMA5 > EMA10 > EMA20(三重多头排列)全程检查
|
||||||
- 价格不能跌破EMA10支撑(博弈、突破、回踩全程)
|
- 价格不能跌破EMA10支撑(博弈、突破、回踩全程)
|
||||||
- 价格必须创新高后回踩到博弈阶段最高点附近才产生正式信号
|
- 价格必须创新高后回踩到博弈K线最高点附近才产生正式信号
|
||||||
- 回踩容忍度:{self.pullback_tolerance:.0%}
|
- 回踩容忍度:{self.pullback_tolerance:.0%}
|
||||||
- 确认窗口:{self.pullback_confirmation_days}天
|
- 确认窗口:{self.pullback_confirmation_days}天
|
||||||
- 当前价格过滤:自动过滤价格已跌破入场价5%以上的信号
|
- 当前价格过滤:自动过滤价格已跌破入场价5%以上的信号
|
||||||
|
- 时间过滤:信号保留3天内,形态保留7天内
|
||||||
|
|
||||||
【策略优势】
|
【策略优势】
|
||||||
- 真实市场心理:强势→博弈→突破的完整过程
|
- 真实市场心理:强势→博弈→突破的完整过程
|
||||||
@ -1565,64 +1549,71 @@ K线形态策略 - 双形态识别(多头排列+创新高回踩确认)
|
|||||||
|
|
||||||
【阶段0 - 多头排列筛选】{trend_summary}
|
【阶段0 - 多头排列筛选】{trend_summary}
|
||||||
|
|
||||||
【阶段1 - 模式识别(不产生信号)】
|
【阶段1 - 形态识别(不产生信号)】
|
||||||
|
|
||||||
形态1:阳+阳+阴+突破
|
形态A:双阳+阴线+突破
|
||||||
1. 识别基础形态(前3根K线):阳线 + 阳线 + 阴线
|
1. 强势阶段:2根连续阳线(不限制实体长短)
|
||||||
2. 不限制阳线实体比例(任何阳线都可以)
|
2. 博弈阶段:1根阴K线(阴线记录最高价)
|
||||||
3. 在第4/5/6根K线中寻找阳线突破阴线最高价
|
3. 突破确认:阴线后1-3根K线内,大阳线(实体≥55%)突破阴K线最高价
|
||||||
|
|
||||||
形态2:大阳+小实体+突破
|
形态B:高实体+博弈+突破
|
||||||
1. 第1根K线:大阳线(实体>55%)
|
1. 强势阶段:1根高实体阳线(实体≥60%)
|
||||||
2. 第2根K线:小实体(实体<45%,阴阳不限)
|
2. 博弈阶段:1根K线,实体≤40%(阴线/十字星/小阳线)
|
||||||
3. 在第4/5/6根K线中寻找大阳线(实体>55%)突破第2根K线最高价
|
3. 突破确认:博弈后1-3根K线内,大阳线(实体≥55%)突破博弈K线最高价
|
||||||
|
|
||||||
※ 此时仅记录模式,不产生交易信号
|
※ 此时仅记录形态,不产生交易信号
|
||||||
|
|
||||||
【阶段1 - 创新高回踩确认(产生信号)】
|
【阶段2 - 创新高回踩确认(产生信号)】
|
||||||
4. 价格必须创新高(高于突破阳线价格)
|
4. 价格必须创新高(高于突破阳线价格)
|
||||||
5. 然后回踩到参考K线最高点附近(容忍度:{self.pullback_tolerance:.0%})
|
5. 然后回踩到参考K线最高点附近(形态A回踩阴线最高价,形态B回踩博弈K线最高价)
|
||||||
6. 确认窗口:模式识别后 {self.pullback_confirmation_days} 个交易日内
|
6. 回踩容忍度:{self.pullback_tolerance:.0%}
|
||||||
7. 只有完成"创新高+回踩参考价"才产生正式交易信号
|
7. 确认窗口:形态识别后 {self.pullback_confirmation_days} 个交易日内
|
||||||
|
8. 只有完成"创新高+回踩参考K线最高价"才产生正式交易信号
|
||||||
|
9. 优先使用1小时级别数据实时确认回踩
|
||||||
|
|
||||||
|
严格约束条件:
|
||||||
|
- EMA5 > EMA10 > EMA20(三重多头排列)全程检查
|
||||||
|
- 价格不能跌破EMA10支撑(博弈、突破、回踩全程)
|
||||||
|
- 当前价格过滤:自动过滤价格已跌破入场价5%以上的信号
|
||||||
|
- 时间过滤:信号保留3天内,形态保留7天内
|
||||||
|
|
||||||
核心优化理念:
|
核心优化理念:
|
||||||
- 支持双形态识别,提高识别覆盖率
|
- 双形态识别,提高识别覆盖率
|
||||||
- 形态1:传统形态,适合标准突破
|
- 形态A:经典"双阳+阴线"突破,适合标准趋势反转
|
||||||
- 形态2:强势整理,适合快速突破
|
- 形态B:强势"高实体+博弈"突破,适合快速拉升
|
||||||
- 等待价格证明突破有效性(创新高)
|
- 等待价格证明突破有效性(创新高)
|
||||||
- 再等待合理回踩机会(回踩参考价)
|
- 再等待合理回踩机会(回踩参考K线最高价)
|
||||||
- 确保信号质量和入场时机的最佳化
|
- 确保信号质量和入场时机的最佳化
|
||||||
|
|
||||||
回踩监控功能:
|
回踩监控功能:
|
||||||
- 自动监控已确认信号后的价格走势
|
- 自动监控已确认信号后的价格走势
|
||||||
- 当价格再次回踩到阴线最高点附近时发送特殊提醒
|
- 当价格再次回踩到参考K线最高点附近时发送特殊提醒
|
||||||
- 监控期限:信号确认后 {self.monitor_days} 天
|
- 监控期限:信号确认后 {self.monitor_days} 天
|
||||||
- 提醒条件:价格接近阴线最高点且相比新高有明显回调
|
- 提醒条件:价格接近参考K线最高点且相比新高有明显回调
|
||||||
|
|
||||||
信号特征:
|
信号特征:
|
||||||
- 模式识别:满足基础形态条件(无信号)
|
- 形态识别:满足基础形态条件(无信号)
|
||||||
- 确认信号:完成创新高+回踩确认(正式信号)
|
- 确认信号:完成创新高+回踩确认(正式信号)
|
||||||
- 信号内容:包含模式日期、突破位置(第几根K线)、创新高日期、回踩确认日期
|
- 信号内容:包含形态日期、突破位置(第几根K线)、创新高日期、回踩确认日期
|
||||||
- 支持时间周期:{', '.join(self.timeframes)}
|
- 支持时间周期:{', '.join(self.timeframes)}
|
||||||
|
|
||||||
优化效果:
|
优化效果:
|
||||||
- 基础形态识别率提高(不要求第4根必须突破)
|
- 多形态识别率提高,覆盖更多机会
|
||||||
- 极大降低假突破信号
|
- 极大降低假突破信号
|
||||||
- 提供更优质的入场时机
|
- 提供更优质的入场时机
|
||||||
- 确保突破的真实有效性
|
- 确保突破的真实有效性
|
||||||
- 降低追高风险,提高成功率
|
- 降低追高风险,提高成功率
|
||||||
|
|
||||||
扫描范围:
|
扫描范围:
|
||||||
- 优先使用双数据源合并(同花顺热股+东财人气榜)
|
- 主力股票池:沪深300 + 中证500 + 创业板指成分股
|
||||||
- 自动去重,保留最优质股票
|
- 约1000-1500只优质股票
|
||||||
- 回退到全市场股票列表
|
|
||||||
|
|
||||||
通知方式:
|
通知方式:
|
||||||
- 钉钉webhook汇总推送(10个信号一组分批发送)
|
- 钉钉webhook汇总推送(10个信号一组分批发送)
|
||||||
- 仅确认信号发送通知(模式识别不通知)
|
- 仅确认信号发送通知(形态识别不通知)
|
||||||
- 创新高回踩确认完成通知(正式信号)
|
- 创新高回踩确认完成通知(正式信号)
|
||||||
- 价格二次回踩特殊提醒(5个提醒一组分批发送)
|
- 价格二次回踩特殊提醒(5个提醒一组分批发送)
|
||||||
- 包含关键信息:代码、股票名称、模式日期、创新高日期、回踩确认日期、价格等
|
- 包含关键信息:代码、股票名称、形态日期、创新高日期、回踩确认日期、价格等
|
||||||
- 系统日志详细记录
|
- 系统日志详细记录
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 加密货币市场扫描启动脚本
|
# 加密货币市场扫描启动脚本 (Docker定时任务版本)
|
||||||
# 用于手动运行或在Docker容器中启动
|
# 用于在Docker容器中启动定时任务服务
|
||||||
|
|
||||||
# 设置错误时退出
|
# 设置错误时退出
|
||||||
set -e
|
set -e
|
||||||
@ -12,11 +12,12 @@ cd "$SCRIPT_DIR"
|
|||||||
|
|
||||||
# 打印启动信息
|
# 打印启动信息
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "🚀 加密货币市场扫描程序启动"
|
echo "🚀 加密货币市场扫描定时服务启动"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "工作目录: $(pwd)"
|
echo "工作目录: $(pwd)"
|
||||||
echo "Python版本: $(python --version 2>&1)"
|
echo "Python版本: $(python --version 2>&1)"
|
||||||
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
echo "时区: $(date '+%Z %z')"
|
||||||
|
echo "当前时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
|
||||||
# 检查Python是否安装
|
# 检查Python是否安装
|
||||||
@ -25,6 +26,12 @@ if ! command -v python &> /dev/null; then
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# 检查加密货币扫描器是否存在
|
||||||
|
if [ ! -f "crypto_scanner.py" ]; then
|
||||||
|
echo "❌ 错误: 未找到crypto_scanner.py"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# 检查依赖包是否安装
|
# 检查依赖包是否安装
|
||||||
echo "📦 检查依赖包..."
|
echo "📦 检查依赖包..."
|
||||||
if ! python -c "import binance" 2>/dev/null; then
|
if ! python -c "import binance" 2>/dev/null; then
|
||||||
@ -35,19 +42,56 @@ fi
|
|||||||
# 创建日志目录
|
# 创建日志目录
|
||||||
mkdir -p logs
|
mkdir -p logs
|
||||||
|
|
||||||
# 获取命令行参数(扫描交易对数量)
|
# 检查cron服务
|
||||||
MAX_SYMBOLS=${1:-100}
|
if ! command -v cron &> /dev/null; then
|
||||||
|
echo "⚠️ 警告: cron未安装,正在安装..."
|
||||||
|
apt-get update && apt-get install -y cron
|
||||||
|
fi
|
||||||
|
|
||||||
echo "📊 扫描参数: 最大交易对数量 = $MAX_SYMBOLS"
|
# 安装加密货币扫描定时任务
|
||||||
echo "=========================================="
|
echo "📅 安装加密货币扫描定时任务..."
|
||||||
echo ""
|
if [ -f "crontab/crypto-scanner" ]; then
|
||||||
|
# 复制定时任务配置
|
||||||
|
cp crontab/crypto-scanner /etc/cron.d/crypto-scanner
|
||||||
|
chmod 0644 /etc/cron.d/crypto-scanner
|
||||||
|
echo "✅ 加密货币扫描定时任务已安装"
|
||||||
|
else
|
||||||
|
echo "❌ 错误: 未找到crontab/crypto-scanner文件"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# 运行扫描程序
|
# 启动cron服务
|
||||||
python crypto_scanner.py "$MAX_SYMBOLS"
|
echo "🔄 启动cron定时服务..."
|
||||||
|
service cron start
|
||||||
|
|
||||||
|
# 显示当前的定时任务
|
||||||
|
echo "📋 当前定时任务列表:"
|
||||||
|
crontab -l -u root 2>/dev/null || echo "暂无定时任务"
|
||||||
|
|
||||||
# 打印完成信息
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo "✅ 加密货币市场扫描完成"
|
echo "✅ 加密货币市场扫描定时服务启动完成"
|
||||||
echo "时间: $(date '+%Y-%m-%d %H:%M:%S')"
|
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
|
echo "📅 定时任务计划:"
|
||||||
|
echo " - 每天 08:00: 扫描100个交易对 (隔夜行情)"
|
||||||
|
echo " - 每天 16:00: 扫描100个交易对 (下午行情)"
|
||||||
|
echo " - 每天 00:00: 扫描100个交易对 (晚间行情)"
|
||||||
|
echo " - 每周日 10:00: 扫描200个交易对 (深度扫描)"
|
||||||
|
echo ""
|
||||||
|
echo "📊 日志文件: /app/logs/crypto_cron.log"
|
||||||
|
echo "🔄 服务状态: $(service cron status | head -1)"
|
||||||
|
echo "=========================================="
|
||||||
|
|
||||||
|
# 保持容器运行,监控cron服务
|
||||||
|
echo "🔍 监控cron服务状态..."
|
||||||
|
while true; do
|
||||||
|
# 检查cron服务是否运行
|
||||||
|
if ! service cron status > /dev/null 2>&1; then
|
||||||
|
echo "⚠️ cron服务停止,正在重启..."
|
||||||
|
service cron start
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 每小时输出一次状态
|
||||||
|
sleep 3600
|
||||||
|
echo "$(date '+%Y-%m-%d %H:%M:%S') - 加密货币扫描定时服务正常运行"
|
||||||
|
done
|
||||||
Loading…
Reference in New Issue
Block a user