From c6edebe6743f1232f4d60af511f90ccdc4c9ad64 Mon Sep 17 00:00:00 2001 From: blade <8019068@qq.com> Date: Tue, 5 Aug 2025 17:03:59 +0800 Subject: [PATCH] complete market monitor functions. --- .../market_monitor.cpython-312.pyc | Bin 0 -> 13623 bytes core/biz/market_monitor.py | 104 ++++++++++-- .../db_market_monitor.cpython-312.pyc | Bin 0 -> 19075 bytes market_monitor_main.py | 159 +++++++++++++----- monitor_schedule.py | 20 +++ sql/query/sql_playground.sql | 6 + 6 files changed, 230 insertions(+), 59 deletions(-) create mode 100644 core/biz/__pycache__/market_monitor.cpython-312.pyc create mode 100644 core/db/__pycache__/db_market_monitor.cpython-312.pyc create mode 100644 monitor_schedule.py diff --git a/core/biz/__pycache__/market_monitor.cpython-312.pyc b/core/biz/__pycache__/market_monitor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5e3939c543a4bc682421b38af7759a956dd2b680 GIT binary patch literal 13623 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 0: - contents.append(f"#### 多头指标信号") contents.append(f"{"\n".join(long_info_list)}") - if len(short_info_list) > 0: - contents.append(f"#### 空头指标信号") - contents.append(f"{"\n".join(short_info_list)}") + else: + contents.append(f"无多头指标信号") + contents.append(f"#### 空头指标信号") + if len(short_info_list) > 0: + contents.append(f"{"\n".join(short_info_list)}") + else: + contents.append(f"无空头指标信号") + + if next_bar_row is not None: + contents.append(f"## {symbol} 与更长周期技术形态对比") + contents.extend(get_long_short_over_buy_sell(next_bar_row)) + if btc_bar_row is not None: + contents.append(f"## {symbol} 与BTC相同周期技术形态对比") + contents.extend(get_long_short_over_buy_sell(btc_bar_row)) + mark_down_text = "\n\n".join(contents) return mark_down_text + + +def get_long_short_over_buy_sell( + row: pd.Series, +): + result = {} + symbol = row["symbol"] + bar = row["bar"] + ma_long_short = str(row["ma_long_short"]) + macd_signal = str(row["macd_signal"]) + macd_divergence = str(row["macd_divergence"]) + kdj_signal = str(row["kdj_signal"]) + kdj_pattern = str(row["kdj_pattern"]) + rsi_signal = str(row["rsi_signal"]) + boll_signal = str(row["boll_signal"]) + boll_pattern = str(row["boll_pattern"]) + contents = [] + contents.append(f"### {symbol} {bar} 对比形态") + if ma_long_short in ["多", "空"]: + contents.append(f"均线形态: {ma_long_short}") + else: + contents.append(f"均线形态: 震荡") + if macd_signal in ["金叉", "死叉"]: + contents.append(f"MACD信号: {macd_signal}") + if macd_divergence in ["顶背离", "底背离"]: + contents.append(f"MACD背离: {macd_divergence}") + if kdj_signal in ["金叉", "死叉"]: + contents.append(f"KDJ信号: {kdj_signal}") + if kdj_pattern in ["超超买", "超买", "超超卖", "超卖"]: + contents.append(f"KDJ形态: {kdj_pattern}") + if rsi_signal in ["超超买", "超买", "超超卖", "超卖"]: + contents.append(f"RSI形态: {rsi_signal}") + if boll_signal in ["突破下轨", "击穿上轨"]: + contents.append(f"BOLL信号: {boll_signal}") + if boll_pattern in ["超超买", "超买", "超超卖", "超卖"]: + contents.append(f"BOLL形态: {boll_pattern}") + return contents + diff --git a/core/db/__pycache__/db_market_monitor.cpython-312.pyc b/core/db/__pycache__/db_market_monitor.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9642ab78593bf0e330094f69ccb6d16686712825 GIT binary patch literal 19075 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 R+VOjpOGW2?PGLxJ{~xp1Aou_P literal 0 HcmV?d00001 diff --git a/market_monitor_main.py b/market_monitor_main.py index 3904b14..9da3420 100644 --- a/market_monitor_main.py +++ b/market_monitor_main.py @@ -27,7 +27,7 @@ class MarketMonitorMain: self.start_date = MONITOR_CONFIG.get("volume_monitor", {}).get( "initial_date", "2025-05-01 00:00:00" ) - self.latest_record_file_path = "./output/latest_record.json" + self.latest_record_file_path = "./output/record/latest_record.json" self.latest_record = self.get_latest_record() self.output_folder = "./output/report/market_monitor/" os.makedirs(self.output_folder, exist_ok=True) @@ -48,12 +48,17 @@ class MarketMonitorMain: """ 获取最新记录 """ - if os.path.exists(self.latest_record_file_path): - with open(self.latest_record_file_path, "r", encoding="utf-8") as f: - return json.load(f) - else: - with open(self.latest_record_file_path, "w", encoding="utf-8") as f: - json.dump({}, f, ensure_ascii=False, indent=4) + os.makedirs(os.path.dirname(self.latest_record_file_path), exist_ok=True) + try: + if os.path.exists(self.latest_record_file_path): + with open(self.latest_record_file_path, "r", encoding="utf-8") as f: + return json.load(f) + else: + with open(self.latest_record_file_path, "w", encoding="utf-8") as f: + json.dump({}, f, ensure_ascii=False, indent=4) + return {} + except Exception as e: + logging.error(f"获取最后一次报表生成记录失败: {e}") return {} def monitor_realtime_market( @@ -67,10 +72,14 @@ class MarketMonitorMain: 监控最新市场数据 考虑到速度,暂不与数据库交互,直接从api获取数据 """ + # 获得当前时间字符串 + now_datetime = datetime.now() + now_datetime_str = now_datetime.strftime("%Y-%m-%d %H:%M:%S") + end_time = transform_date_time_to_timestamp(now_datetime_str) real_time_data = self.market_data_main.market_data.get_realtime_kline_data( symbol=symbol, bar=bar, - end_time=None, + end_time=end_time, limit=50, ) @@ -79,28 +88,28 @@ class MarketMonitorMain: return latest_realtime_timestamp = real_time_data["timestamp"].iloc[-1] + latest_realtime_timestamp = int(latest_realtime_timestamp) latest_record_timestamp = ( - self.latest_record.get(symbol, {}).get(bar, {}).get("timestamp", 0) + self.latest_record.get(symbol, {}).get(bar, {}).get("timestamp", None) ) latest_reatime_datetime = timestamp_to_datetime(latest_realtime_timestamp) - latest_record_datetime = timestamp_to_datetime(latest_record_timestamp) - if ( - latest_record_timestamp is not None - and latest_realtime_timestamp <= latest_record_timestamp - ): + if latest_record_timestamp is not None: + latest_record_timestamp = int(latest_record_timestamp) + + latest_record_datetime = timestamp_to_datetime(latest_record_timestamp) + if ( + latest_record_timestamp is not None + and latest_realtime_timestamp <= latest_record_timestamp + ): + logging.info( + f"最新市场数据时间戳 {latest_reatime_datetime} 小于等于最新记录时间戳 {latest_record_datetime}, 不进行监控" + ) + return logging.info( - f"最新市场数据时间戳 {latest_reatime_datetime} 小于等于最新记录时间戳 {latest_record_datetime}, 不进行监控" - ) - return + f"最新市场数据时间 {latest_reatime_datetime}, 上一次记录时间 {latest_record_datetime}") else: - self.latest_record[symbol] = {bar: {"timestamp": latest_realtime_timestamp}} - with open(self.latest_record_file_path, "w", encoding="utf-8") as f: - json.dump(self.latest_record, f, ensure_ascii=False, indent=4) - - - logging.info( - f"最新市场数据时间 {latest_reatime_datetime}, 上一次记录时间 {latest_record_datetime}" - ) + logging.info( + f"最新市场数据时间 {latest_reatime_datetime}, 上一次记录时间为空") real_time_data = self.market_data_main.add_new_columns(real_time_data) logging.info(f"开始计算技术指标: {symbol} {bar}") @@ -111,7 +120,7 @@ class MarketMonitorMain: window_size=self.window_size, threshold=self.huge_volume_main.threshold, check_price=True, - only_output_huge_volume=only_output_huge_volume, + only_output_huge_volume=False, output_excel=False, ) if real_time_data is None or len(real_time_data) == 0: @@ -119,20 +128,42 @@ class MarketMonitorMain: f"计算大成交量失败: {symbol} {bar} 窗口大小: {self.window_size}" ) return + realtime_row = real_time_data.iloc[-1] - report = create_metrics_report(real_time_data, only_output_rise) + if only_output_huge_volume: + if realtime_row["huge_volume"] == 1: + logging.info(f"监控到巨量: {symbol} {bar} 窗口大小: {self.window_size}") + else: + logging.info(f"监控到非巨量: {symbol} {bar} 窗口大小: {self.window_size},退出本次监控") + return + if only_output_rise: + if realtime_row["pct_change"] > 0: + logging.info(f"监控到上涨: {symbol} {bar} 窗口大小: {self.window_size}") + else: + logging.info(f"监控到下跌: {symbol} {bar} 窗口大小: {self.window_size},退出本次监控") + return + + next_bar_row = self.get_other_realtime_data(symbol, bar, end_time, next=True) + if 'BTC-USDT' in symbol: + btc_bar_row = None + else: + btc_bar_row = self.get_other_realtime_data('BTC-USDT', bar, end_time, next=False) + + + report = create_metrics_report(realtime_row, next_bar_row, btc_bar_row, only_output_huge_volume, only_output_rise) text_length = len(report.encode("utf-8")) + logging.info(f"发送报告到企业微信,字节数: {text_length}") self.wechat.send_markdown(report) - self.latest_record[symbol][bar]["timestamp"] = latest_realtime_timestamp - with open(self.latest_record_file_path, "w", encoding="utf-8") as f: - json.dump(self.latest_record, f, ensure_ascii=False, indent=4) + # remove punction in latest_reatime_datetime - latest_reatime_datetime = re.sub(r"[\:\-\s]", "", latest_reatime_datetime) - report_file_name = f"{symbol}_{bar}_{self.window_size}_{latest_reatime_datetime}.md" + file_datetime = re.sub(r"[\:\-\s]", "", latest_reatime_datetime) + report_file_name = ( + f"{symbol}_{bar}_{self.window_size}_{file_datetime}.md" + ) report_file_path = os.path.join(self.output_folder, report_file_name) with open(report_file_path, "w", encoding="utf-8") as f: - f.write(report.replace(":", "_")) + f.write(report) report_file_byte_size = os.path.getsize(report_file_path) report_data = { "symbol": symbol, @@ -143,22 +174,68 @@ class MarketMonitorMain: "report": report, "report_file_path": report_file_path, "report_file_name": report_file_name, - "report_file_byte_size": report_file_byte_size + "report_file_byte_size": report_file_byte_size, } report_data = pd.DataFrame([report_data]) logging.info(f"插入数据到数据库") self.db_market_monitor.insert_data_to_mysql(report_data) + if self.latest_record.get(symbol, None) is None: + self.latest_record[symbol] = {bar: {"timestamp": latest_realtime_timestamp}} + else: + self.latest_record[symbol][bar]["timestamp"] = latest_realtime_timestamp + with open(self.latest_record_file_path, "w", encoding="utf-8") as f: + json.dump(self.latest_record, f, ensure_ascii=False, indent=4) + + def get_other_realtime_data(self, symbol: str, bar: str, end_time: int, next: bool = True): + """ + 获取下一个长周期实时数据 + """ + if next: + # 获得bar在self.market_data_main.bars中的索引 + bar_index = self.market_data_main.bars.index(bar) + if bar_index == len(self.market_data_main.bars) - 1: + logging.error(f"已经是最后一个bar: {bar}") + return None + # 获得下一个bar + bar = self.market_data_main.bars[bar_index + 1] + # 获得下一个bar的实时数据 + data = self.market_data_main.market_data.get_realtime_kline_data( + symbol=symbol, bar=bar, end_time=end_time, limit=50 + ) + if data is None or len(data) == 0: + logging.error(f"获取实时数据失败: {symbol}, {bar}") + return None + data = self.market_data_main.add_new_columns(data) + logging.info(f"开始计算技术指标: {symbol} {bar}") + data = self.market_data_main.calculate_metrics(data) + row = data.iloc[-1] + return row def batch_monitor_realtime_market( self, only_output_huge_volume: bool = True, - only_output_rise: bool = False, + only_output_rise: bool = True, ): for symbol in self.market_data_main.symbols: for bar in self.market_data_main.bars: - self.monitor_realtime_market( - symbol, - bar, - only_output_huge_volume, - only_output_rise, - ) + logging.info(f"开始监控: {symbol} {bar} 窗口大小: {self.window_size} 行情数据") + try: + self.monitor_realtime_market( + symbol, + bar, + only_output_huge_volume, + only_output_rise, + ) + except Exception as e: + logging.error(f"监控失败: {symbol} {bar} 窗口大小: {self.window_size} 行情数据: {e}") + continue + + +if __name__ == "__main__": + market_monitor_main = MarketMonitorMain() + market_monitor_main.monitor_realtime_market( + symbol="ETH-USDT", + bar="5m", + only_output_huge_volume=True, + only_output_rise=False, + ) diff --git a/monitor_schedule.py b/monitor_schedule.py new file mode 100644 index 0000000..67c5fb5 --- /dev/null +++ b/monitor_schedule.py @@ -0,0 +1,20 @@ +from market_monitor_main import MarketMonitorMain +import logging +import time + +logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s: %(message)s") + + +def monitor_schedule(): + market_monitor_main = MarketMonitorMain() + logging.info("开始监控") + while True: # 每分钟监控一次 + market_monitor_main.batch_monitor_realtime_market( + only_output_huge_volume=True, + only_output_rise=False, + ) + logging.info("本次循环监控结束,等待1分钟") + time.sleep(60) + +if __name__ == "__main__": + monitor_schedule() \ No newline at end of file diff --git a/sql/query/sql_playground.sql b/sql/query/sql_playground.sql index 45f9454..d07bb41 100644 --- a/sql/query/sql_playground.sql +++ b/sql/query/sql_playground.sql @@ -1,3 +1,5 @@ +select * from crypto_market_monitor; +delete from crypto_market_monitor where timestamp=1754382900000; select date_time, open, high, low, close, k_shape from crypto_market_data WHERE symbol='XCH-USDT' and bar='5m' and date_time > '2025-08-04 15:00:00' order by timestamp ; @@ -6,6 +8,10 @@ select * from crypto_market_data WHERE symbol='XCH-USDT' and bar='5m' and date_time > '2025-08-04 15:00:00' order by timestamp asc; +select * from crypto_huge_volume +WHERE symbol='XCH-USDT' and bar='5m' #and date_time > '2025-08-04 15:00:00' +order by timestamp asc; + delete FROM crypto_market_data where symbol != 'XCH-USDT';