prosperity3bt 0.2.0__tar.gz → 0.4.0__tar.gz
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.
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/PKG-INFO +13 -11
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/README.md +5 -5
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/__main__.py +55 -69
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/datamodel.py +1 -1
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/models.py +7 -0
- prosperity3bt-0.4.0/prosperity3bt/resources/round0/__init__.py +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/runner.py +51 -20
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt.egg-info/PKG-INFO +13 -11
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt.egg-info/SOURCES.txt +1 -0
- prosperity3bt-0.4.0/prosperity3bt.egg-info/requires.txt +5 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/pyproject.toml +8 -2
- prosperity3bt-0.2.0/prosperity3bt.egg-info/requires.txt +0 -4
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/LICENSE +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/__init__.py +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/data.py +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/file_reader.py +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/open.py +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/parse_submission_logs.py +0 -0
- /prosperity3bt-0.2.0/prosperity3bt/resources/__init__.py → /prosperity3bt-0.4.0/prosperity3bt/py.typed +0 -0
- {prosperity3bt-0.2.0/prosperity3bt/resources/round0 → prosperity3bt-0.4.0/prosperity3bt/resources}/__init__.py +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/resources/round0/prices_round_0_day_-2.csv +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/resources/round0/trades_round_0_day_-2_nn.csv +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt.egg-info/dependency_links.txt +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt.egg-info/entry_points.txt +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt.egg-info/top_level.txt +0 -0
- {prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: prosperity3bt
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Backtester for IMC Prosperity 3 algorithms
|
5
5
|
Author-email: Jasper van Merle <jaspervmerle@gmail.com>
|
6
6
|
License: MIT License
|
@@ -37,10 +37,12 @@ Classifier: Programming Language :: Python :: 3
|
|
37
37
|
Requires-Python: >=3.9
|
38
38
|
Description-Content-Type: text/markdown
|
39
39
|
License-File: LICENSE
|
40
|
-
Requires-Dist: ipython
|
41
|
-
Requires-Dist: jsonpickle
|
42
|
-
Requires-Dist: orjson
|
43
|
-
Requires-Dist: tqdm
|
40
|
+
Requires-Dist: ipython>=8.18.1
|
41
|
+
Requires-Dist: jsonpickle>=4.0.2
|
42
|
+
Requires-Dist: orjson>=3.10.15
|
43
|
+
Requires-Dist: tqdm>=4.67.1
|
44
|
+
Requires-Dist: typer>=0.15.2
|
45
|
+
Dynamic: license-file
|
44
46
|
|
45
47
|
# IMC Prosperity 3 Backtester
|
46
48
|
|
@@ -98,16 +100,16 @@ $ prosperity3bt example/starter.py 1 --data prosperity3bt/resources
|
|
98
100
|
# Print trader's output to stdout while running
|
99
101
|
# This may be helpful when debugging a broken trader
|
100
102
|
$ prosperity3bt example/starter.py 1 --print
|
101
|
-
|
102
|
-
# Only match orders against order depths, not against market trades
|
103
|
-
$ prosperity3bt example/starter.py 1 --no-trades-matching
|
104
103
|
```
|
105
104
|
|
106
105
|
## Order Matching
|
107
106
|
|
108
|
-
Orders placed by `Trader.run` at a given timestamp are matched against the order depths and market trades of that timestamp's state. Order depths take priority, if an order can be filled completely using volume in the relevant order depth, market trades are not considered. If not, the backtester matches your order against the timestamp's market trades,
|
107
|
+
Orders placed by `Trader.run` at a given timestamp are matched against the order depths and market trades of that timestamp's state. Order depths take priority, if an order can be filled completely using volume in the relevant order depth, market trades are not considered. If not, the backtester matches your order against the timestamp's market trades. In this case the backtester assumes that for each trade, the buyer and the seller of the trade are willing to trade with you instead at the trade's price and volume. Market trades are matched at the price of your orders, e.g. if you place a sell order for €9 and there is a market trade for €10, the sell order is matched at €9 (even though there is a buyer willing to pay €10, this appears to be consistent with what the official Prosperity environment does).
|
109
108
|
|
110
|
-
|
109
|
+
Matching orders against market trades can be configured through the `--match-trades` option:
|
110
|
+
- `--match-trades all` (default): match market trades with prices equal to or worse than your quotes.
|
111
|
+
- `--match-trades worse`: match market trades with prices worse than your quotes, inspired by [team Linear Utility's Prosperity 2 write-up](https://github.com/ericcccsliu/imc-prosperity-2).
|
112
|
+
- `--match-trades none`: do not match market trades against orders.
|
111
113
|
|
112
114
|
Limits are enforced before orders are matched to order depths. If for a product your position would exceed the limit, assuming all your orders would get filled, all your orders for that product get canceled.
|
113
115
|
|
@@ -54,16 +54,16 @@ $ prosperity3bt example/starter.py 1 --data prosperity3bt/resources
|
|
54
54
|
# Print trader's output to stdout while running
|
55
55
|
# This may be helpful when debugging a broken trader
|
56
56
|
$ prosperity3bt example/starter.py 1 --print
|
57
|
-
|
58
|
-
# Only match orders against order depths, not against market trades
|
59
|
-
$ prosperity3bt example/starter.py 1 --no-trades-matching
|
60
57
|
```
|
61
58
|
|
62
59
|
## Order Matching
|
63
60
|
|
64
|
-
Orders placed by `Trader.run` at a given timestamp are matched against the order depths and market trades of that timestamp's state. Order depths take priority, if an order can be filled completely using volume in the relevant order depth, market trades are not considered. If not, the backtester matches your order against the timestamp's market trades,
|
61
|
+
Orders placed by `Trader.run` at a given timestamp are matched against the order depths and market trades of that timestamp's state. Order depths take priority, if an order can be filled completely using volume in the relevant order depth, market trades are not considered. If not, the backtester matches your order against the timestamp's market trades. In this case the backtester assumes that for each trade, the buyer and the seller of the trade are willing to trade with you instead at the trade's price and volume. Market trades are matched at the price of your orders, e.g. if you place a sell order for €9 and there is a market trade for €10, the sell order is matched at €9 (even though there is a buyer willing to pay €10, this appears to be consistent with what the official Prosperity environment does).
|
65
62
|
|
66
|
-
|
63
|
+
Matching orders against market trades can be configured through the `--match-trades` option:
|
64
|
+
- `--match-trades all` (default): match market trades with prices equal to or worse than your quotes.
|
65
|
+
- `--match-trades worse`: match market trades with prices worse than your quotes, inspired by [team Linear Utility's Prosperity 2 write-up](https://github.com/ericcccsliu/imc-prosperity-2).
|
66
|
+
- `--match-trades none`: do not match market trades against orders.
|
67
67
|
|
68
68
|
Limits are enforced before orders are matched to order depths. If for a product your position would exceed the limit, assuming all your orders would get filled, all your orders for that product get canceled.
|
69
69
|
|
@@ -1,31 +1,28 @@
|
|
1
1
|
import sys
|
2
|
-
from argparse import ArgumentParser
|
3
2
|
from collections import defaultdict
|
4
3
|
from datetime import datetime
|
5
4
|
from functools import reduce
|
6
5
|
from importlib import import_module, metadata, reload
|
7
6
|
from pathlib import Path
|
8
|
-
from typing import Any, Optional
|
7
|
+
from typing import Annotated, Any, Optional
|
8
|
+
|
9
|
+
from typer import Argument, Option, Typer
|
9
10
|
|
10
11
|
from prosperity3bt.data import has_day_data
|
11
12
|
from prosperity3bt.file_reader import FileReader, FileSystemReader, PackageResourcesReader
|
12
|
-
from prosperity3bt.models import BacktestResult
|
13
|
+
from prosperity3bt.models import BacktestResult, TradeMatchingMode
|
13
14
|
from prosperity3bt.open import open_visualizer
|
14
15
|
from prosperity3bt.runner import run_backtest
|
15
16
|
|
16
17
|
|
17
|
-
def parse_algorithm(algorithm:
|
18
|
-
|
19
|
-
|
20
|
-
raise ModuleNotFoundError(f"{algorithm_path} is not a file")
|
21
|
-
|
22
|
-
sys.path.append(str(algorithm_path.parent))
|
23
|
-
return import_module(algorithm_path.stem)
|
18
|
+
def parse_algorithm(algorithm: Path) -> Any:
|
19
|
+
sys.path.append(str(algorithm.parent))
|
20
|
+
return import_module(algorithm.stem)
|
24
21
|
|
25
22
|
|
26
|
-
def parse_data(data_root: Optional[
|
23
|
+
def parse_data(data_root: Optional[Path]) -> FileReader:
|
27
24
|
if data_root is not None:
|
28
|
-
return FileSystemReader(
|
25
|
+
return FileSystemReader(data_root)
|
29
26
|
else:
|
30
27
|
return PackageResourcesReader()
|
31
28
|
|
@@ -63,9 +60,9 @@ def parse_days(file_reader: FileReader, days: list[str]) -> list[tuple[int, int]
|
|
63
60
|
return parsed_days
|
64
61
|
|
65
62
|
|
66
|
-
def parse_out(out: Optional[
|
63
|
+
def parse_out(out: Optional[Path], no_out: bool) -> Optional[Path]:
|
67
64
|
if out is not None:
|
68
|
-
return
|
65
|
+
return out
|
69
66
|
|
70
67
|
if no_out:
|
71
68
|
return None
|
@@ -174,68 +171,53 @@ def format_path(path: Path) -> str:
|
|
174
171
|
return str(path)
|
175
172
|
|
176
173
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
type=str,
|
183
|
-
nargs="+",
|
184
|
-
help="the days to backtest on (<round>-<day> for a single day, <round> for all days in a round)",
|
185
|
-
)
|
186
|
-
parser.add_argument("--merge-pnl", action="store_true", help="merge profit and loss across days")
|
187
|
-
parser.add_argument("--vis", action="store_true", help="open backtest result in visualizer when done")
|
188
|
-
parser.add_argument("--out", type=str, help="path to save output log to (defaults to backtests/<timestamp>.log)")
|
189
|
-
parser.add_argument(
|
190
|
-
"--data",
|
191
|
-
type=str,
|
192
|
-
help="path to data directory (must look similar in structure to https://github.com/jmerle/imc-prosperity-3-backtester/tree/master/prosperity3bt/resources)",
|
193
|
-
)
|
194
|
-
parser.add_argument("--print", action="store_true", help="print the trader's output to stdout while it's running")
|
195
|
-
parser.add_argument(
|
196
|
-
"--no-trades-matching", action="store_true", help="disable matching orders against market trades"
|
197
|
-
)
|
198
|
-
parser.add_argument("--no-out", action="store_true", help="skip saving the output log to a file")
|
199
|
-
parser.add_argument("--no-progress", action="store_true", help="don't show progress bars")
|
200
|
-
parser.add_argument(
|
201
|
-
"--original-timestamps",
|
202
|
-
action="store_true",
|
203
|
-
help="preserve original timestamps in output log rather than making them increase across days",
|
204
|
-
)
|
205
|
-
# parser.add_argument(
|
206
|
-
# "--no-names", action="store_true", help="don't use de-anonymized trades data, even if it exists"
|
207
|
-
# )
|
208
|
-
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {metadata.version(__package__)}")
|
209
|
-
|
210
|
-
args = parser.parse_args()
|
211
|
-
|
212
|
-
if args.vis and args.no_out:
|
213
|
-
print("Error: --vis and --no-out are mutually exclusive")
|
214
|
-
sys.exit(1)
|
174
|
+
def version_callback(value: bool) -> None:
|
175
|
+
if value:
|
176
|
+
print(f"prosperity3bt {metadata.version(__package__)}")
|
177
|
+
sys.exit(0)
|
178
|
+
|
215
179
|
|
216
|
-
|
180
|
+
app = Typer(context_settings={"help_option_names": ["--help", "-h"]})
|
181
|
+
|
182
|
+
|
183
|
+
@app.command()
|
184
|
+
def cli(
|
185
|
+
algorithm: Annotated[Path, Argument(help="Path to the Python file containing the algorithm to backtest.", show_default=False, exists=True, file_okay=True, dir_okay=False, resolve_path=True)],
|
186
|
+
days: Annotated[list[str], Argument(help="The days to backtest on. <round>-<day> for a single day, <round> for all days in a round.", show_default=False)],
|
187
|
+
merge_pnl: Annotated[bool, Option("--merge-pnl", help="Merge profit and loss across days.")] = False,
|
188
|
+
vis: Annotated[bool, Option("--vis", help="Open backtest results in https://jmerle.github.io/imc-prosperity-3-visualizer/ when done.")] = False,
|
189
|
+
out: Annotated[Optional[Path], Option(help="File to save output log to (defaults to backtests/<timestamp>.log).", show_default=False, dir_okay=False, resolve_path=True)] = None,
|
190
|
+
no_out: Annotated[bool, Option("--no-out", help="Skip saving output log.")] = False,
|
191
|
+
data: Annotated[Optional[Path], Option(help="Path to data directory. Must look similar in structure to https://github.com/jmerle/imc-prosperity-3-backtester/tree/master/prosperity3bt/resources.", show_default=False, exists=True, file_okay=False, dir_okay=True, resolve_path=True)] = None,
|
192
|
+
print_output: Annotated[bool, Option("--print", help="Print the trader's output to stdout while it's running.")] = False,
|
193
|
+
match_trades: Annotated[TradeMatchingMode, Option(help="How to match orders against market trades. 'all' matches trades with prices equal to or worse than your quotes, 'worse' matches trades with prices worse than your quotes, 'none' does not match trades against orders at all.")] = TradeMatchingMode.all,
|
194
|
+
no_progress: Annotated[bool, Option("--no-progress", help="Don't show progress bars.")] = False,
|
195
|
+
original_timestamps: Annotated[bool, Option("--original-timestamps", help="Preserve original timestamps in output log rather than making them increase across days.")] = False,
|
196
|
+
version: Annotated[bool, Option("--version", "-v", help="Show the program's version number and exit.", is_eager=True, callback=version_callback)] = False,
|
197
|
+
) -> None: # fmt: skip
|
198
|
+
if out is not None and no_out:
|
217
199
|
print("Error: --out and --no-out are mutually exclusive")
|
218
200
|
sys.exit(1)
|
219
201
|
|
220
202
|
try:
|
221
|
-
trader_module = parse_algorithm(
|
203
|
+
trader_module = parse_algorithm(algorithm)
|
222
204
|
except ModuleNotFoundError as e:
|
223
|
-
print(f"{
|
205
|
+
print(f"{algorithm} is not a valid algorithm file: {e}")
|
224
206
|
sys.exit(1)
|
225
207
|
|
226
208
|
if not hasattr(trader_module, "Trader"):
|
227
|
-
print(f"{
|
209
|
+
print(f"{algorithm} does not expose a Trader class")
|
228
210
|
sys.exit(1)
|
229
211
|
|
230
|
-
file_reader = parse_data(
|
231
|
-
|
232
|
-
output_file = parse_out(
|
212
|
+
file_reader = parse_data(data)
|
213
|
+
parsed_days = parse_days(file_reader, days)
|
214
|
+
output_file = parse_out(out, no_out)
|
233
215
|
|
234
|
-
show_progress_bars = not
|
216
|
+
show_progress_bars = not no_progress and not print_output
|
235
217
|
|
236
218
|
results = []
|
237
|
-
for round_num, day_num in
|
238
|
-
print(f"Backtesting {
|
219
|
+
for round_num, day_num in parsed_days:
|
220
|
+
print(f"Backtesting {algorithm} on round {round_num} day {day_num}")
|
239
221
|
|
240
222
|
reload(trader_module)
|
241
223
|
|
@@ -244,29 +226,33 @@ def main() -> None:
|
|
244
226
|
file_reader,
|
245
227
|
round_num,
|
246
228
|
day_num,
|
247
|
-
|
248
|
-
|
249
|
-
True,
|
229
|
+
print_output,
|
230
|
+
match_trades,
|
231
|
+
True,
|
250
232
|
show_progress_bars,
|
251
233
|
)
|
252
234
|
|
253
235
|
print_day_summary(result)
|
254
|
-
if len(
|
236
|
+
if len(parsed_days) > 1:
|
255
237
|
print()
|
256
238
|
|
257
239
|
results.append(result)
|
258
240
|
|
259
|
-
if len(
|
241
|
+
if len(parsed_days) > 1:
|
260
242
|
print_overall_summary(results)
|
261
243
|
|
262
244
|
if output_file is not None:
|
263
|
-
merged_results = reduce(lambda a, b: merge_results(a, b,
|
245
|
+
merged_results = reduce(lambda a, b: merge_results(a, b, merge_pnl, not original_timestamps), results)
|
264
246
|
write_output(output_file, merged_results)
|
265
247
|
print(f"\nSuccessfully saved backtest results to {format_path(output_file)}")
|
266
248
|
|
267
|
-
if
|
249
|
+
if vis and output_file is not None:
|
268
250
|
open_visualizer(output_file)
|
269
251
|
|
270
252
|
|
253
|
+
def main() -> None:
|
254
|
+
app()
|
255
|
+
|
256
|
+
|
271
257
|
if __name__ == "__main__":
|
272
258
|
main()
|
@@ -13,7 +13,7 @@ ObservationValue = int
|
|
13
13
|
|
14
14
|
|
15
15
|
class Listing:
|
16
|
-
def __init__(self, symbol: Symbol, product: Product, denomination:
|
16
|
+
def __init__(self, symbol: Symbol, product: Product, denomination: int):
|
17
17
|
self.symbol = symbol
|
18
18
|
self.product = product
|
19
19
|
self.denomination = denomination
|
@@ -1,4 +1,5 @@
|
|
1
1
|
from dataclasses import dataclass
|
2
|
+
from enum import Enum
|
2
3
|
from typing import Any
|
3
4
|
|
4
5
|
import orjson
|
@@ -101,3 +102,9 @@ class MarketTrade:
|
|
101
102
|
trade: Trade
|
102
103
|
buy_quantity: int
|
103
104
|
sell_quantity: int
|
105
|
+
|
106
|
+
|
107
|
+
class TradeMatchingMode(str, Enum):
|
108
|
+
all = "all"
|
109
|
+
worse = "worse"
|
110
|
+
none = "none"
|
File without changes
|
@@ -1,15 +1,21 @@
|
|
1
1
|
import os
|
2
2
|
from contextlib import closing, redirect_stdout
|
3
3
|
from io import StringIO
|
4
|
-
from typing import Any
|
5
4
|
|
6
5
|
from IPython.utils.io import Tee
|
7
6
|
from tqdm import tqdm
|
8
7
|
|
9
8
|
from prosperity3bt.data import LIMITS, BacktestData, read_day_data
|
10
|
-
from prosperity3bt.datamodel import Observation, Order, OrderDepth, Symbol, Trade, TradingState
|
9
|
+
from prosperity3bt.datamodel import Listing, Observation, Order, OrderDepth, Symbol, Trade, TradingState
|
11
10
|
from prosperity3bt.file_reader import FileReader
|
12
|
-
from prosperity3bt.models import
|
11
|
+
from prosperity3bt.models import (
|
12
|
+
ActivityLogRow,
|
13
|
+
BacktestResult,
|
14
|
+
MarketTrade,
|
15
|
+
SandboxLogRow,
|
16
|
+
TradeMatchingMode,
|
17
|
+
TradeRow,
|
18
|
+
)
|
13
19
|
|
14
20
|
|
15
21
|
def prepare_state(state: TradingState, data: BacktestData) -> None:
|
@@ -25,11 +31,7 @@ def prepare_state(state: TradingState, data: BacktestData) -> None:
|
|
25
31
|
|
26
32
|
state.order_depths[product] = order_depth
|
27
33
|
|
28
|
-
state.listings[product] =
|
29
|
-
"symbol": product,
|
30
|
-
"product": product,
|
31
|
-
"denomination": 1,
|
32
|
-
}
|
34
|
+
state.listings[product] = Listing(product, product, 1)
|
33
35
|
|
34
36
|
|
35
37
|
def create_activity_logs(
|
@@ -97,7 +99,11 @@ def enforce_limits(
|
|
97
99
|
|
98
100
|
|
99
101
|
def match_buy_order(
|
100
|
-
state: TradingState,
|
102
|
+
state: TradingState,
|
103
|
+
data: BacktestData,
|
104
|
+
order: Order,
|
105
|
+
market_trades: list[MarketTrade],
|
106
|
+
trade_matching_mode: TradeMatchingMode,
|
101
107
|
) -> list[Trade]:
|
102
108
|
trades = []
|
103
109
|
|
@@ -119,8 +125,15 @@ def match_buy_order(
|
|
119
125
|
if order.quantity == 0:
|
120
126
|
return trades
|
121
127
|
|
128
|
+
if trade_matching_mode == TradeMatchingMode.none:
|
129
|
+
return trades
|
130
|
+
|
122
131
|
for market_trade in market_trades:
|
123
|
-
if
|
132
|
+
if (
|
133
|
+
market_trade.sell_quantity == 0
|
134
|
+
or market_trade.trade.price > order.price
|
135
|
+
or (market_trade.trade.price == order.price and trade_matching_mode == TradeMatchingMode.worse)
|
136
|
+
):
|
124
137
|
continue
|
125
138
|
|
126
139
|
volume = min(order.quantity, market_trade.sell_quantity)
|
@@ -142,7 +155,11 @@ def match_buy_order(
|
|
142
155
|
|
143
156
|
|
144
157
|
def match_sell_order(
|
145
|
-
state: TradingState,
|
158
|
+
state: TradingState,
|
159
|
+
data: BacktestData,
|
160
|
+
order: Order,
|
161
|
+
market_trades: list[MarketTrade],
|
162
|
+
trade_matching_mode: TradeMatchingMode,
|
146
163
|
) -> list[Trade]:
|
147
164
|
trades = []
|
148
165
|
|
@@ -164,8 +181,15 @@ def match_sell_order(
|
|
164
181
|
if order.quantity == 0:
|
165
182
|
return trades
|
166
183
|
|
184
|
+
if trade_matching_mode == TradeMatchingMode.none:
|
185
|
+
return trades
|
186
|
+
|
167
187
|
for market_trade in market_trades:
|
168
|
-
if
|
188
|
+
if (
|
189
|
+
market_trade.buy_quantity == 0
|
190
|
+
or market_trade.trade.price < order.price
|
191
|
+
or (market_trade.trade.price == order.price and trade_matching_mode == TradeMatchingMode.worse)
|
192
|
+
):
|
169
193
|
continue
|
170
194
|
|
171
195
|
volume = min(abs(order.quantity), market_trade.buy_quantity)
|
@@ -184,11 +208,17 @@ def match_sell_order(
|
|
184
208
|
return trades
|
185
209
|
|
186
210
|
|
187
|
-
def match_order(
|
211
|
+
def match_order(
|
212
|
+
state: TradingState,
|
213
|
+
data: BacktestData,
|
214
|
+
order: Order,
|
215
|
+
market_trades: list[MarketTrade],
|
216
|
+
trade_matching_mode: TradeMatchingMode,
|
217
|
+
) -> list[Trade]:
|
188
218
|
if order.quantity > 0:
|
189
|
-
return match_buy_order(state, data, order, market_trades)
|
219
|
+
return match_buy_order(state, data, order, market_trades, trade_matching_mode)
|
190
220
|
elif order.quantity < 0:
|
191
|
-
return match_sell_order(state, data, order, market_trades)
|
221
|
+
return match_sell_order(state, data, order, market_trades, trade_matching_mode)
|
192
222
|
else:
|
193
223
|
return []
|
194
224
|
|
@@ -198,7 +228,7 @@ def match_orders(
|
|
198
228
|
data: BacktestData,
|
199
229
|
orders: dict[Symbol, list[Order]],
|
200
230
|
result: BacktestResult,
|
201
|
-
|
231
|
+
trade_matching_mode: TradeMatchingMode,
|
202
232
|
) -> None:
|
203
233
|
market_trades = {
|
204
234
|
product: [MarketTrade(t, t.quantity, t.quantity) for t in trades]
|
@@ -214,7 +244,8 @@ def match_orders(
|
|
214
244
|
state,
|
215
245
|
data,
|
216
246
|
order,
|
217
|
-
|
247
|
+
market_trades.get(product, []),
|
248
|
+
trade_matching_mode,
|
218
249
|
)
|
219
250
|
)
|
220
251
|
|
@@ -233,12 +264,12 @@ def match_orders(
|
|
233
264
|
|
234
265
|
|
235
266
|
def run_backtest(
|
236
|
-
trader
|
267
|
+
trader,
|
237
268
|
file_reader: FileReader,
|
238
269
|
round_num: int,
|
239
270
|
day_num: int,
|
240
271
|
print_output: bool,
|
241
|
-
|
272
|
+
trade_matching_mode: TradeMatchingMode,
|
242
273
|
no_names: bool,
|
243
274
|
show_progress_bar: bool,
|
244
275
|
) -> BacktestResult:
|
@@ -299,6 +330,6 @@ def run_backtest(
|
|
299
330
|
|
300
331
|
create_activity_logs(state, data, result)
|
301
332
|
enforce_limits(state, data, orders, sandbox_row)
|
302
|
-
match_orders(state, data, orders, result,
|
333
|
+
match_orders(state, data, orders, result, trade_matching_mode)
|
303
334
|
|
304
335
|
return result
|
@@ -1,6 +1,6 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: prosperity3bt
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.0
|
4
4
|
Summary: Backtester for IMC Prosperity 3 algorithms
|
5
5
|
Author-email: Jasper van Merle <jaspervmerle@gmail.com>
|
6
6
|
License: MIT License
|
@@ -37,10 +37,12 @@ Classifier: Programming Language :: Python :: 3
|
|
37
37
|
Requires-Python: >=3.9
|
38
38
|
Description-Content-Type: text/markdown
|
39
39
|
License-File: LICENSE
|
40
|
-
Requires-Dist: ipython
|
41
|
-
Requires-Dist: jsonpickle
|
42
|
-
Requires-Dist: orjson
|
43
|
-
Requires-Dist: tqdm
|
40
|
+
Requires-Dist: ipython>=8.18.1
|
41
|
+
Requires-Dist: jsonpickle>=4.0.2
|
42
|
+
Requires-Dist: orjson>=3.10.15
|
43
|
+
Requires-Dist: tqdm>=4.67.1
|
44
|
+
Requires-Dist: typer>=0.15.2
|
45
|
+
Dynamic: license-file
|
44
46
|
|
45
47
|
# IMC Prosperity 3 Backtester
|
46
48
|
|
@@ -98,16 +100,16 @@ $ prosperity3bt example/starter.py 1 --data prosperity3bt/resources
|
|
98
100
|
# Print trader's output to stdout while running
|
99
101
|
# This may be helpful when debugging a broken trader
|
100
102
|
$ prosperity3bt example/starter.py 1 --print
|
101
|
-
|
102
|
-
# Only match orders against order depths, not against market trades
|
103
|
-
$ prosperity3bt example/starter.py 1 --no-trades-matching
|
104
103
|
```
|
105
104
|
|
106
105
|
## Order Matching
|
107
106
|
|
108
|
-
Orders placed by `Trader.run` at a given timestamp are matched against the order depths and market trades of that timestamp's state. Order depths take priority, if an order can be filled completely using volume in the relevant order depth, market trades are not considered. If not, the backtester matches your order against the timestamp's market trades,
|
107
|
+
Orders placed by `Trader.run` at a given timestamp are matched against the order depths and market trades of that timestamp's state. Order depths take priority, if an order can be filled completely using volume in the relevant order depth, market trades are not considered. If not, the backtester matches your order against the timestamp's market trades. In this case the backtester assumes that for each trade, the buyer and the seller of the trade are willing to trade with you instead at the trade's price and volume. Market trades are matched at the price of your orders, e.g. if you place a sell order for €9 and there is a market trade for €10, the sell order is matched at €9 (even though there is a buyer willing to pay €10, this appears to be consistent with what the official Prosperity environment does).
|
109
108
|
|
110
|
-
|
109
|
+
Matching orders against market trades can be configured through the `--match-trades` option:
|
110
|
+
- `--match-trades all` (default): match market trades with prices equal to or worse than your quotes.
|
111
|
+
- `--match-trades worse`: match market trades with prices worse than your quotes, inspired by [team Linear Utility's Prosperity 2 write-up](https://github.com/ericcccsliu/imc-prosperity-2).
|
112
|
+
- `--match-trades none`: do not match market trades against orders.
|
111
113
|
|
112
114
|
Limits are enforced before orders are matched to order depths. If for a product your position would exceed the limit, assuming all your orders would get filled, all your orders for that product get canceled.
|
113
115
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
[project]
|
2
2
|
name = "prosperity3bt"
|
3
3
|
description = "Backtester for IMC Prosperity 3 algorithms"
|
4
|
-
version = "0.
|
4
|
+
version = "0.4.0"
|
5
5
|
readme = "README.md"
|
6
6
|
license = {file = "LICENSE"}
|
7
7
|
authors = [{name = "Jasper van Merle", email = "jaspervmerle@gmail.com"}]
|
@@ -14,7 +14,13 @@ classifiers = [
|
|
14
14
|
"Programming Language :: Python :: 3",
|
15
15
|
]
|
16
16
|
requires-python = ">= 3.9"
|
17
|
-
dependencies = [
|
17
|
+
dependencies = [
|
18
|
+
"ipython>=8.18.1",
|
19
|
+
"jsonpickle>=4.0.2",
|
20
|
+
"orjson>=3.10.15",
|
21
|
+
"tqdm>=4.67.1",
|
22
|
+
"typer>=0.15.2",
|
23
|
+
]
|
18
24
|
|
19
25
|
[build-system]
|
20
26
|
requires = ["setuptools >= 61.0"]
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{prosperity3bt-0.2.0 → prosperity3bt-0.4.0}/prosperity3bt/resources/round0/prices_round_0_day_-2.csv
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|