From 51f3401ea4d3f999b4b33cdb022411ba9820c14f Mon Sep 17 00:00:00 2001 From: 27942 <1313123@342> Date: Sun, 18 Jan 2026 19:36:48 +0800 Subject: [PATCH] 14564 --- mexc/获取数据.py | 659 +++++++++++++++++++++++++++++++++++++++++++++ models/database.db | Bin 57344 -> 102400 bytes models/mexc.py | 57 ++++ 3 files changed, 716 insertions(+) create mode 100644 mexc/获取数据.py create mode 100644 models/mexc.py diff --git a/mexc/获取数据.py b/mexc/获取数据.py new file mode 100644 index 0000000..158d622 --- /dev/null +++ b/mexc/获取数据.py @@ -0,0 +1,659 @@ +import requests +import pandas as pd +import datetime +import time +from models.mexc import Mexc1, Mexc15, Mexc30, Mexc1Hour + + +def get_mexc_klines(symbol="SOLUSDT", interval="30m", limit=500): + url = "https://api.mexc.com/api/v3/klines" + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + } + params = { + "symbol": symbol.upper(), + "interval": interval, + "limit": limit + } + + try: + resp = requests.get(url, params=params, headers=headers, timeout=10) + resp.raise_for_status() # 提前抛出 HTTP 错误 + data = resp.json() + + if not isinstance(data, list) or not data: + print("返回数据异常:", data) + return None + + # 使用正确的 8 列 + columns = [ + "open_time", "open", "high", "low", "close", + "volume", "close_time", "quote_volume" + ] + + df = pd.DataFrame(data, columns=columns) + + # 保存原始时间戳(用于数据库id) + df["open_time_ms"] = df["open_time"].astype(int) + + # 时间戳转可读时间 + df["open_time"] = pd.to_datetime(df["open_time"], unit="ms") + df["close_time"] = pd.to_datetime(df["close_time"], unit="ms") + + # 常用字段排序 + 类型转换 + df = df[[ + "open_time", "open", "high", "low", "close", + "volume", "quote_volume", "open_time_ms" + ]] + + # 转成浮点数(方便后续计算) + for col in ["open", "high", "low", "close", "volume", "quote_volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + + print(f"成功获取 {len(df)} 条 {interval} K线({symbol})") + return df + + except Exception as e: + print("请求失败:", str(e)) + return None + + +def normalize_futures_symbol(symbol): + """将现货格式转换为合约格式,如 SOLUSDT -> SOL_USDT""" + if "_" in symbol: + return symbol.upper() + symbol = symbol.upper() + for quote in ["USDT", "USDC", "USD", "BTC", "ETH"]: + if symbol.endswith(quote): + base = symbol[:-len(quote)] + return f"{base}_{quote}" + return symbol + + +def map_futures_interval(interval): + """合约K线周期映射""" + mapping = { + "1m": "Min1", + "5m": "Min5", + "15m": "Min15", + "30m": "Min30", + "1h": "Min60", + "4h": "Hour4", + "8h": "Hour8", + "1d": "Day1", + "1w": "Week1", + "1M": "Month1", + } + return mapping.get(interval, interval) + + +def get_mexc_klines_with_time(symbol="SOLUSDT", interval="30m", start_time_ms=None, end_time_ms=None, limit=1000): + """ + 获取指定时间范围内的K线数据 + :param symbol: 交易对符号 + :param interval: 时间间隔,如 "30m", "1m", "15m", "1h" + :param start_time_ms: 开始时间(毫秒级时间戳) + :param end_time_ms: 结束时间(毫秒级时间戳) + :param limit: 单次获取数量(最大1000) + :return: DataFrame或None + """ + def _fetch(params): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + } + resp = requests.get(url, params=params, headers=headers, timeout=10) + resp.raise_for_status() + data = resp.json() + return resp, data + + url = "https://api.mexc.com/api/v3/klines" + params = { + "symbol": symbol.upper(), + "interval": interval, + "limit": limit + } + if start_time_ms: + params["startTime"] = start_time_ms + if end_time_ms: + params["endTime"] = end_time_ms + + try: + resp, data = _fetch(params) + + # 检查是否是错误响应 + if isinstance(data, dict) and 'code' in data: + print(f"API返回错误: {data}, 请求参数: {params}") + return None + + # 尝试秒级时间戳回退(部分接口可能只接受秒) + if (not isinstance(data, list) or not data) and (start_time_ms or end_time_ms): + params_s = params.copy() + if start_time_ms: + params_s["startTime"] = int(start_time_ms // 1000) + if end_time_ms: + params_s["endTime"] = int(end_time_ms // 1000) + resp_s, data_s = _fetch(params_s) + if isinstance(data_s, list) and data_s: + print("检测到秒级时间戳可用,已自动回退到秒级参数") + resp, data = resp_s, data_s + params = params_s + + if not isinstance(data, list) or not data: + print(f"API返回数据异常: {data}, 请求参数: {params}") + print(f"完整URL: {resp.url}") + return None + + columns = [ + "open_time", "open", "high", "low", "close", + "volume", "close_time", "quote_volume" + ] + + df = pd.DataFrame(data, columns=columns) + # 判断时间单位(秒/毫秒) + max_open = pd.to_numeric(df["open_time"], errors="coerce").max() + if max_open < 1_000_000_000_000: # 小于1e12视为秒 + df["open_time_ms"] = (df["open_time"].astype(int) * 1000) + df["open_time"] = pd.to_datetime(df["open_time"], unit="s") + df["close_time"] = pd.to_datetime(df["close_time"], unit="s") + else: + df["open_time_ms"] = df["open_time"].astype(int) + df["open_time"] = pd.to_datetime(df["open_time"], unit="ms") + df["close_time"] = pd.to_datetime(df["close_time"], unit="ms") + + df = df[[ + "open_time", "open", "high", "low", "close", + "volume", "quote_volume", "open_time_ms" + ]] + + for col in ["open", "high", "low", "close", "volume", "quote_volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + + return df + + except Exception as e: + print(f"请求失败: {str(e)}") + return None + + +def get_mexc_futures_klines(symbol="SOLUSDT", interval="30m", limit=500): + return get_mexc_futures_klines_with_time( + symbol=symbol, + interval=interval, + start_time_ms=None, + end_time_ms=None, + limit=limit + ) + + +def get_mexc_futures_klines_with_time(symbol="SOLUSDT", interval="30m", start_time_ms=None, end_time_ms=None, limit=1000): + """ + 获取合约K线数据(使用 contract.mexc.com) + """ + url = "https://contract.mexc.com/api/v1/contract/kline" + symbol = normalize_futures_symbol(symbol) + interval = map_futures_interval(interval) + + def _fetch(params): + headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "Accept": "application/json, text/plain, */*", + "Content-Type": "application/json" + } + resp = requests.get(url, params=params, headers=headers, timeout=10) + resp.raise_for_status() + return resp, resp.json() + + params = { + "symbol": symbol, + "interval": interval, + "limit": limit + } + # 合约接口通常使用 start/end(秒级),这里默认秒级 + if start_time_ms: + params["start"] = int(start_time_ms // 1000) + if end_time_ms: + params["end"] = int(end_time_ms // 1000) + + try: + resp, data = _fetch(params) + + # 兼容错误结构 + if isinstance(data, dict) and ("success" in data and data.get("success") is False): + print(f"合约API返回错误: {data}, 请求参数: {params}") + return None + + payload = data + if isinstance(data, dict) and "data" in data: + payload = data["data"] + + # 兼容返回为 dict of arrays + if isinstance(payload, dict) and "time" in payload: + times = payload.get("time", []) + opens = payload.get("open", []) + highs = payload.get("high", []) + lows = payload.get("low", []) + closes = payload.get("close", []) + vols = payload.get("vol", []) + rows = list(zip(times, opens, highs, lows, closes, vols)) + payload = rows + + if not isinstance(payload, list) or not payload: + print(f"合约API返回数据异常: {payload}, 请求参数: {params}") + print(f"完整URL: {resp.url}") + return None + + # 兼容 list of lists: [time, open, high, low, close, vol, ...] + columns = ["open_time", "open", "high", "low", "close", "volume"] + df = pd.DataFrame(payload, columns=columns + list(range(len(payload[0]) - len(columns)))) + df = df[columns] + + # 判断时间单位(秒/毫秒) + max_open = pd.to_numeric(df["open_time"], errors="coerce").max() + if max_open < 1_000_000_000_000: + df["open_time_ms"] = (df["open_time"].astype(int) * 1000) + df["open_time"] = pd.to_datetime(df["open_time"], unit="s") + else: + df["open_time_ms"] = df["open_time"].astype(int) + df["open_time"] = pd.to_datetime(df["open_time"], unit="ms") + + for col in ["open", "high", "low", "close", "volume"]: + df[col] = pd.to_numeric(df[col], errors="coerce") + + # 合约接口不一定返回 quote_volume,这里保持一致字段结构 + df["quote_volume"] = pd.NA + df = df[[ + "open_time", "open", "high", "low", "close", + "volume", "quote_volume", "open_time_ms" + ]] + + return df + + except Exception as e: + print(f"合约请求失败: {str(e)}") + return None + + +def get_interval_ms(interval): + """ + 将时间间隔字符串转换为毫秒数 + :param interval: 如 "1m", "15m", "30m", "1h" + :return: 毫秒数 + """ + if interval.endswith("m"): + minutes = int(interval[:-1]) + return minutes * 60 * 1000 + elif interval.endswith("h"): + hours = int(interval[:-1]) + return hours * 60 * 60 * 1000 + elif interval.endswith("d"): + days = int(interval[:-1]) + return days * 24 * 60 * 60 * 1000 + else: + return 60 * 1000 # 默认1分钟 + + +def fetch_historical_data(symbol="SOLUSDT", interval="30m", start_date="2024-01-01", end_date=None, auto_save=True, mode="auto", market="spot"): + """ + 批量获取历史K线数据并存储到数据库 + :param symbol: 交易对符号 + :param interval: 时间间隔,如 "30m", "1m", "15m", "1h" + :param start_date: 开始日期,格式 "YYYY-MM-DD",默认 "2024-01-01" + :param end_date: 结束日期,格式 "YYYY-MM-DD",默认当前时间 + :param auto_save: 是否自动保存到数据库,默认True + :param mode: 获取模式,"auto" 自动判断,"forward" 正向,"reverse" 反向 + :return: 总共获取的数据条数 + """ + # 转换日期为时间戳(毫秒) + start_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d") + start_dt = start_dt.replace(hour=0, minute=0, second=0, microsecond=0) + start_time_ms = int(start_dt.timestamp() * 1000) + + if end_date: + end_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d") + end_dt = end_dt.replace(hour=23, minute=59, second=59, microsecond=999999) + else: + end_dt = datetime.datetime.now() + end_time_ms = int(end_dt.timestamp() * 1000) + + print(f"开始获取 {symbol} {interval} K线数据") + print(f"时间范围: {start_date} 至 {end_dt.strftime('%Y-%m-%d %H:%M:%S')}") + + current_start = start_time_ms + total_count = 0 + limit = 1000 # API最大限制 + interval_ms = get_interval_ms(interval) + + # 计算每次请求的时间窗口(1000条数据的时间跨度) + window_ms = limit * interval_ms + + if mode == "reverse": + return fetch_historical_data_reverse( + symbol=symbol, + interval=interval, + start_date=start_date, + end_date=end_date, + auto_save=auto_save, + market=market + ) + + fetch_fn = get_mexc_klines_with_time + latest_fn = get_mexc_klines + if market == "futures": + fetch_fn = get_mexc_futures_klines_with_time + latest_fn = get_mexc_futures_klines + + if mode == "auto": + # 探测:如果 start+end 为空、start 单独返回最新数据,说明 startTime 被忽略,改用反向获取 + probe_end = min(start_time_ms + window_ms, end_time_ms) + probe_df = fetch_fn( + symbol=symbol, + interval=interval, + start_time_ms=start_time_ms, + end_time_ms=probe_end, + limit=10 + ) + if probe_df is None or probe_df.empty: + probe_df2 = fetch_fn( + symbol=symbol, + interval=interval, + start_time_ms=start_time_ms, + end_time_ms=None, + limit=10 + ) + if probe_df2 is not None and not probe_df2.empty: + latest_ms = int(probe_df2.iloc[-1]["open_time_ms"]) + if latest_ms > probe_end: + print("检测到 startTime 被忽略,自动改为反向获取模式") + return fetch_historical_data_reverse( + symbol=symbol, + interval=interval, + start_date=start_date, + end_date=end_date, + auto_save=auto_save, + market=market + ) + + while current_start < end_time_ms: + current_end = min(current_start + window_ms, end_time_ms) + + print(f"正在获取: {datetime.datetime.fromtimestamp(current_start/1000)} 至 {datetime.datetime.fromtimestamp(current_end/1000)}") + print(f"时间戳: {current_start} 至 {current_end}") + + df = fetch_fn( + symbol=symbol, + interval=interval, + start_time_ms=current_start, + end_time_ms=current_end, + limit=limit + ) + + if df is None or df.empty: + # 先测试一下不带时间参数的最新数据是否能获取到 + if current_start == start_time_ms and total_count == 0: + print("\n=== 开始调试 ===") + print("测试1:尝试获取最新数据(不带时间参数)...") + test_df = latest_fn(symbol=symbol, interval=interval, limit=10) + if test_df is not None and not test_df.empty: + print(f"✓ 测试1成功!最新数据可以获取") + print(f" 最新时间: {test_df.iloc[-1]['open_time']}") + print(f" 最新时间戳: {test_df.iloc[-1]['open_time_ms']}") + + # 测试2:只带startTime,不带endTime + print(f"\n测试2:尝试只带startTime(从{start_date}开始)...") + test_df2 = fetch_fn( + symbol=symbol, + interval=interval, + start_time_ms=current_start, + end_time_ms=None, + limit=100 + ) + if test_df2 is not None and not test_df2.empty: + print(f"✓ 测试2成功!从指定时间开始获取数据") + print(f" 获取到 {len(test_df2)} 条数据") + print(f" 第一条时间: {test_df2.iloc[0]['open_time']}") + print(f" 最后一条时间: {test_df2.iloc[-1]['open_time']}") + print("=== 调试结束 ===\n") + # 使用测试2的方法继续,赋值给df + df = test_df2 + # 不break,继续下面的处理流程 + else: + print("✗ 测试2失败:带startTime无法获取数据") + print("\n建议:MEXC API可能不支持从历史日期开始查询") + print("可以尝试:1. 检查时间范围是否在API支持范围内") + print(" 2. 使用反向获取:从最新数据往前推") + print("=== 调试结束 ===\n") + break + else: + print("✗ 测试1失败:即使不带时间参数也无法获取数据") + print("可能原因:交易对符号或时间间隔不正确") + print("=== 调试结束 ===\n") + break + + # 如果测试后df仍然是None或空,说明无法获取数据 + if df is None or df.empty: + if total_count > 0: + print("已到达数据末尾") + else: + print("本次获取数据为空,跳过") + break + + count = len(df) + total_count += count + print(f"本次获取 {count} 条数据,累计 {total_count} 条") + + # 自动保存到数据库 + if auto_save: + save_to_database(df, interval=interval, symbol=symbol) + + # 更新下一次的起始时间(从最后一条数据的开盘时间 + 一个间隔) + last_open_ms = int(df.iloc[-1]['open_time_ms']) + current_start = last_open_ms + interval_ms + + # 避免请求过快 + time.sleep(0.2) + + print(f"完成!总共获取 {total_count} 条 {symbol} {interval} K线数据") + return total_count + + +def fetch_historical_data_reverse(symbol="SOLUSDT", interval="30m", start_date="2024-01-01", end_date=None, auto_save=True, market="spot"): + """ + 反向获取历史K线数据(从最新往前推,使用startTime参数) + """ + start_dt = datetime.datetime.strptime(start_date, "%Y-%m-%d") + start_dt = start_dt.replace(hour=0, minute=0, second=0, microsecond=0) + start_time_ms = int(start_dt.timestamp() * 1000) + + if end_date: + end_dt = datetime.datetime.strptime(end_date, "%Y-%m-%d") + end_dt = end_dt.replace(hour=23, minute=59, second=59, microsecond=999999) + else: + end_dt = datetime.datetime.now() + end_time_ms = int(end_dt.timestamp() * 1000) + + print(f"开始反向获取 {symbol} {interval} K线数据(从最新往前推)") + print(f"目标时间范围: {start_date} 至 {end_dt.strftime('%Y-%m-%d %H:%M:%S')}") + + total_count = 0 + limit = 1000 # 每次尝试获取1000条 + interval_ms = get_interval_ms(interval) + + # 使用带时间参数的函数 + fetch_fn = get_mexc_klines_with_time + if market == "futures": + fetch_fn = get_mexc_futures_klines_with_time + + # 先获取最新数据,确定当前最新时间 + print("\n第 1 批:获取最新数据...") + df_latest = get_mexc_klines(symbol=symbol, interval=interval, limit=100) + if df_latest is None or df_latest.empty: + print("无法获取最新数据,停止") + return 0 + + latest_time_ms = int(df_latest.iloc[-1]["open_time_ms"]) + earliest_fetched_ms = int(df_latest.iloc[0]["open_time_ms"]) + + # 保存第一批数据 + df_latest_filtered = df_latest[df_latest["open_time_ms"] >= start_time_ms] + if not df_latest_filtered.empty: + count = len(df_latest_filtered) + total_count += count + print(f"获取 {count} 条最新数据,时间范围: {datetime.datetime.fromtimestamp(earliest_fetched_ms/1000)} 至 {datetime.datetime.fromtimestamp(latest_time_ms/1000)}") + if auto_save: + save_to_database(df_latest_filtered, interval=interval, symbol=symbol) + + # 从已获取的最早时间往前推 + current_start_ms = earliest_fetched_ms - interval_ms + batch_num = 1 + + while current_start_ms >= start_time_ms: + batch_num += 1 + # 计算本次请求的时间窗口(往前推limit条数据的时间跨度) + window_ms = limit * interval_ms + batch_start_ms = max(current_start_ms - window_ms, start_time_ms) + + print(f"\n第 {batch_num} 批:从 {datetime.datetime.fromtimestamp(batch_start_ms/1000)} 开始获取...") + + # 使用startTime参数,尝试获取更早的数据 + df = fetch_fn( + symbol=symbol, + interval=interval, + start_time_ms=batch_start_ms, + end_time_ms=current_start_ms, + limit=limit + ) + + if df is None or df.empty: + # 如果返回空,尝试只使用startTime,不带endTime + print(" 带endTime返回空,尝试只使用startTime...") + df = fetch_fn( + symbol=symbol, + interval=interval, + start_time_ms=batch_start_ms, + end_time_ms=None, + limit=limit + ) + + if df is None or df.empty: + print(f" 无法获取数据,可能已到达API历史数据限制") + break + + # 检查返回的数据是否真的从startTime开始(判断startTime是否被忽略) + returned_earliest = int(df.iloc[0]["open_time_ms"]) + returned_latest = int(df.iloc[-1]["open_time_ms"]) + + # 如果返回的数据仍然是最新的,说明startTime被忽略 + if returned_latest >= latest_time_ms - 1000: # 允许1秒误差 + print(f" 检测到startTime被忽略(返回最新数据),无法继续往前获取") + break + + # 过滤掉已获取的数据和早于start_date的数据 + df = df[df["open_time_ms"] < current_start_ms] + df = df[df["open_time_ms"] >= start_time_ms] + + if df.empty: + print(f" 过滤后无新数据") + # 如果已经到达目标起始时间,完成 + if batch_start_ms <= start_time_ms: + print(f"已到达目标起始时间({start_date}),完成") + break + # 否则继续往前推 + current_start_ms = batch_start_ms - interval_ms + time.sleep(0.3) + continue + + # 更新已获取的最早时间戳 + current_earliest = int(df.iloc[0]["open_time_ms"]) + if current_earliest < earliest_fetched_ms: + earliest_fetched_ms = current_earliest + + count = len(df) + total_count += count + print(f" 获取 {count} 条新数据,最早时间: {datetime.datetime.fromtimestamp(current_earliest/1000)}") + print(f" 累计获取 {total_count} 条数据") + + if auto_save: + save_to_database(df, interval=interval, symbol=symbol) + + # 更新下一次的起始时间 + current_start_ms = current_earliest - interval_ms + + # 避免请求过快 + time.sleep(0.3) + + # 安全限制 + if batch_num > 500: + print("批次过多,停止") + break + + print(f"\n完成!总共获取 {total_count} 条 {symbol} {interval} K线数据") + if earliest_fetched_ms: + print(f"最早数据时间: {datetime.datetime.fromtimestamp(earliest_fetched_ms/1000)}") + if earliest_fetched_ms > start_time_ms: + print(f"⚠️ 注意:最早数据时间 ({datetime.datetime.fromtimestamp(earliest_fetched_ms/1000)}) 晚于目标起始时间 ({start_date})") + print(f" 这可能是因为MEXC API的历史数据限制,无法获取更早的数据") + return total_count + + +def save_to_database(df, interval="30m", symbol="SOLUSDT"): + """ + 将K线数据存储到数据库 + :param df: pandas DataFrame,包含K线数据 + :param interval: 时间间隔,如 "30m", "1m", "15m", "1h" + :param symbol: 交易对符号 + """ + if df is None or df.empty: + print("数据为空,无法存储") + return + + # 根据时间间隔选择对应的模型 + interval_map = { + "1m": Mexc1, + "15m": Mexc15, + "30m": Mexc30, + "1h": Mexc1Hour, + } + + model_class = interval_map.get(interval) + if model_class is None: + print(f"不支持的时间间隔: {interval}") + return + + # 存储数据到数据库 + saved_count = 0 + for _, row in df.iterrows(): + # 使用原始时间戳(毫秒级)作为 id + timestamp_ms = int(row['open_time_ms']) + + model_class.get_or_create( + id=timestamp_ms, + defaults={ + 'open': float(row['open']), + 'high': float(row['high']), + 'low': float(row['low']), + 'close': float(row['close']), + } + ) + saved_count += 1 + + print(f"成功存储 {saved_count} 条 {interval} K线数据({symbol})到数据库") + + +# 使用示例 +if __name__ == "__main__": + # 方式1: 获取最新200条数据 + # df = get_mexc_klines("SOLUSDT", "30m", 200) + # if df is not None: + # print(df.tail(8)) + # save_to_database(df, interval="30m", symbol="SOLUSDT") + + # 方式2: 批量获取2024年至今的历史数据(使用现货接口,反向获取) + fetch_historical_data( + symbol="SOLUSDT", + interval="30m", + start_date="2024-01-01", + end_date=None, # None表示到当前时间 + auto_save=True, + market="spot", # 使用现货接口 + mode="reverse" # 强制使用反向获取模式(从最新往前推) + ) \ No newline at end of file diff --git a/models/database.db b/models/database.db index cdcb63b72cbdd34882dfbf6d07ae3ce921e821df..af6a2c2f929e4c053643c9ef73915ce639870f74 100644 GIT binary patch literal 102400 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 DH+Kw9 diff --git a/models/mexc.py b/models/mexc.py new file mode 100644 index 0000000..54e05e9 --- /dev/null +++ b/models/mexc.py @@ -0,0 +1,57 @@ +from peewee import * + +from models import db + + +class Mexc1(Model): + id = IntegerField(primary_key=True) + open = FloatField(null=True) + high = FloatField(null=True) + low = FloatField(null=True) + close = FloatField(null=True) + + class Meta: + database = db + table_name = 'mexc_1' + + +class Mexc15(Model): + id = IntegerField(primary_key=True) + open = FloatField(null=True) + high = FloatField(null=True) + low = FloatField(null=True) + close = FloatField(null=True) + + class Meta: + database = db + table_name = 'mexc_15' + + +class Mexc30(Model): + id = IntegerField(primary_key=True) + open = FloatField(null=True) + high = FloatField(null=True) + low = FloatField(null=True) + close = FloatField(null=True) + + class Meta: + database = db + table_name = 'mexc_30' + + +class Mexc1Hour(Model): + id = IntegerField(primary_key=True) + open = FloatField(null=True) + high = FloatField(null=True) + low = FloatField(null=True) + close = FloatField(null=True) + + class Meta: + database = db + table_name = 'mexc_1_hour' + + +# 连接到数据库 +db.connect() +# 创建表(如果表不存在) +db.create_tables([Mexc1, Mexc15, Mexc30, Mexc1Hour])