import sys
from pathlib import Path
关键:拿到上一级目录(flow 文件夹)
current_dir = Path(file).parent
upper_dir = current_dir.parent
把上级目录放进Python搜索路径
sys.path.insert(0, str(upper_dir))
from parsers.etf_flow_parser import ETFFLOWParser # 导入数据解析器依赖类
import pandas as pd # 导入pandas库用于数据结构化处理与时间序列筛选
class ETFFlowCollector: # 定义量化金融建模遍历取数模块核心类
"""封装数据获取接口,提供标准化数据访问服务""" # 类功能文档说明
def init(self, btc_price, parser=None): # 初始化实例方法,接收BTC价格作为必要参数
"""初始化数据获取器,创建数据解析器实例""" # 方法文档说明
if parser is not None: # 判断是否外部传入了解析器实例
self.parser = parser # 直接使用外部传入的解析器实例
else: # 若未传入外部实例则内部构建
self.parser = ETFFLOWParser(btc_price=btc_price) # 使用指定价格初始化解析器实例
self.default_price = btc_price # 缓存默认价格供后续取数方法使用
def _get_parsed_records(self, price=None): # 内部封装方法:调用解析器获取全量解析数据
"""执行解析器的run_flow_check流程获取records列表""" # 方法说明
target_price = price if price else self.default_price # 确定当前请求使用的价格参数
result_dict = self.parser.run_flow_check(btc_price=target_price) # 调用解析器获取结果字典
if result_dict.get("status") == "success": # 检查取数状态是否成功
return result_dict.get("records", []) # 提取并返回记录列表
else: # 处理取数失败情况
print(f"取数失败: {result_dict.get('message')}") # 打印错误信息
return [] # 返回空列表
def get_data_by_range(self, symbol, start_date, end_date, fields=None, price=None): # 定义按时间范围取数方法
"""获取指定时间范围内的数据""" # 方法说明
records = self._get_parsed_records(price) # 获取解析后的数据记录列表
if not records: # 判断数据是否为空
return pd.DataFrame() # 返回空DataFrame
df = pd.DataFrame(records) # 将列表转换为DataFrame结构
df['date'] = pd.to_datetime(df['date']) # 将日期字符串转换为时间对象
start_dt = pd.to_datetime(start_date) # 转换起始日期参数
end_dt = pd.to_datetime(end_date) # 转换结束日期参数
mask = (df['date'] >= start_dt) & (df['date'] <= end_dt) # 构建布尔索引掩码
df = df.loc[mask] # 应用时间范围筛选
if fields is not None: # 判断是否需要字段筛选
df = self.filter_fields(df, fields) # 调用字段过滤方法
return df # 返回处理后的数据
def get_latest_n(self, symbol, n, fields=None, price=None): # 定义按条数获取最新数据方法
"""获取最近N条数据记录""" # 方法说明
records = self._get_parsed_records(price) # 获取解析后的数据记录列表
if not records: # 判断数据是否为空
return pd.DataFrame() # 返回空DataFrame
df = pd.DataFrame(records) # 将列表转换为DataFrame结构
df['date'] = pd.to_datetime(df['date']) # 转换日期格式
df = df.sort_values(by='date', ascending=True) # 按日期升序排列
df = df.tail(n) # 截取最后N条数据
if fields is not None: # 判断是否需要字段筛选
df = self.filter_fields(df, fields) # 调用字段过滤方法
return df # 返回处理后的数据
def filter_fields(self, data, fields): # 定义私有字段筛选方法
"""从DataFrame中筛选指定字段""" # 方法说明
if not isinstance(fields, list): # 校验字段参数类型
fields = [fields] # 将单字段转换为列表
existing_fields = [f for f in fields if f in data.columns] # 过滤出有效字段
if not existing_fields: # 若无匹配字段
return data.iloc[:, 0:0] # 返回空DataFrame
return data[existing_fields] # 返回筛选后的数据
#指定条数输出的遍历输出函数
#symbol 数据条数
#输出内容:BTC的ETF每日的净流入总金额和净流入总数量
def print_formatted_flow(self, symbol, n, price=None): # 定义封装后的格式化输出方法
"""获取最近N条数据并按指定格式遍历打印输出""" # 方法功能说明
df = self.get_latest_n(symbol, n, price=price) # 调用内部方法获取DataFrame数据
if df.empty: # 判断数据是否为空
print("当前无有效数据可供输出。") # 输出无数据提示
return # 结束方法执行
for index, row in df.iterrows(): # 遍历DataFrame每一行数据
date_str = row['date'].strftime('%Y-%m-%d') # 格式化日期为字符串
btc_flow_str = f"{row['total_flow_btc']:.8f}" # 格式化BTC流量,保留8位小数
usd_flow_str = f"{row['total_flow_usd']:.2f}" # 格式化USD流量,保留2位小数
print(f"日期:{date_str}") # 输出日期行
print(f"BTC 净流入/流出:{btc_flow_str} BTC") # 输出BTC流量行
print(f"USD 净流入/流出:{usd_flow_str} USD") # 输出USD流量行
if name == "main": # 模块测试入口
fetcher = ETFFlowCollector(btc_price=70000.0) # 实例化取数对象,传入BTC价格
fetcher.print_formatted_flow('BTCUSDT', 10) # 通过类实例调用封装好的格式化输出函数