# bridge **Repository Path**: pengchuanchao/bridge ## Basic Information - **Project Name**: bridge - **Description**: 以rust为核心开发的简易python回测框架 - **Primary Language**: Rust - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 1 - **Created**: 2023-07-21 - **Last Updated**: 2023-12-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: Rust, Python, 回测, backtest ## README # Bridge 核心功能基于**Cython**实现的简易**Python**回测框架 ## 1.设计思路 使用**Rust**过程中发现由于**gil**释放时机问题导致PyObject释放延迟, 内存出现间歇性的大量占用,引发OOM,于是写了版Cython的回测框架对比性能 核心逻辑以Cython实现,包括回测引擎和策略模板 ### 1.1 Cython端核心功能(Broker) - 行情播放 - 接受订单,拟合交易 - 更新持仓、账户和产品净值 - 数据推送到策略层 ### 1.2 Python端核心功能 Python端需要实现`Strategy`类以及`next()`方法来供`Broker`调用以运行回测策略 ## 2.安装 克隆后编译安装 ```sh > git clone https://gitee.com/pengchuanchao/bridge.git > git checkout cython > pip install build wheel cython setuptools -U # 编译wheel然后安装 > python -m build --no-isolation > pip install dist/bridge-[version].whl # 安装开发版本 > pip install -e . ``` ## 3.示例 以下为一个简单的策略示例,实现的功能很简单,就是一个均价买入卖出的策略,代码如下: ```python3 import time from collections import defaultdict import bridge import pandas as pd from cuba import get_client client = get_client() index = "000906.SH" weight = client.read("index::weight", start="2023-06-01") stocks = weight.query("symbol==@index")["stock"].unique().tolist() class Strategy(bridge.BaseStrategy): def __init__(self): super().__init__() factor = client.read("stock::factor::adj_vol", columns=stocks) self.fac = dict(zip(factor.index, factor.to_dict(orient="records"))) self.local_orders = defaultdict(set) self.prices = {} def buy(self, symbol: str, volume: int) -> bridge.Order: order = bridge.Order(symbol, 0.0, volume, bridge.Offset.OPEN, bridge.Direction.LONG, bridge.OrderType.MARKET) return order def sell(self, symbol: str, volume: int) -> bridge.Order: order = bridge.Order(symbol, 0.0, volume, bridge.Offset.CLOSE, bridge.Direction.SHORT, bridge.OrderType.MARKET) return order def adjust(self): orders = [] balance = self.account.balance * 0.8 for symbol, volume in self.local_orders["sell"]: order = self.sell(symbol, volume) orders.append(order) buy = list(self.local_orders["buy"]) hold = [x.symbol for x in self.positions.values() if x.volume > 0] buy.extend(hold) if not buy: self.local_orders.clear() self.prices.clear() return orders buy_ammount_every = balance / len(buy) for symbol in buy: # 停牌 price = self.prices.get(symbol) if not price: continue volume = int(buy_ammount_every / (price * 100)) * 100 key = f"{symbol}.{bridge.Direction.LONG}" pos = self.positions.get(key) if not pos: if volume == 0: continue order = self.buy(symbol, volume) else: delta = volume - pos.volume if delta == 0: continue elif delta > 0: order = self.buy(symbol, delta) else: order = self.sell(symbol, -delta) orders.append(order) self.local_orders.clear() self.prices.clear() return orders def on_bar(self, line: bridge.Line): self.prices[line.symbol] = line.close dfac = self.fac.get(line.date) if not dfac: return fac = dfac.get(line.symbol) if pd.isna(fac) or not fac: return if fac > 0.9: self.local_orders["buy"].add(line.symbol) position = self.positions.get(f"{line.symbol}.{bridge.Direction.LONG}") if position and position.volume > 0: if fac < 0.1: self.local_orders["sell"].add((line.symbol, position.volume)) def next(self, line: bridge.Line, is_last: bool): self.on_bar(line) orders = [] if is_last: orders = self.adjust() return orders def backtest(): strategy = Strategy() data = client.read("stock::pv", start="2018-01-01") data = data.drop_duplicates() data = data.reset_index() data = data[data["symbol"].isin(stocks)] print("data load finish: \n", data.tail(), "\n") data = [bridge.Line(x.symbol, x.date, x.open, x.high, x.low, x.close, 0, 0) for x in data.itertuples()] broker = bridge.Broker() broker.set_cash(1e8) broker.set_data(data) broker.set_strategy(strategy) t1 = time.time() broker.run() t2 = time.time() print(f"backtest finish in {round(t2-t1, 2)} seconds") nav = broker.get_nav() print(broker.get_account()) perf = bridge.Performance.from_navs(nav) print(perf.perf()) if __name__ == "__main__": backtest() ```