diff --git a/user_data/strategies/grid_manager.py b/user_data/strategies/grid_manager.py index 6999118..2639ff5 100644 --- a/user_data/strategies/grid_manager.py +++ b/user_data/strategies/grid_manager.py @@ -1134,3 +1134,223 @@ class GridManager: print(f"[GridManager] {pair} 从 Redis 加载失败: {str(e)}", file=sys.stderr, flush=True) return None + + # ===== Redis List 操作接口 ===== + + def add_to_current_grid_list(self, grid: 'Grid') -> bool: + """ + 添加一个 Grid 到 currentGridList(从右侧入栈) + 后进先出,最新添加的在右侧 + + Args: + grid: Grid 对象 + + Returns: + 是否添加成功 + """ + if not self.redis_client: + return False + + try: + grid_data = { + 'hash_id': grid.hash_id, + 'grid_index': grid.grid_index, + 'direction': grid.direction, + 'entry_price': grid.entry_price, + 'entry_time': grid.entry_time, + 'quantity': grid.quantity, + 'bid_price': grid.bid_price, + 'bid_time': grid.bid_time, + } + self.redis_client.rpush( + self.current_grid_list_key, + json.dumps(grid_data) + ) + return True + except Exception as e: + print(f"[GridManager] {self.pair} 添加到 currentGridList 失败: {str(e)}", + file=sys.stderr, flush=True) + return False + + def remove_from_current_grid_list(self, hash_id: str) -> bool: + """ + 从 currentGridList 移除指定的 Grid + (按 hash_id 查找并删除) + + Args: + hash_id: 要移除的 Grid 的 hash_id + + Returns: + 是否移除成功 + """ + if not self.redis_client: + return False + + try: + # 获取列表中的所有元素 + items = self.redis_client.lrange(self.current_grid_list_key, 0, -1) + + # 重新构建列表(跳过目标元素) + self.redis_client.delete(self.current_grid_list_key) + + for item in items: + data = json.loads(item) + if data['hash_id'] != hash_id: + self.redis_client.rpush(self.current_grid_list_key, item) + + return True + except Exception as e: + print(f"[GridManager] {self.pair} 从 currentGridList 移除失败: {str(e)}", + file=sys.stderr, flush=True) + return False + + def get_current_grid_list(self) -> list: + """ + 获取整个 currentGridList + + Returns: + Grid 对象列表 + """ + if not self.redis_client: + return [] + + try: + items = self.redis_client.lrange(self.current_grid_list_key, 0, -1) + grids = [] + for item in items: + data = json.loads(item) + grid = Grid( + hash_id=data['hash_id'], + grid_index=data['grid_index'], + direction=data['direction'], + entry_price=data.get('entry_price'), + entry_time=data.get('entry_time'), + quantity=data.get('quantity', 0.0), + bid_price=data.get('bid_price'), + bid_time=data.get('bid_time'), + ) + grids.append(grid) + return grids + except Exception as e: + print(f"[GridManager] {self.pair} 获取 currentGridList 失败: {str(e)}", + file=sys.stderr, flush=True) + return [] + + def add_to_history_grid_list(self, grid: 'Grid', filled_price: float, filled_time: int, profit: float) -> bool: + """ + 添加一个已兑现的 Grid 到 historyGridList(仅追加) + + Args: + grid: Grid 对象 + filled_price: 兑现价格 + filled_time: 兑现时间(蜡烛线索引) + profit: 单个网格的盈亏 + + Returns: + 是否添加成功 + """ + if not self.redis_client: + return False + + try: + history_data = { + 'hash_id': grid.hash_id, + 'grid_index': grid.grid_index, + 'direction': grid.direction, + 'entry_price': grid.entry_price, + 'entry_time': grid.entry_time, + 'quantity': grid.quantity, + 'bid_price': grid.bid_price, + 'filled_price': filled_price, + 'filled_time': filled_time, + 'profit': profit, + } + self.redis_client.rpush( + self.history_grid_list_key, + json.dumps(history_data) + ) + return True + except Exception as e: + print(f"[GridManager] {self.pair} 添加到 historyGridList 失败: {str(e)}", + file=sys.stderr, flush=True) + return False + + def get_history_grid_list(self) -> list: + """ + 获取整个 historyGridList + + Returns: + 已兑现的 Grid 对象列表 + """ + if not self.redis_client: + return [] + + try: + items = self.redis_client.lrange(self.history_grid_list_key, 0, -1) + grids = [] + for item in items: + data = json.loads(item) + grid = Grid( + hash_id=data['hash_id'], + grid_index=data['grid_index'], + direction=data['direction'], + entry_price=data.get('entry_price'), + entry_time=data.get('entry_time'), + quantity=data.get('quantity', 0.0), + bid_price=data.get('bid_price'), + filled_price=data.get('filled_price'), + filled_time=data.get('filled_time'), + profit=data.get('profit'), + ) + grids.append(grid) + return grids + except Exception as e: + print(f"[GridManager] {self.pair} 获取 historyGridList 失败: {str(e)}", + file=sys.stderr, flush=True) + return [] + + def calculate_pnl(self) -> Dict[str, float]: + """ + 通过对比 currentGridList 和 historyGridList 计算盈亏 + + Returns: + 包含以下字段的字典: + - total_entry_cost: 总建仓成本 + - total_filled_revenue: 总兑现收益 + - realized_pnl: 已实现盈亏 + - unrealized_pnl: 未实现盈亏 + - total_pnl: 总盈亏 + """ + try: + history = self.get_history_grid_list() + current = self.get_current_grid_list() + + # 计算已兑现的盈亏 + realized_pnl = sum(grid.profit or 0.0 for grid in history) + + # 计算未实现的盈亏(当前持仓) + unrealized_pnl = 0.0 + if self.current_price and self.avg_entry_price > 0: + unrealized_pnl = (self.current_price - self.avg_entry_price) * self.total_quantity + + # 总成本和收益 + total_entry_cost = sum( + (grid.entry_price or 0) * grid.quantity + for grid in history if grid.direction == 'buy' + ) + total_filled_revenue = sum( + (grid.filled_price or 0) * grid.quantity + for grid in history if grid.direction == 'sell' + ) + + return { + 'total_entry_cost': total_entry_cost, + 'total_filled_revenue': total_filled_revenue, + 'realized_pnl': realized_pnl, + 'unrealized_pnl': unrealized_pnl, + 'total_pnl': realized_pnl + unrealized_pnl, + } + except Exception as e: + print(f"[GridManager] {self.pair} 计算盈亏失败: {str(e)}", + file=sys.stderr, flush=True) + return {}