Q22 Quick Start S&P500¶
Introduction to S&P500 stocks, a quick-start example strategy that dynamically selects low-volatility stocks and allocates capital based on their transaction volume.
You can clone and edit this example there (tab Examples).
Introduction to S&P500 stocks¶
This template demonstrates how to build a trading strategy, specifically designed for use with a dataset containing historical constituents of the S&P 500. The strategy can only trade stocks that are part of the S&P 500 index, and only when the stock is considered liquid. A company is deemed liquid if it is included in the S&P 500 at the observed point in time.
The strategy takes a low-risk approach by allocating weights to stocks based on their volatility. It assigns capital only when the ratio of the Average True Range (ATR(14)) to the closing price is below 0.0205, signaling lower volatility. The allocation is further refined by the stock's transaction money flow, where stocks with higher liquidity and trading volume receive a larger portion of the capital (weight). This approach ensures that more capital is directed towards assets with higher market activity, while also maintaining a risk-aware allocation.
Important Considerations:
- Trade only on historical S&P 500 stocks, and only when they are considered liquid (i.e., part of the index at the time of observation).
- The in-sample period begins on 2006-01-01. Earlier data may be used for testing and training purposes.
- The strategy must achieve a minimum in-sample Sharpe Ratio of 0.70 to be considered valid.
- The maximum allocation to any single asset is capped at 10% of total capital. If a weight exceeds this, it will be limited to 0.1.
- Manual stock selection or direct hand-picking is not permitted. The allocation process must be automatic.
- The strategy can open both long and short positions.
For official Q22 rules, click here.
Accessing the S&P 500 Stocks Dataset - Data loading¶
For the Q22 contest, a new dataset of S&P 500 stocks is available. You can obtain this dataset by calling the function stocks_load_spx_data() from the data module:
snp_stocks_data = qndata.stocks_load_spx_data(min_date='2005-06-01')
To check the list of S&P 500 companies starting from 2006-01-01, along with additional stock information, use the load_list() function:
snp_stocks_list = qndata.stocks_load_spx_list()
Quantiacs also provides data for other instruments, such as cryptocurrencies, indices, futures, as well as additional fundamental stock data and macroeconomic data. Note that there is also a Nasdaq-100 dataset available, and it is not a subset of the S&P 500. However, there is a significant overlap between the two.
The data is provided in xarray.DataArray format. Check here for more details on manipulating xarray data.
Technical analysis - indicators
Once the data is loaded, various technical indicators from the qnt.ta module can be used to generate trading signals. For a complete list of indicators and examples of their implementation, check the documentation page.
Trading strategy - algorithm¶
# Necessary imports
import xarray as xr
import numpy as np
import qnt.stats as qnstats
import qnt.data as qndata
import qnt.output as qnout
import qnt.ta as qnta
import qnt.backtester as qnbt
import qnt.graph as qngraph
def strategy(data, state=None, mfsp=135, limit=0.0205): # parameter state is used in the backtester (multi-pass) for stateful strategies
vol = data.sel(field="vol") ### Volume values
liq = data.sel(field="is_liquid") ### Liquidity values, 1.0 or 0.0 -> True or False
close = data.sel(field="close") ### close prices
high = data.sel(field="high") ### daily high
low = data.sel(field="low") ### daily low
atrs = qnta.atr(high=high, low=low, close=close, ma=14) ### Average True Range of 14 bars (working days)
ratio = atrs / close ### indicator
weights = xr.where(ratio > limit, 0, 1) ### Strategy condition - zero weight if ratio is bigger than limit
money_vol = vol * liq * close ### Daily money flow per liquid asset
total_money_vol = money_vol.sum(dim='asset', skipna=True) ### Total daily money flow of liquid assets
money_vol_share = money_vol / total_money_vol ### Daily money flow share per liquid asset
mvs_mov = qnta.sma(money_vol_share, mfsp) ### weights allocation by average money flow share in period of 135 days
return mvs_mov * weights
Weights - Single / Multi pass approach¶
In Single pass backtesting, which is significantly faster, weights are calculated using the entire dataset in a single run.
Multi pass backtesting evaluates weights on a day-by-day basis by slicing the dataset for each individual day.
If applicable, we recommend using the single pass approach for efficiency, while verifying strategy statistics with the multi pass backtester. If the statistics from single pass and multi pass match exactly, it indicates that forward-looking bias has likely not been introduced, and the strategy can be confidently submitted as single pass.
In rare cases, even if results are identical between single and multi pass, forward-looking bias might unintentionally occur (e.g., by incorporating global data variables into the logic). Such issues are generally mitigated in production but can result in discrepancies in statistics comparing development and production results.
### SINGLE PASS ###
## The min_date parameter is set to '2005-06-01' to ensure that the dataset includes at least 135 bars (approximately 190 days) of data,
## which is necessary for creating indicators and determining weights for the strategy.
snp_stocks_data = qndata.stocks_load_spx_data(min_date='2005-06-01')
weights = strategy(snp_stocks_data)
weights = qnout.clean(weights, snp_stocks_data) # fix liquidity
0% (0 of 367973) | | Elapsed Time: 0:00:00 ETA: --:--:--
100% (367973 of 367973) |################| Elapsed Time: 0:00:00 Time: 0:00:00
0% (0 of 130300) | | Elapsed Time: 0:00:00 ETA: --:--:--
100% (130300 of 130300) |################| Elapsed Time: 0:00:00 Time: 0:00:00
0% (0 of 13133580) | | Elapsed Time: 0:00:00 ETA: --:--:--
22% (3020705 of 13133580) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
44% (5910075 of 13133580) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
67% (8930780 of 13133580) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
88% (11688815 of 13133580) |########## | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133580 of 13133580) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 1/22 1s
0% (0 of 13133580) | | Elapsed Time: 0:00:00 ETA: --:--:--
20% (2758035 of 13133580) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
43% (5778740 of 13133580) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
65% (8668110 of 13133580) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
87% (11557480 of 13133580) |########## | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133580 of 13133580) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 2/22 2s
0% (0 of 13133580) | | Elapsed Time: 0:00:00 ETA: --:--:--
6% (919345 of 13133580) | | Elapsed Time: 0:00:00 ETA: 00:00:00
12% (1707355 of 13133580) |# | Elapsed Time: 0:00:00 ETA: 0:00:00
20% (2758035 of 13133580) |## | Elapsed Time: 0:00:00 ETA: 0:00:00
43% (5778740 of 13133580) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
69% (9193450 of 13133580) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
91% (12082820 of 13133580) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133580 of 13133580) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 3/22 4s
0% (0 of 13133580) | | Elapsed Time: 0:00:00 ETA: --:--:--
25% (3414710 of 13133580) |### | Elapsed Time: 0:00:00 ETA: 00:00:00
44% (5910075 of 13133580) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
62% (8274105 of 13133580) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
81% (10769470 of 13133580) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
98% (13002165 of 13133580) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133580 of 13133580) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 4/22 5s
0% (0 of 13133576) | | Elapsed Time: 0:00:00 ETA: --:--:--
12% (1707355 of 13133576) |# | Elapsed Time: 0:00:00 ETA: 00:00:00
27% (3677380 of 13133576) |### | Elapsed Time: 0:00:00 ETA: 0:00:00
40% (5384735 of 13133576) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
65% (8668110 of 13133576) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
94% (12476825 of 13133576) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133576 of 13133576) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 5/22 6s
0% (0 of 13133580) | | Elapsed Time: 0:00:00 ETA: --:--:--
13% (1838690 of 13133580) |# | Elapsed Time: 0:00:00 ETA: 00:00:00
27% (3677380 of 13133580) |### | Elapsed Time: 0:00:00 ETA: 0:00:00
43% (5778740 of 13133580) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
59% (7880100 of 13133580) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
75% (9981460 of 13133580) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
93% (12345490 of 13133580) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133580 of 13133580) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 6/22 7s
0% (0 of 13133580) | | Elapsed Time: 0:00:00 ETA: --:--:--
24% (3283375 of 13133580) |### | Elapsed Time: 0:00:00 ETA: 00:00:00
45% (6041410 of 13133580) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
65% (8668110 of 13133580) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
86% (11426145 of 13133580) |########## | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133580 of 13133580) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 7/22 9s
0% (0 of 13133552) | | Elapsed Time: 0:00:00 ETA: --:--:--
20% (2758035 of 13133552) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
37% (4990730 of 13133552) |#### | Elapsed Time: 0:00:00 ETA: 0:00:00
55% (7354760 of 13133552) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
72% (9587455 of 13133552) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
91% (12082820 of 13133552) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133552 of 13133552) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 8/22 10s
0% (0 of 13133552) | | Elapsed Time: 0:00:00 ETA: --:--:--
15% (2101360 of 13133552) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
34% (4596725 of 13133552) |#### | Elapsed Time: 0:00:00 ETA: 0:00:00
55% (7354760 of 13133552) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
77% (10244130 of 13133552) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
97% (12870830 of 13133552) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133552 of 13133552) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 9/22 12s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
17% (2364012 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
35% (4728024 of 13133476) |#### | Elapsed Time: 0:00:00 ETA: 0:00:00
48% (6435366 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
62% (8274042 of 13133476) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
74% (9850050 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
93% (12345396 of 13133476) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 10/22 13s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
18% (2495346 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
35% (4728024 of 13133476) |#### | Elapsed Time: 0:00:00 ETA: 0:00:00
49% (6566700 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
63% (8405376 of 13133476) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
77% (10244052 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 11/22 14s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
15% (2101344 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
29% (3940020 of 13133476) |### | Elapsed Time: 0:00:00 ETA: 0:00:00
43% (5778696 of 13133476) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
58% (7748706 of 13133476) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
79% (10506720 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 12/22 16s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
15% (2101344 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
30% (4071354 of 13133476) |#### | Elapsed Time: 0:00:00 ETA: 0:00:00
43% (5778696 of 13133476) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
56% (7486038 of 13133476) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
72% (9587382 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
89% (11820060 of 13133476) |########## | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 13/22 17s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
16% (2232678 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
34% (4596690 of 13133476) |#### | Elapsed Time: 0:00:00 ETA: 0:00:00
46% (6172698 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
60% (8011374 of 13133476) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
72% (9587382 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
86% (11426058 of 13133476) |########## | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 14/22 19s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
20% (2758014 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
40% (5384694 of 13133476) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
67% (8930712 of 13133476) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
96% (12739398 of 13133476) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 15/22 21s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
7% (1050672 of 13133476) |# | Elapsed Time: 0:00:00 ETA: 00:00:00
10% (1444674 of 13133476) |# | Elapsed Time: 0:00:00 ETA: 0:00:00
15% (2101344 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 0:00:00
49% (6566700 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
72% (9587382 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 16/22 22s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
20% (2758014 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
41% (5516028 of 13133476) |##### | Elapsed Time: 0:00:00 ETA: 0:00:00
67% (8930712 of 13133476) |######## | Elapsed Time: 0:00:00 ETA: 0:00:00
93% (12345396 of 13133476) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 17/22 23s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
27% (3677352 of 13133476) |### | Elapsed Time: 0:00:00 ETA: 00:00:00
55% (7354704 of 13133476) |####### | Elapsed Time: 0:00:00 ETA: 0:00:00
77% (10244052 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 18/22 25s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
26% (3546018 of 13133476) |### | Elapsed Time: 0:00:00 ETA: 00:00:00
49% (6566700 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
69% (9193380 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 19/22 26s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
18% (2495346 of 13133476) |## | Elapsed Time: 0:00:00 ETA: 00:00:00
48% (6435366 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
77% (10244052 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
91% (12082728 of 13133476) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 20/22 27s
0% (0 of 13133476) | | Elapsed Time: 0:00:00 ETA: --:--:--
27% (3677352 of 13133476) |### | Elapsed Time: 0:00:00 ETA: 00:00:00
49% (6566700 of 13133476) |###### | Elapsed Time: 0:00:00 ETA: 0:00:00
72% (9587382 of 13133476) |######### | Elapsed Time: 0:00:00 ETA: 0:00:00
96% (12739398 of 13133476) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (13133476 of 13133476) |############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 21/22 28s
0% (0 of 8426696) | | Elapsed Time: 0:00:00 ETA: --:--:--
53% (4550364 of 8426696) |####### | Elapsed Time: 0:00:00 ETA: 00:00:00
100% (8426696 of 8426696) |##############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 22/22 29s
Data loaded 30s
Output cleaning...
fix uniq
ffill if the current price is None...
Check liquidity...
WARNING! Strategy trades non-liquid assets.
Fix liquidity...
Ok.
Check missed dates...
Ok.
Normalization...
Output cleaning is complete.
When using the backtester for weight calculation, there is no need to manually load data or define a "load_data" function as input. It is only required to specify the competition_type (in this case, "stocks_s&p500"), and the corresponding data will be loaded automatically by default.
### MULTI PASS ###
w=qnbt.backtest(
competition_type="stocks_s&p500",
lookback_period=250,
start_date="2006-01-01",
strategy=strategy,
analyze=True,
)
Run last pass...
Load data...
0% (0 of 130300) | | Elapsed Time: 0:00:00 ETA: --:--:--
100% (130300 of 130300) |################| Elapsed Time: 0:00:00 Time: 0:00:00
0% (0 of 9357004) | | Elapsed Time: 0:00:00 ETA: --:--:--
39% (3742800 of 9357004) |##### | Elapsed Time: 0:00:00 ETA: 00:00:00
79% (7485600 of 9357004) |########### | Elapsed Time: 0:00:00 ETA: 0:00:00
100% (9357004 of 9357004) |##############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 1/1 16s
Data loaded 16s
Run strategy...
Can't load state. [Errno 2] No such file or directory: '/root/state.in.pickle.gz'
Load data for cleanup...
ERROR:root:download error: data
Traceback (most recent call last):
File "/usr/local/lib/python3.10/site-packages/qnt/data/common.py", line 79, in request_with_retry
with urllib.request.urlopen(req, timeout=TIMEOUT) as response:
File "/usr/local/lib/python3.10/urllib/request.py", line 216, in urlopen
return opener.open(url, data, timeout)
File "/usr/local/lib/python3.10/urllib/request.py", line 519, in open
response = self._open(req, data)
File "/usr/local/lib/python3.10/urllib/request.py", line 536, in _open
result = self._call_chain(self.handle_open, protocol, protocol +
File "/usr/local/lib/python3.10/urllib/request.py", line 496, in _call_chain
result = func(*args)
File "/usr/local/lib/python3.10/urllib/request.py", line 1377, in http_open
return self.do_open(http.client.HTTPConnection, req)
File "/usr/local/lib/python3.10/urllib/request.py", line 1352, in do_open
r = h.getresponse()
File "/usr/local/lib/python3.10/http/client.py", line 1375, in getresponse
response.begin()
File "/usr/local/lib/python3.10/http/client.py", line 318, in begin
version, status, reason = self._read_status()
File "/usr/local/lib/python3.10/http/client.py", line 287, in _read_status
raise RemoteDisconnected("Remote end closed connection without"
http.client.RemoteDisconnected: Remote end closed connection without response
0% (0 of 2334404) | | Elapsed Time: 0:00:00 ETA: --:--:--
100% (2334404 of 2334404) |##############| Elapsed Time: 0:00:00 Time: 0:00:00
fetched chunk 1/1 42s
Data loaded 42s
Output cleaning...
fix uniq
ffill if the current price is None...
Check liquidity...
Ok.
Check missed dates...
Ok.
Normalization...
Output cleaning is complete.
Write result...
Write output: /root/fractions.nc.gz
Statistics¶
While the multi pass approach automatically displays the strategy’s statistics through the backtester, single pass mode only calculates the weights, so visualizing the stats must be done manually. You can use the helper print_stats function for this purpose:
def print_stats(stat):
display(stat.sel(time=slice('2006-01-01', None)).to_pandas().tail(10))
performance = stat.to_pandas()["equity"]
qngraph.make_plot_filled(performance.index, performance, name="PnL (Equity)", type="log")
def get_sharpe(stat):
return stat.isel(time=-1).sel(field="sharpe_ratio").item()
stat = qnstats.calc_stat(snp_stocks_data, weights.sel(time=slice('2006-01-01', None)))
print_stats(stat)
| field | equity | relative_return | volatility | underwater | max_drawdown | sharpe_ratio | mean_return | bias | instruments | avg_turnover | avg_holding_time |
|---|---|---|---|---|---|---|---|---|---|---|---|
| time | |||||||||||
| 2026-04-06 | 2.150284 | -0.000075 | 0.045925 | -0.003003 | -0.072077 | 0.840348 | 0.038593 | 1.0 | 699.0 | 0.028688 | 27.306563 |
| 2026-04-07 | 2.149240 | -0.000486 | 0.045921 | -0.003487 | -0.072077 | 0.839715 | 0.038560 | 1.0 | 699.0 | 0.028685 | 27.306645 |
| 2026-04-08 | 2.150390 | 0.000535 | 0.045916 | -0.002954 | -0.072077 | 0.840227 | 0.038580 | 1.0 | 699.0 | 0.028682 | 27.303280 |
| 2026-04-09 | 2.151380 | 0.000461 | 0.045912 | -0.002495 | -0.072077 | 0.840655 | 0.038596 | 1.0 | 699.0 | 0.028678 | 27.301789 |
| 2026-04-10 | 2.150033 | -0.000626 | 0.045907 | -0.003119 | -0.072077 | 0.839863 | 0.038556 | 1.0 | 699.0 | 0.028675 | 27.299018 |
| 2026-04-13 | 2.149329 | -0.000327 | 0.045903 | -0.003446 | -0.072077 | 0.839409 | 0.038532 | 1.0 | 699.0 | 0.028672 | 27.297893 |
| 2026-04-14 | 2.149309 | -0.000009 | 0.045899 | -0.003455 | -0.072077 | 0.839313 | 0.038523 | 1.0 | 699.0 | 0.028668 | 27.297173 |
| 2026-04-15 | 2.148704 | -0.000282 | 0.045894 | -0.003736 | -0.072077 | 0.838911 | 0.038501 | 1.0 | 699.0 | 0.028664 | 27.296259 |
| 2026-04-16 | 2.149071 | 0.000171 | 0.045890 | -0.003565 | -0.072077 | 0.839017 | 0.038502 | 1.0 | 699.0 | 0.028660 | 27.296221 |
| 2026-04-17 | 2.149828 | 0.000352 | 0.045885 | -0.003214 | -0.072077 | 0.839324 | 0.038513 | 1.0 | 699.0 | 0.028655 | 27.334677 |
get_sharpe(stat)
0.8393244906625948
Submit strategy to the competition¶
Use Submit button on my strategies page
Make sure that qnout.write(weights) has been added to cell, and the weights have been written. It is not required when using Multi pass backtester.
Note: After submitting the strategy to the contest, any weight exceeding 0.1 will be capped at that limit. You can apply weight normalization functions before submission, which may be more suitable, such as those that maintain the ratio between allocated weights. Details on these functions can be found here.
### Run this cell to get all weights below 0.1. This "hard cut" way is used on production if any weight exceeds 0.1.
### Any other normalization method can be used.
import qnt.exposure as qnexp
weights_capped = qnexp.cut_big_positions(weights=weights, max_weight=0.1)
Compare weights before and after normalization. Check the statistics again, it can change not only in negative way.
qnout.write(weights_capped)
Write output: /root/fractions.nc.gz
Common Reasons for Submission Rejection and Their Solutions¶
Here are some of the frequent reasons causing submission rejection in algorithmic trading competitions, and their corresponding remedies.
1) Missed call to write_output
Save algorithm weights, run code
qnt.output.write(weights)
2) Not eligible send to contest. In-Sample Sharpe must be larger than 0.7
Improve your algorithm. For example, you can use sections and get an algorithm that will pass the filter
- Example Trading System Optimization
- Example of a strategy using technical analysis indicators
Need help? Check the Documentation and find solutions/report problems in the Forum section.
3) Not enough bid information.
Run code
min_time = weights.time[abs(weights).fillna(0).sum('asset')> 0].min()
min_time
min_time must be less than or equal to January 1, 2006.
If min_time is larger than the starting date, we recommend to fill the starting values of the time series with non-vanishing values, for example a simple buy-and-hold strategy.
def get_enough_bid_for(data_, weights_):
time_traded = weights_.time[abs(weights_).fillna(0).sum('asset') > 0]
is_strategy_traded = len(time_traded)
if is_strategy_traded:
return xr.where(weights_.time < time_traded.min(), data_.sel(field="is_liquid"), weights_)
return weights_
weights_new = get_enough_bid_for(data, weights)
weights_new = weights_new.sel(time=slice("2006-01-01",None))