polymarket-backtest 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polymarket_backtest/__init__.py +35 -0
- polymarket_backtest/api/__init__.py +13 -0
- polymarket_backtest/api/clob.py +136 -0
- polymarket_backtest/api/gamma.py +194 -0
- polymarket_backtest/api/models.py +69 -0
- polymarket_backtest/backtest/__init__.py +21 -0
- polymarket_backtest/backtest/metrics.py +286 -0
- polymarket_backtest/data/__init__.py +11 -0
- polymarket_backtest/data/loader.py +136 -0
- polymarket_backtest/data/sample/btc_orderbook.csv +13750 -0
- polymarket_backtest/data/sample/flash_crash_summary.csv +3 -0
- polymarket_backtest/data/sample/flash_crash_trades.csv +23 -0
- polymarket_backtest/data/sample/hedge_arb_trades.csv +5 -0
- polymarket_backtest-0.1.0.dist-info/METADATA +216 -0
- polymarket_backtest-0.1.0.dist-info/RECORD +17 -0
- polymarket_backtest-0.1.0.dist-info/WHEEL +5 -0
- polymarket_backtest-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
polymarket-backtest
|
|
3
|
+
===================
|
|
4
|
+
|
|
5
|
+
Polymarket API 封装与策略回测评估工具包。
|
|
6
|
+
|
|
7
|
+
快速开始:
|
|
8
|
+
# 1. 查询市场信息
|
|
9
|
+
from polymarket_backtest.api import GammaClient, ClobClient
|
|
10
|
+
|
|
11
|
+
gamma = GammaClient()
|
|
12
|
+
market = gamma.get_market_info("BTC")
|
|
13
|
+
print(market.slug, market.up_price)
|
|
14
|
+
|
|
15
|
+
# 2. 拉取赔率历史
|
|
16
|
+
clob = ClobClient()
|
|
17
|
+
history = clob.get_price_history(market.up_token_id, interval="1d")
|
|
18
|
+
print(f"{len(history)} 个价格点")
|
|
19
|
+
|
|
20
|
+
# 3. 加载内置示例数据集
|
|
21
|
+
from polymarket_backtest.data import load_trades, load_orderbook
|
|
22
|
+
|
|
23
|
+
trades = load_trades("flash_crash")
|
|
24
|
+
ob = load_orderbook("BTC")
|
|
25
|
+
|
|
26
|
+
# 4. 计算回测指标
|
|
27
|
+
from polymarket_backtest.backtest import summary
|
|
28
|
+
|
|
29
|
+
pnl = trades["gross_pnl"].dropna().tolist()
|
|
30
|
+
result = summary(pnl)
|
|
31
|
+
print(result)
|
|
32
|
+
# {'net_pnl': 47.92, 'win_rate': 0.143, 'sharpe_ratio': 0.85, 'max_drawdown': 12.5, ...}
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Polymarket API 封装模块。"""
|
|
2
|
+
|
|
3
|
+
from .clob import ClobClient
|
|
4
|
+
from .gamma import GammaClient
|
|
5
|
+
from .models import MarketInfo, OddsHistory, PricePoint
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"GammaClient",
|
|
9
|
+
"ClobClient",
|
|
10
|
+
"MarketInfo",
|
|
11
|
+
"PricePoint",
|
|
12
|
+
"OddsHistory",
|
|
13
|
+
]
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polymarket CLOB API 客户端(只读)
|
|
3
|
+
|
|
4
|
+
封装赔率历史数据查询。无需认证。
|
|
5
|
+
|
|
6
|
+
用法示例:
|
|
7
|
+
from polymarket_backtest.api import GammaClient, ClobClient
|
|
8
|
+
|
|
9
|
+
gamma = GammaClient()
|
|
10
|
+
clob = ClobClient()
|
|
11
|
+
|
|
12
|
+
market = gamma.get_market_info("BTC")
|
|
13
|
+
history = clob.get_price_history(market.up_token_id, interval="1d")
|
|
14
|
+
|
|
15
|
+
for point in history.points:
|
|
16
|
+
print(point.timestamp, point.price)
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from typing import Literal
|
|
22
|
+
|
|
23
|
+
import requests
|
|
24
|
+
|
|
25
|
+
from .models import OddsHistory, PricePoint
|
|
26
|
+
|
|
27
|
+
Interval = Literal["1m", "1h", "6h", "1d", "1w", "max"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ClobClient:
|
|
31
|
+
"""
|
|
32
|
+
Polymarket CLOB API 客户端(只读子集)。
|
|
33
|
+
|
|
34
|
+
提供赔率历史查询,无需 API 密钥。
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
BASE_URL = "https://clob.polymarket.com"
|
|
38
|
+
|
|
39
|
+
def __init__(self, base_url: str = BASE_URL, timeout: int = 15):
|
|
40
|
+
self.base_url = base_url.rstrip("/")
|
|
41
|
+
self.timeout = timeout
|
|
42
|
+
self._session = requests.Session()
|
|
43
|
+
|
|
44
|
+
# ------------------------------------------------------------------
|
|
45
|
+
# 公开接口
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def get_price_history(
|
|
49
|
+
self,
|
|
50
|
+
token_id: str,
|
|
51
|
+
interval: Interval | None = "1d",
|
|
52
|
+
fidelity: int = 60,
|
|
53
|
+
start_ts: int | None = None,
|
|
54
|
+
end_ts: int | None = None,
|
|
55
|
+
) -> OddsHistory:
|
|
56
|
+
"""
|
|
57
|
+
拉取某 token 的赔率历史时间序列。
|
|
58
|
+
|
|
59
|
+
参数:
|
|
60
|
+
token_id: CLOB token ID(从 GammaClient 获取)
|
|
61
|
+
interval: 时间窗口("1m","1h","6h","1d","1w","max")
|
|
62
|
+
与 start_ts/end_ts 互斥
|
|
63
|
+
fidelity: 数据分辨率(分钟),例如 60 = 每小时一个点
|
|
64
|
+
start_ts: 开始时间 Unix 时间戳(UTC)
|
|
65
|
+
end_ts: 结束时间 Unix 时间戳(UTC)
|
|
66
|
+
|
|
67
|
+
返回:
|
|
68
|
+
OddsHistory 对象,包含时间戳和对应价格列表
|
|
69
|
+
|
|
70
|
+
示例:
|
|
71
|
+
# 拉取最近 1 天数据,每小时一个点
|
|
72
|
+
history = clob.get_price_history(token_id, interval="1d", fidelity=60)
|
|
73
|
+
|
|
74
|
+
# 拉取指定时间范围
|
|
75
|
+
history = clob.get_price_history(
|
|
76
|
+
token_id,
|
|
77
|
+
start_ts=1697875200,
|
|
78
|
+
end_ts=1697961600,
|
|
79
|
+
fidelity=5,
|
|
80
|
+
)
|
|
81
|
+
"""
|
|
82
|
+
params: dict = {"market": token_id, "fidelity": fidelity}
|
|
83
|
+
|
|
84
|
+
if start_ts is not None or end_ts is not None:
|
|
85
|
+
if start_ts is not None:
|
|
86
|
+
params["startTs"] = start_ts
|
|
87
|
+
if end_ts is not None:
|
|
88
|
+
params["endTs"] = end_ts
|
|
89
|
+
elif interval is not None:
|
|
90
|
+
params["interval"] = interval
|
|
91
|
+
|
|
92
|
+
url = f"{self.base_url}/prices-history"
|
|
93
|
+
resp = self._session.get(url, params=params, timeout=self.timeout)
|
|
94
|
+
resp.raise_for_status()
|
|
95
|
+
|
|
96
|
+
data = resp.json()
|
|
97
|
+
raw_points = data.get("history", [])
|
|
98
|
+
points = [PricePoint(timestamp=int(p["t"]), price=float(p["p"])) for p in raw_points]
|
|
99
|
+
|
|
100
|
+
return OddsHistory(
|
|
101
|
+
token_id=token_id,
|
|
102
|
+
interval=interval or "custom",
|
|
103
|
+
fidelity=fidelity,
|
|
104
|
+
points=points,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def get_price_history_df(
|
|
108
|
+
self,
|
|
109
|
+
token_id: str,
|
|
110
|
+
interval: Interval | None = "1d",
|
|
111
|
+
fidelity: int = 60,
|
|
112
|
+
start_ts: int | None = None,
|
|
113
|
+
end_ts: int | None = None,
|
|
114
|
+
):
|
|
115
|
+
"""
|
|
116
|
+
同 get_price_history,但直接返回 pandas DataFrame。
|
|
117
|
+
|
|
118
|
+
DataFrame 列:timestamp(Unix)、price、datetime(UTC)
|
|
119
|
+
"""
|
|
120
|
+
import pandas as pd
|
|
121
|
+
|
|
122
|
+
history = self.get_price_history(
|
|
123
|
+
token_id,
|
|
124
|
+
interval=interval,
|
|
125
|
+
fidelity=fidelity,
|
|
126
|
+
start_ts=start_ts,
|
|
127
|
+
end_ts=end_ts,
|
|
128
|
+
)
|
|
129
|
+
if not history.points:
|
|
130
|
+
return pd.DataFrame(columns=["timestamp", "price", "datetime"])
|
|
131
|
+
|
|
132
|
+
df = pd.DataFrame(
|
|
133
|
+
{"timestamp": history.timestamps(), "price": history.prices()}
|
|
134
|
+
)
|
|
135
|
+
df["datetime"] = pd.to_datetime(df["timestamp"], unit="s", utc=True)
|
|
136
|
+
return df
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polymarket Gamma API 客户端
|
|
3
|
+
|
|
4
|
+
封装市场发现与合约信息查询,支持 BTC/ETH/SOL/XRP 的 15 分钟涨跌市场。
|
|
5
|
+
|
|
6
|
+
用法示例:
|
|
7
|
+
from polymarket_backtest.api import GammaClient
|
|
8
|
+
|
|
9
|
+
client = GammaClient()
|
|
10
|
+
market = client.get_market_info("ETH")
|
|
11
|
+
print(market.slug, market.up_price, market.down_price)
|
|
12
|
+
|
|
13
|
+
# 列出近期市场
|
|
14
|
+
markets = client.list_recent_markets("BTC", n=5)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import time
|
|
21
|
+
from datetime import datetime, timezone
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
import requests
|
|
25
|
+
|
|
26
|
+
from .models import MarketInfo
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class GammaClient:
|
|
30
|
+
"""
|
|
31
|
+
Polymarket Gamma API 客户端。
|
|
32
|
+
|
|
33
|
+
用于发现市场和获取市场元数据(无需认证)。
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
BASE_URL = "https://gamma-api.polymarket.com"
|
|
37
|
+
|
|
38
|
+
COIN_SLUGS: dict[str, str] = {
|
|
39
|
+
"BTC": "btc-updown-15m",
|
|
40
|
+
"ETH": "eth-updown-15m",
|
|
41
|
+
"SOL": "sol-updown-15m",
|
|
42
|
+
"XRP": "xrp-updown-15m",
|
|
43
|
+
}
|
|
44
|
+
SUPPORTED_COINS = list(COIN_SLUGS.keys())
|
|
45
|
+
|
|
46
|
+
def __init__(self, base_url: str = BASE_URL, timeout: int = 10):
|
|
47
|
+
self.base_url = base_url.rstrip("/")
|
|
48
|
+
self.timeout = timeout
|
|
49
|
+
self._session = requests.Session()
|
|
50
|
+
|
|
51
|
+
# ------------------------------------------------------------------
|
|
52
|
+
# 公开接口
|
|
53
|
+
# ------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def get_market_info(self, coin: str) -> MarketInfo | None:
|
|
56
|
+
"""
|
|
57
|
+
获取某币种当前活跃的 15 分钟市场信息。
|
|
58
|
+
|
|
59
|
+
参数:
|
|
60
|
+
coin: 币种符号(BTC / ETH / SOL / XRP,不区分大小写)
|
|
61
|
+
|
|
62
|
+
返回:
|
|
63
|
+
MarketInfo 对象,无活跃市场时返回 None
|
|
64
|
+
"""
|
|
65
|
+
market = self._get_active_market(coin.upper())
|
|
66
|
+
if not market:
|
|
67
|
+
return None
|
|
68
|
+
return self._parse_market(market, coin.upper())
|
|
69
|
+
|
|
70
|
+
def get_market_by_slug(self, slug: str) -> MarketInfo | None:
|
|
71
|
+
"""
|
|
72
|
+
通过 slug 精确查询市场。
|
|
73
|
+
|
|
74
|
+
参数:
|
|
75
|
+
slug: 市场 slug,例如 "eth-updown-15m-1775035200"
|
|
76
|
+
|
|
77
|
+
返回:
|
|
78
|
+
MarketInfo 对象,未找到时返回 None
|
|
79
|
+
"""
|
|
80
|
+
coin = self._coin_from_slug(slug)
|
|
81
|
+
raw = self._fetch_by_slug(slug)
|
|
82
|
+
if not raw:
|
|
83
|
+
return None
|
|
84
|
+
return self._parse_market(raw, coin)
|
|
85
|
+
|
|
86
|
+
def list_recent_markets(self, coin: str, n: int = 10) -> list[MarketInfo]:
|
|
87
|
+
"""
|
|
88
|
+
列出某币种最近 n 个市场(从当前窗口往前推算)。
|
|
89
|
+
|
|
90
|
+
参数:
|
|
91
|
+
coin: 币种符号
|
|
92
|
+
n: 返回的市场数量
|
|
93
|
+
|
|
94
|
+
返回:
|
|
95
|
+
MarketInfo 列表(按时间从新到旧排列)
|
|
96
|
+
"""
|
|
97
|
+
coin = coin.upper()
|
|
98
|
+
self._check_coin(coin)
|
|
99
|
+
prefix = self.COIN_SLUGS[coin]
|
|
100
|
+
|
|
101
|
+
now = datetime.now(timezone.utc)
|
|
102
|
+
minute = (now.minute // 15) * 15
|
|
103
|
+
current_ts = int(now.replace(minute=minute, second=0, microsecond=0).timestamp())
|
|
104
|
+
|
|
105
|
+
results: list[MarketInfo] = []
|
|
106
|
+
ts = current_ts
|
|
107
|
+
while len(results) < n:
|
|
108
|
+
slug = f"{prefix}-{ts}"
|
|
109
|
+
raw = self._fetch_by_slug(slug)
|
|
110
|
+
if raw:
|
|
111
|
+
results.append(self._parse_market(raw, coin))
|
|
112
|
+
ts -= 900 # 往前 15 分钟
|
|
113
|
+
if ts < current_ts - 900 * 200: # 最多往前 200 个窗口(约 2 天)
|
|
114
|
+
break
|
|
115
|
+
|
|
116
|
+
return results
|
|
117
|
+
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
# 内部方法
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def _get_active_market(self, coin: str) -> dict[str, Any] | None:
|
|
123
|
+
"""尝试当前、下一个、上一个窗口,找到 acceptingOrders 的市场。"""
|
|
124
|
+
self._check_coin(coin)
|
|
125
|
+
prefix = self.COIN_SLUGS[coin]
|
|
126
|
+
|
|
127
|
+
now = datetime.now(timezone.utc)
|
|
128
|
+
minute = (now.minute // 15) * 15
|
|
129
|
+
current_ts = int(now.replace(minute=minute, second=0, microsecond=0).timestamp())
|
|
130
|
+
|
|
131
|
+
for offset in [0, 900, -900]:
|
|
132
|
+
ts = current_ts + offset
|
|
133
|
+
raw = self._fetch_by_slug(f"{prefix}-{ts}")
|
|
134
|
+
if raw and raw.get("acceptingOrders"):
|
|
135
|
+
return raw
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def _fetch_by_slug(self, slug: str, retries: int = 3) -> dict[str, Any] | None:
|
|
139
|
+
"""GET /markets/slug/{slug},SSL 错误时自动重试。"""
|
|
140
|
+
url = f"{self.base_url}/markets/slug/{slug}"
|
|
141
|
+
for attempt in range(retries):
|
|
142
|
+
try:
|
|
143
|
+
resp = self._session.get(url, timeout=self.timeout)
|
|
144
|
+
if resp.status_code == 200:
|
|
145
|
+
return resp.json()
|
|
146
|
+
return None
|
|
147
|
+
except Exception as e:
|
|
148
|
+
err = str(e).lower()
|
|
149
|
+
is_network = any(k in err for k in ("ssl", "eof", "tls", "connection"))
|
|
150
|
+
if is_network and attempt < retries - 1:
|
|
151
|
+
time.sleep(0.5 * (attempt + 1))
|
|
152
|
+
continue
|
|
153
|
+
return None
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
def _parse_market(self, raw: dict[str, Any], coin: str) -> MarketInfo:
|
|
157
|
+
"""将原始 API 响应解析为 MarketInfo。"""
|
|
158
|
+
outcomes = self._parse_json_field(raw.get("outcomes", '["Up", "Down"]'))
|
|
159
|
+
token_ids_raw = self._parse_json_field(raw.get("clobTokenIds", "[]"))
|
|
160
|
+
prices_raw = self._parse_json_field(raw.get("outcomePrices", '["0.5", "0.5"]'))
|
|
161
|
+
|
|
162
|
+
token_ids = {str(o).lower(): str(v) for o, v in zip(outcomes, token_ids_raw)}
|
|
163
|
+
prices = {str(o).lower(): float(v) for o, v in zip(outcomes, prices_raw)}
|
|
164
|
+
|
|
165
|
+
return MarketInfo(
|
|
166
|
+
slug=raw.get("slug", ""),
|
|
167
|
+
question=raw.get("question", ""),
|
|
168
|
+
coin=coin,
|
|
169
|
+
end_date=raw.get("endDate", ""),
|
|
170
|
+
token_ids=token_ids,
|
|
171
|
+
prices=prices,
|
|
172
|
+
accepting_orders=bool(raw.get("acceptingOrders", False)),
|
|
173
|
+
raw=raw,
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
@staticmethod
|
|
177
|
+
def _parse_json_field(value: Any) -> list:
|
|
178
|
+
if isinstance(value, str):
|
|
179
|
+
return json.loads(value)
|
|
180
|
+
return list(value)
|
|
181
|
+
|
|
182
|
+
def _check_coin(self, coin: str) -> None:
|
|
183
|
+
if coin not in self.COIN_SLUGS:
|
|
184
|
+
raise ValueError(
|
|
185
|
+
f"不支持的币种: {coin!r}。支持: {self.SUPPORTED_COINS}"
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
@staticmethod
|
|
189
|
+
def _coin_from_slug(slug: str) -> str:
|
|
190
|
+
slug_lower = slug.lower()
|
|
191
|
+
for coin, prefix in GammaClient.COIN_SLUGS.items():
|
|
192
|
+
if slug_lower.startswith(prefix):
|
|
193
|
+
return coin
|
|
194
|
+
return "UNKNOWN"
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Polymarket API 数据模型
|
|
3
|
+
|
|
4
|
+
定义 API 调用的输入输出数据结构。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class MarketInfo:
|
|
14
|
+
"""Polymarket 市场信息。"""
|
|
15
|
+
|
|
16
|
+
slug: str
|
|
17
|
+
question: str
|
|
18
|
+
coin: str
|
|
19
|
+
end_date: str
|
|
20
|
+
token_ids: dict[str, str] # {"up": "token_id", "down": "token_id"}
|
|
21
|
+
prices: dict[str, float] # {"up": 0.52, "down": 0.48}
|
|
22
|
+
accepting_orders: bool
|
|
23
|
+
raw: dict = field(default_factory=dict, repr=False)
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def up_token_id(self) -> str | None:
|
|
27
|
+
return self.token_ids.get("up")
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def down_token_id(self) -> str | None:
|
|
31
|
+
return self.token_ids.get("down")
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def up_price(self) -> float:
|
|
35
|
+
return self.prices.get("up", 0.5)
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def down_price(self) -> float:
|
|
39
|
+
return self.prices.get("down", 0.5)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PricePoint:
|
|
44
|
+
"""单个历史价格点。"""
|
|
45
|
+
|
|
46
|
+
timestamp: int # Unix UTC
|
|
47
|
+
price: float
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return f"PricePoint(t={self.timestamp}, p={self.price:.4f})"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class OddsHistory:
|
|
55
|
+
"""某 token 的赔率历史时间序列。"""
|
|
56
|
+
|
|
57
|
+
token_id: str
|
|
58
|
+
interval: str
|
|
59
|
+
fidelity: int # 分辨率(分钟)
|
|
60
|
+
points: list[PricePoint] = field(default_factory=list)
|
|
61
|
+
|
|
62
|
+
def __len__(self) -> int:
|
|
63
|
+
return len(self.points)
|
|
64
|
+
|
|
65
|
+
def prices(self) -> list[float]:
|
|
66
|
+
return [p.price for p in self.points]
|
|
67
|
+
|
|
68
|
+
def timestamps(self) -> list[int]:
|
|
69
|
+
return [p.timestamp for p in self.points]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""策略回测评估模块。"""
|
|
2
|
+
|
|
3
|
+
from .metrics import (
|
|
4
|
+
calmar_ratio,
|
|
5
|
+
max_drawdown,
|
|
6
|
+
profit_factor,
|
|
7
|
+
sharpe_ratio,
|
|
8
|
+
summary,
|
|
9
|
+
summary_from_df,
|
|
10
|
+
win_rate,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"sharpe_ratio",
|
|
15
|
+
"max_drawdown",
|
|
16
|
+
"win_rate",
|
|
17
|
+
"profit_factor",
|
|
18
|
+
"calmar_ratio",
|
|
19
|
+
"summary",
|
|
20
|
+
"summary_from_df",
|
|
21
|
+
]
|