0% found this document useful (0 votes)
3 views22 pages

Python Competition Notes

The WorldQuant Python Alphas Competition 2026 invites participants to create Python functions that generate daily trading signals for 3000 stocks, with a prize pool of $30,000. The competition emphasizes maximizing the Sharpe Ratio as the primary scoring metric, and participants must iterate on their alpha functions using BrainLabs before submitting for actual simulation. Key concepts include understanding data types, using NumPy for calculations, and adhering to specific coding rules to ensure successful submissions.

Uploaded by

violetoketch851
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views22 pages

Python Competition Notes

The WorldQuant Python Alphas Competition 2026 invites participants to create Python functions that generate daily trading signals for 3000 stocks, with a prize pool of $30,000. The competition emphasizes maximizing the Sharpe Ratio as the primary scoring metric, and participants must iterate on their alpha functions using BrainLabs before submitting for actual simulation. Key concepts include understanding data types, using NumPy for calculations, and adhering to specific coding rules to ensure successful submissions.

Uploaded by

violetoketch851
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd

WORLDQUANT BRAIN

Python Alphas Competition 2026


Complete Beginner to Competitor Study Notes

Competition Period: June 1 – July 12, 2026 | Prize Pool: $30,000


Region: USA D1 | Language: Python | Universe: TOP3000
SECTION 1 — The Big Picture

1.1 What is a Quantitative Alpha?


An alpha is simply a daily signal that tells a trading system which stocks to buy, which to sell, and how strongly.
You are NOT building a trading bot or managing real money. You are writing a Python function that looks at
market data and outputs a score for each of 3000 stocks — every single trading day.

Positive score → buy that stock


Negative score → sell that stock
Larger number → stronger conviction

The competition rewards you when your alpha signal correctly predicts which stocks will go up and which will go
down. Brain handles all the trading mechanics — your entire job is producing the best signal.

1.2 What Brain Does With Your Signal


Step What It Does Setting

Your alpha function Outputs a score for each of


3000 stocks daily

Neutralization Removes overall market bias MARKET or SECTOR


from your scores

Pasteurization Masks stocks not in the ON or OFF


active universe

Scaling Normalizes positions to a Automatic


unit book size

Truncation Caps max position size per 0.8 typical


stock

Simulation Trades and measures PnL BrainLabs / Actual


over historical data

1.3 How You Are Scored


After simulation, Brain reports these metrics. Your goal is to maximise Sharpe Ratio above everything else:

Metric Meaning Target

Sharpe Ratio Return divided by risk. The > 1.5 is solid. > 2.0 is
primary metric. excellent.
Ann. Return Annualised profit Higher is better.
percentage.

Max Drawdown Worst peak-to-trough loss Less negative is better.


period.

Turnover (Daily) How often your positions Moderate — too high hurts.
change.

Fitness Brain’s composite score. Must pass threshold.

⚠️ SHARPE IS KING
Every technique you learn in these notes has one purpose — improve your Sharpe Ratio. A Sharpe above 1.5
is competitive. Above 2.0 is excellent. Focus all iteration on moving this number up.

1.4 The Competition Workflow


You will repeat this loop dozens of times between now and July 12:

1. Write or modify your alpha function


2. Test it locally with BrainLabs — fast, takes seconds
3. Check the Sharpe. If good, validate with Actual Simulation
4. Submit your best alphas to Brain before the deadline

💡 KEY RULE FROM DOCS


Develop and iterate using BrainLabs simulation (fast feedback), then validate your best Alpha with actual
simulation before submission. Never waste remote simulation quota on unfinished ideas.
SECTION 2 — Python Fundamentals for Brain
You do not need to know everything about Python. You only need the building blocks that appear in every alpha.
This section covers exactly those.

2.1 Variables — Storing Information


A variable is a named box that holds a value. The = sign means store this value — it is NOT the equals sign from
maths.

name = "mean_reversion" # text (called a string)


delay = 1 # whole number (int)
sharpe = 1.84 # decimal number (float)
active = True # True or False (bool)

2.2 Data Types — Critical for Brain


Brain is extremely strict about data types. Understanding these four types now will save you hours of debugging
later:

Type Description
int Whole numbers: 1, 0, -5, 3000
float Decimal numbers: 1.84, -0.05, 0.0004
float32 A compact 32-bit decimal. Brain requires this for
your output.
float64 A 64-bit decimal. NumPy defaults to this — must
convert to float32.
bool True or False only.
str Text in quotes: "returns", "USA", "MARKET".

🔴 CRITICAL: The float32 Rule


Your alpha function MUST return a float32 array. NumPy operations like [Link] silently upgrade float32
to float64. If you forget to convert back, Brain raises: "Alpha vector is not float32". Always end
with .astype(np.float32).

2.3 Lists — Multiple Values in One Box


A list holds multiple values. Indexing starts at 0. Negative indices count from the end. You will use this constantly
with Brain data.
returns = [0.01, -0.02, 0.005, 0.03, -0.01]

returns[0] # 0.01 ← first item (index 0, NOT 1)


returns[-1] # -0.01 ← last item (most recent day)
returns[-2] # 0.03 ← second to last (yesterday)
returns[1:3] # [-0.02, 0.005] ← slice: from index 1 up to (not including) 3

💡 Why This Matters for Brain


In your alpha, [Link][-1] gives you TODAY's returns for all 3000 stocks. [Link][-2] gives you
yesterday. [Link] gives you the full window. This exact pattern is in every alpha you will write.

2.4 Functions — Reusable Instructions


A function takes inputs, does something, and returns an output. Your entire alpha is one function. Helper
functions like pasteurize and neutralize are also functions you define.

def greet(name): # def = define. name = input parameter


message = "Hello " + name
return message # return = send back the result

result = greet("Brain") # result = "Hello Brain"

Key parts: def (define), function name, parameters in brackets, return at the end.

2.5 Importing Libraries


Libraries are pre-built toolkits. You bring them in with import. Brain uses two main libraries:

import numpy as np # The maths toolkit — used for EVERYTHING


import [Link] as npt # Type hints for NumPy arrays

# The "as np" gives it a short nickname


# So you write [Link]() instead of [Link]()

💡 Import Rule for Brain Submission


When submitting to Brain, only "from [Link] import alpha" is allowed in the same cell as your @alpha
function. Do NOT include Brain, BrainCache, or SimulationSettings imports in that cell — your submission will
fail.
SECTION 3 — NumPy — The Engine Behind Every Alpha
NumPy is a Python library for fast maths on large arrays of numbers. Every piece of data in Brain is a NumPy
array. Every operation you perform on stock data uses NumPy functions. This section is not optional — it is the
core skill.

3.1 What is a NumPy Array?


A NumPy array is like a Python list, but faster, more powerful, and built for maths. Unlike a list, every item in an
array must be the same data type.

import numpy as np

# 1D array — one row of numbers


prices = [Link]([100.0, 105.0, 98.0, 110.0, 107.0])
print([Link]) # (5,) ← 5 items, 1 dimension
print([Link]) # float64

3.2 2D Arrays — The Shape of Brain Data


Brain data arrives as a 2D array: rows are dates (time), columns are stocks (instruments). This is the most
important concept in these notes.

# Imagine this is [Link] with lookback=2


# Shape: [3 rows, 4 stocks]

[Link] = [Link]([
[ 0.01, -0.02, 0.005, 0.03 ], # row 0: 3 days ago
[-0.01, 0.03, -0.010, 0.01 ], # row 1: 2 days ago
[ 0.02, -0.01, 0.015, -0.02 ], # row 2: TODAY (most recent)
])

[Link] # (3, 4) ← 3 dates, 4 instruments


[Link][-1] # [ 0.02, -0.01, 0.015, -0.02] ← today only
[Link][:, 0] # [ 0.01, -0.01, 0.02] ← stock 0, all dates

3.3 axis=0 vs axis=1 — The Most Important Concept


The axis parameter tells NumPy WHICH direction to perform an operation. This single concept explains most of
the maths in every alpha.

axis Direction Result

axis=0 Collapse rows → operate Result shape:


DOWN the columns (across [n_instruments]
time)
axis=1 Collapse columns → operate Result shape: [n_dates]
ACROSS the rows (across
stocks)

# [Link] shape: [3 dates, 4 stocks]

[Link]([Link], axis=0)
# → ONE number per STOCK (average return of each stock over time)
# Result shape: (4,) ← this is what we want for an alpha signal!

[Link]([Link], axis=1)
# → ONE number per DATE (average return across all stocks that day)
# Result shape: (3,) ← NOT what we want for alpha output

💡 Why axis=0 in Every Alpha


Your alpha must return ONE signal per STOCK. axis=0 collapses the time dimension and gives you one value
per stock. That is why the docs always use axis=0. [Link]([Link], axis=0) means "for each stock,
calculate the average return across all historical days in the window."

3.4 Essential NumPy Functions for Brain


Function Purpose
[Link](a, axis=0) Mean ignoring NaN values. Use instead of [Link] —
stock data has gaps.
[Link](a, axis=0) Standard deviation ignoring NaN. Used for
volatility calculations.
[Link](a, axis=0) Sum ignoring NaN values.
np.nan_to_num(a, nan=0) Replace NaN with 0 (or any value). Used before
division.
[Link](a) Returns True where values are NaN. Used for
masking.
[Link](a) Returns indices that would sort the array. Used
for ranking.
[Link](a, L1 norm — sum of absolute values. Used for scaling
ord=1) positions.
[Link](np.float32) Convert array dtype. ALWAYS do this before
returning from alpha.
[Link]() Create a separate copy. ALWAYS do this before
modifying Brain data.
[Link](n) Array of n zeros. Used to initialize store
variables.
[Link]([a, b]) Stack arrays vertically (add rows). Used for rank
caching.
[Link](cond, x, y) Choose x where condition is True, else y. Used for
rank calculations.
3.5 NaN — Missing Data in Brain
NaN stands for "Not a Number". It represents missing data. In Brain, stocks that have no data for a given day
have NaN in their field. This is extremely common — every alpha must handle NaN correctly.

import numpy as np

# NaN contamination — normal mean FAILS with missing data


returns = [Link]([0.01, [Link], 0.03, -0.02])
[Link](returns) # nan ← one NaN poisons the whole result!
[Link](returns) # 0.006667 ← ignores NaN, correct result

# Always use nan-safe functions in Brain alphas

🔴 Never Use [Link] in a Brain Alpha


Always use [Link], [Link], [Link] instead of [Link], [Link], [Link]. Stock data always has
NaN gaps. One NaN in the input turns the entire output into NaN, which breaks your alpha signal.
SECTION 4 — The Brain Setup
Every Brain session starts with the same three lines. Understanding exactly what each line does will prevent
confusion later.

4.1 The Three Setup Lines


from brain import Brain, BrainCache
from [Link] import SimulationSettings
from [Link] import alpha
import numpy as np
import [Link] as npt

brain = Brain(raw=True)
cache = BrainCache(brain)

4.2 Brain(raw=True) — The Connection


Brain() is the main connection to WorldQuant's data platform. It defaults to:

Parameter Default
instrument_type 'EQUITY' — we are trading stocks
region 'USA' — US stock market
delay 1 — one day execution delay (realistic)
universe 'TOP3000' — the 3000 largest US stocks

raw=True — tells Brain to give you data in its native format (float32, int32). Without raw=True, everything
converts to float64, which does not match the real simulation environment. Always use raw=True.

4.3 BrainCache — Speed Optimisation


BrainCache wraps Brain and stores downloaded data in memory so you do not re-download the same data
repeatedly during simulation. Pass it to every simulation call.

⚠️ When to Create a New BrainCache


Create a new BrainCache whenever you change region, delay, or date range. Cached data is tied to those
parameters. If you change settings but keep the old cache, you will get wrong results silently.

# If you change region — create a new cache


brain_eur = Brain(raw=True) # same Brain instance is fine
cache_eur = BrainCache(brain_eur) # new cache for EUR region
# BrainCache is for simulation only
# For exploring data, use brain.get_data_frame() directly:
df = brain.get_data_frame("returns")
print([Link]) # (n_dates, n_instruments)
print([Link]) # check the data types
SECTION 5 — The @alpha Decorator
The @alpha decorator is the heart of everything. Every alpha you submit must use it. It declares what data your
alpha needs and what state it remembers between time steps.

5.1 The Full Structure


from [Link] import alpha
import numpy as np
import [Link] as npt

@alpha(
data=["returns"], # which data fields to load
store=[], # what state to remember (empty for simple alphas)
)
def mean_reversion(data, store) -> [Link][np.float32]:
# [Link] — shape [lookback+1, n_instruments], float32
# [Link] — always available, int: 1=in universe, 0=out

signal = -[Link]([Link], axis=0)


return [Link](np.float32)

5.2 The data Parameter — Declaring Your Inputs


data is a list of field names. Each field becomes available inside your function as [Link], shaped
[lookback+1, n_instruments].

Declaration Effect
data=["returns"] Load only daily returns (most common)
data=["returns", Load returns AND closing prices
"close"]
data=["returns", Load returns AND trading volume
"volume"]
data=[] Load no fields (unusual, rarely used)

🔴 RULE: Never include "universe" in data


"universe" is automatically available as [Link] in every alpha. If you add it to data=[], Brain raises an
error. The docs are explicit: "Do not include universe in your data list."

5.3 Accessing Data Inside Your Alpha


# [Link] shape: [lookback+1, n_instruments]
# With lookback=5: shape is [6, 3000]
[Link][-1] # TODAY's returns — shape: [3000]
[Link][-2] # Yesterday's returns — shape: [3000]
[Link] # Full window — shape: [6, 3000]

# universe: 1 = stock is in universe today, 0 = it is not


[Link][-1] # Today's universe mask — shape: [3000]
# Values: 1 or 0 for each stock

⚠️ Data Arrays Are Read-Only


The engine marks data arrays as non-writable. Trying to modify them in-place raises a ValueError. Always
use .copy() before modifying. This is one of the 6 rules Brain checks.

# WRONG — raises ValueError: assignment destination is read-only


a = [Link][-1]
a[a < 0] = 0 # trying to modify in-place

# CORRECT — copy first, then modify


a = [Link][-1].copy()
a[a < 0] = 0 # safe to modify the copy

5.4 The Sliding Window


Brain calls your alpha function once per trading day. Each day, the window slides forward: one new row of data
enters at the bottom, the oldest row drops off the top. With lookback=5, you always have 6 rows (5 history +
today).

Setting Window Size Meaning

lookback=0 1 row Today only. No history.

lookback=5 6 rows Today + 5 previous days.


Good for short-term.

lookback=21 22 rows Today + 21 previous days


(~1 month).

lookback=63 64 rows Today + 63 previous days


(~1 quarter).

💡 Early Time Steps Warning


During the first few trading days, the window is still growing and has fewer rows than lookback+1. Your code
must handle this gracefully. The store variable (store.x is None) is the standard way to detect the first call.
SECTION 6 — The Three Essential Helper Functions
Every competitive alpha uses three helper functions: pasteurize, neutralize, and scale. These are not optional
extras — they are required for your alpha to be valid and competitive. The docs show them in every example.

6.1 pasteurize — Remove Out-of-Universe Stocks


Pasteurization sets the signal to NaN for any stock that is not in the active universe today. If you skip this, your
alpha bets on stocks that cannot actually be traded.

def pasteurize(a, u):


"""
a = your alpha signal array, shape [n_instruments]
u = [Link][-1], shape [n_instruments], values: 1 or 0
"""
a = [Link]() # MUST copy — never modify in-place
a[~[Link](bool)] = [Link] # set out-of-universe to NaN
return a

# How it works:
# [Link](bool) → [True, False, True, True, False, ...]
# ~ → flips: [False, True, False, False, True, ...]
# a[~[Link](bool)] = [Link] → NaN for all out-of-universe stocks

6.2 neutralize — Remove Market Bias


Neutralization removes the overall market direction from your signal. Without it, your alpha might just be
betting "the whole market goes up" every day, which is not an edge.

def neutralize(a):
"""Remove market mean from the signal."""
a0 = np.nan_to_num(a, nan=0, posinf=0, neginf=0)
return a - [Link](a0)

# Why nan_to_num first?


# [Link] on an array with NaN returns NaN
# nan_to_num replaces NaN with 0 so the mean calculation works
# The mean is subtracted from every stock's signal
# Result: signal is now centred around zero (market-neutral)

6.3 scale — Normalise Position Sizes


Scaling ensures your signal sums to a unit book size. Without it, position sizes can be arbitrary and the
simulation cannot compare alphas fairly.

def scale(a):
"""L1-normalise the signal to unit book size."""
a0 = np.nan_to_num(a, nan=0, posinf=0, neginf=0)
norm = [Link](a0, ord=1) # L1 norm = sum of absolute values
return a / norm if norm > 0 else a # guard against division by zero

# ord=1 means: norm = |a[0]| + |a[1]| + |a[2]| + ...


# Dividing by this makes the absolute values sum to 1.0
# "Unit book" means: total long positions = total short positions = 0.5

6.4 Putting It All Together — The Standard Alpha Pattern


Every alpha you write will follow this exact pattern from the docs:

from [Link] import alpha


import numpy as np
import [Link] as npt

def pasteurize(a, u):


a = [Link]()
a[~[Link](bool)] = [Link]
return a

def neutralize(a):
a0 = np.nan_to_num(a, nan=0, posinf=0, neginf=0)
return a - [Link](a0)

def scale(a):
a0 = np.nan_to_num(a, nan=0, posinf=0, neginf=0)
norm = [Link](a0, ord=1)
return a / norm if norm > 0 else a

@alpha(
data=["returns"],
store=[],
)
def mean_reversion(data, store) -> [Link][np.float32]:
# Step 1: compute raw signal
a = -[Link]([Link], axis=0).astype(np.float32)

# Step 2: remove out-of-universe stocks


a = pasteurize(a, [Link][-1])

# Step 3: remove market bias, normalise positions


a = scale(neutralize(a))

# Step 4: ALWAYS return float32


return [Link](np.float32)
SECTION 7 — Store — Memory Across Time Steps
The store parameter lets your alpha remember things between time steps. By default, your alpha function has
no memory — each day it only sees the current window of data. The store fixes that.

7.1 Why You Need Store


Suppose you want to track a running average that goes further back than your lookback window allows. Or you
want to compare today's signal to what it was 30 days ago. You need store for that.

7.2 Simple Usage — Untyped String Entry


@alpha(
data=["returns"],
store=["my_state"], # declare a store variable called my_state
)
def my_alpha(data, store) -> [Link][np.float32]:
# On the FIRST call, store.my_state is None
if store.my_state is None:
store.my_state = [Link]([Link][1], dtype=np.float32)

# Update the stored value


today = [Link][-1].copy().astype(np.float32)
store.my_state = 0.9 * store.my_state + 0.1 * today # EMA

return store.my_state.astype(np.float32)

7.3 Typed Store Entry — Recommended for Instrument Arrays


If your store variable is instrument-sized (one value per stock), use a typed dict. The simulator then
automatically extends it when the universe grows.

@alpha(
data=["returns"],
store=[{"name": "ema", "dims": "i", "extend": np.float32(0)}],
)
def ema_alpha(data, store) -> [Link][np.float32]:
if [Link] is None:
[Link] = [Link]([Link][1], dtype=np.float32)

today = np.nan_to_num([Link][-1].copy(), nan=0.0).astype(np.float32)


[Link] = (0.9 * [Link] + 0.1 * today).astype(np.float32)

a = [Link]()
a = pasteurize(a, [Link][-1])
a = scale(neutralize(a))
return [Link](np.float32)
Setting Meaning
"dims": "i" Instruments axis. Auto-extended when universe
grows. Use for per-stock arrays.
"dims": "xi" 2D array. x = free axis (e.g. time), i =
instruments. Use for rank caches.
"dims": "ii" 2D square matrix. Both axes are instruments. Use
for correlation matrices.
"extend": np.float32(0) Fill new instruments with 0. Must match array
dtype EXACTLY.
"extend": Fill new instruments with NaN. Use when missing
np.float64([Link]) history should be ignored.

🔴 extend dtype Must Match Exactly


The extend value must be a NumPy scalar matching the array dtype. np.float32(0) for float32 arrays.
np.float64(0) for float64 arrays. Never use bare Python 0, 0.0, or [Link] alone. The simulation raises a
ValueError if types do not match.
SECTION 8 — Running Simulations

8.1 SimulationSettings Reference


SimulationSettings controls how Brain runs your alpha. You must configure this correctly before running either
simulation path.

Parameter Typical Value Description

instrument_type 'EQUITY' Market type. Always EQUITY


for this competition.

region 'USA' USA D1 Region. Required for


this competition.

delay 1 Days between signal and


execution. 1 is standard.

universe 'TOP3000' The 3000 largest US stocks


by market cap.

lookback 5, 21, 63 Extra history rows. Window


= lookback + 1.

start_date '2020-01-01' BrainLabs sim start date.

end_date '2024-12-31' BrainLabs sim end date.

neutralization 'MARKET' Removes overall market


direction from positions.

pasteurization 'ON' Masks out-of-universe


instruments.

truncation 0.8 Caps max position size.


Prevents over-
concentration.

decay 10 Smooths the alpha signal


over N days.

language 'PYTHON' Required for actual


simulation. Not for
BrainLabs.

visualization True Shows PnL chart. BrainLabs


only.

max_position 'OFF' Position size limit. OFF


for standard use.

max_trade 'OFF' Trade size limit. OFF for


standard use.
8.2 Path 1: BrainLabs — Use This for Development
from brain import Brain, BrainCache
from [Link] import SimulationSettings

brain = Brain(raw=True)
cache = BrainCache(brain)

settings = SimulationSettings(
instrument_type="EQUITY",
region="USA",
delay=1,
universe="TOP3000",
lookback=5,
visualization=True, # shows PnL chart
)

# The 3-step pipeline


alpha_matrix = brain.generate_alpha_matrix(mean_reversion, settings, cache)
positions = brain.generate_alpha_positions(alpha_matrix, settings, cache)
stats = brain.generate_alpha_stats(positions, settings, cache)

8.3 Path 2: Actual Simulation — Use This for Submission


# CELL 1: Alpha definition — ONLY from [Link] import alpha here
from [Link] import alpha
import numpy as np
import [Link] as npt

# ... helper functions and @alpha decorated function here ...

# CELL 2: Submission — Brain imports go here


from brain import Brain
from [Link] import SimulationSettings

brain = Brain(raw=True)

submit_settings = SimulationSettings(
instrument_type="EQUITY", region="USA", delay=1,
universe="TOP3000", lookback=5, decay=10,
neutralization="MARKET", pasteurization="ON",
truncation=0.8, language="PYTHON",
visualization=False, max_position="OFF", max_trade="OFF",
)

alpha_id = [Link](mean_reversion, submit_settings)


alpha_result = brain.get_alpha(alpha_id)
print(alpha_result)

🔴 Critical Cell Separation Rule


When submitting to the Brain backend, your @alpha function and from [Link] import alpha MUST be in
a separate cell from Brain, BrainCache, and SimulationSettings imports. Mixing them causes the simulation to
fail. This is explicitly stated in the documentation.
SECTION 9 — Alpha Strategies for the Competition
Now you understand all the mechanics. Here are proven alpha strategies you can implement, test, and iterate
on. Each is explained from the idea through to the code.

9.1 Mean Reversion — The Baseline


Idea: stocks that recently went up tend to come back down, and vice versa. Bet against recent performance.

@alpha(data=["returns"], store=[])
def mean_reversion(data, store) -> [Link][np.float32]:
# Negative of average recent return
# High return → negative signal → sell
# Low return → positive signal → buy
a = -[Link]([Link], axis=0).astype(np.float32)
a = pasteurize(a, [Link][-1])
a = scale(neutralize(a))
return [Link](np.float32)

Variants to try: Change lookback (5, 10, 21). Use only the last N days: [Link][-N:]. Use nanstd to normalise
by volatility.

9.2 EMA Momentum — Trend Following


Idea: stocks with a consistently rising smoothed return signal have momentum. Follow the trend.

@alpha(
data=["returns"],
store=[{"name": "ema", "dims": "i", "extend": np.float32(0)}],
)
def ema_momentum(data, store) -> [Link][np.float32]:
n = [Link][1]
if [Link] is None:
[Link] = [Link](n, dtype=np.float32)

today = np.nan_to_num([Link][-1].copy(), nan=0.0).astype(np.float32)


[Link] = (0.9 * [Link] + 0.1 * today).astype(np.float32)

# Positive EMA → trending up → buy


a = [Link]()
a = pasteurize(a, [Link][-1])
a = scale(neutralize(a))
return [Link](np.float32)

Variants to try: Adjust the EMA smoothing factor (0.9/0.1). Try 0.8/0.2 for faster response. Try 0.95/0.05 for
slower, more stable signal.
9.3 Cross-Sectional Rank — More Robust Signal
Idea: instead of using raw return values, rank stocks by return each day. The rank-based signal is more robust to
outliers and extreme values.

@alpha(data=["returns"], store=[])
def rank_reversion(data, store) -> [Link][np.float32]:
today = [Link][-1].copy()

# Replace NaN with -inf so they rank last


finite = [Link]([Link](today), -[Link], today)

# Double argsort gives the rank of each stock


ranks = [Link]([Link](finite)).astype(np.float32)

# Put NaN back for missing data


ranks[[Link](today)] = [Link]

# Negative rank → mean reversion (top ranked → sell)


a = -ranks
a = pasteurize(a, [Link][-1])
a = scale(neutralize(a))
return [Link](np.float32)

9.4 Mean of Rank — From the Official Docs


This is the most advanced example directly from the Brain documentation. It caches cross-sectional ranks over
more days than the lookback window allows, giving a smoother signal.

RANK_DAYS = 20

@alpha(
data=["returns"],
store=[{"name": "rank_cache", "dims": "xi",
"extend": np.float64([Link])}],
)
def mean_of_rank(data, store) -> [Link][np.float32]:
today = [Link][-1]

finite = [Link]([Link](today), -[Link], today)


today_rank = [Link]([Link](finite)).astype(np.float64)
today_rank[[Link](today)] = [Link]

if store.rank_cache is None:
store.rank_cache = today_rank[[Link], :]
else:
new_cache = [Link]([store.rank_cache,
today_rank[[Link], :]])
store.rank_cache = new_cache[-RANK_DAYS:]

mean_rank = [Link](store.rank_cache, axis=0)


return (-mean_rank).astype(np.float32)
SECTION 10 — Rules Summary & Competition Checklist

10.1 The 6 Iron Rules from the Docs


Brain enforces these rules on every submission. The Code Validator in your IDE checks all of them automatically.

Rule Requirement Why

Rule 1 Exactly one @alpha Having two decorators


decorator per function breaks the simulation.

Rule 2 Function must accept def my_alpha(data, store) —


exactly 2 parameters always these two.

Rule 3 Return type must be Always end


float32, shape [n_insts] with .astype(np.float32).

Rule 4 Do NOT include "universe" It is always available


in data list automatically.

Rule 5 Do NOT mutate data arrays Always .copy() before


in-place modifying.

Rule 6 [Link] dtype must np.float32(0) not 0.


match array dtype np.float64(0) not 0.0.

10.2 Before Every Submission — Checklist


5. Run in BrainLabs first. Fix any errors. Check Sharpe > 1.5.
6. Confirm @alpha decorator is present.
7. Confirm return is .astype(np.float32).
8. Confirm "universe" is NOT in data=[...].
9. Confirm all data modifications use .copy() first.
10. Confirm pasteurize, neutralize, scale are all applied.
11. Confirm alpha cell has ONLY from [Link] import alpha (no Brain, BrainCache, SimulationSettings).
12. Run Actual Simulation. Wait for result. Check Sharpe.
13. If Sharpe is good, it is submitted — done.

10.3 Competition Timeline


Date Activity Goal

June 1 (TODAY) Competition opens. Start Mean reversion baseline


writing alphas immediately. first.
Week 1-2 Write 5+ alpha variants. Focus on Sharpe > 1.5.
Test all in BrainLabs.

Week 3-4 Iterate best alphas. Try Target Sharpe > 2.0.
rank-based and EMA
variants.

Week 5-6 Final polish. Submit best Actual Simulation for each.
3-5 alphas.

July 12 Deadline. No more All submissions must be in.


submissions after this
date.

🔥 FINAL TIP: Quantity + Quality


Submit multiple alphas. The $30K pool rewards winners across a range of performances. A portfolio of 5 solid
alphas (Sharpe 1.5-2.0) is better than one perfect alpha. Use BrainLabs to test fast, then only use Actual
Simulation on your best candidates.

You might also like