Note that if we want to rebalance only based on absolute / relative drifts, we should specify 'none' as the rebalancing period.

The default rebalancing config is yearly.

Leverage

The portfolio weights don't have to sum to 100 (%), we can have more than 100% for leverage of less than 100% for a partial cash position

SPY:100|BND:50 // this portfolio has a 1.5 leverage
SPY:25|BND:25 // this portfolio has a 0.5 leverage (or a 50% cash position)

We can also explicitly state the leverage:

// both of these portfolio are the same - both have 25% SPY, 25% BND and 50% cash
SPY:50|BND:50 { leverage 0.5 }
SPY:25|BND:25

Start / End Dates

We can specify specific start / end dates:

SPY
start '2009/3/14'

SPY
start '2009/3/14'
end '2013/1/1'

The date is specified as 'year/month/day'

Deposit / Withdraw

We can make one time or peridoc deposits and withdrawal of cash:

SPY
deposit { amount 1000; every 1month }

We can also specify when to start the deposit and for how long:

SPY
deposit { amount 1000; every 1month; after 5y; for 10y }

Or create a one-time deposit:

SPY
deposit { amount 10000; after 1y }

Note that the 'every', 'after' and 'for' configs expect a Period value.

Periods

Periods are repeating intervals of time such as a day or a month. Examples of periods are: 1month, 2days, 5y, 10w, 2quarters. Each period starts with a
number and is followed by the period name or its initial letter.

Note that there can only be 1 schedule for deposits and withdrawal each.
Support for inflation adjustment and percentage withdrawal is in progress.

Friction

In the real world, investing incurs friction costs, such as taxes, trading fees, management fees, bid-ask spreads, slippage, margin costs and more.
In retrolyzer these costs are accounted for by default. To turn them all off add 'friction off' to the query, or you could disable/enable any one of them
using taxes/fees/slippage/wholeUnits flags with on/off values.

SPY
friction off // turn off all friction costs

SPY
friction off // turn off all friction costs
fees on // but keep fees costs

Configuration flags apply to the entire query, but we can also apply them on a per-strategy basis

SPY // default, has all friction costs
SPY { friction off } // has no friction costs
SPY { fees off } // has no fees costs, but left with taxes and slippage costs
SPY { fees off; slippage off } // has only taxes costs
SPY { friction off; taxes on } // also has only taxes costs
SPY { fees off; slippage off; taxes off } // same as 'friction off'

tradeFees: When on, accounts for fees related to executing a trade.taxes: When on, accounts for dividends and capital gains taxes and their (or lack of) withholding.slippage: When on, accounts for the bid-ask spread using a non-linear model based on the volume of a stock at the time of transaction.wholeUnits: When on, any purchase or sell of stock must be in whole units, meaning imperfect usage of cash and weights allocation.

We can also configure some of them at a more granular level:

tradeFees { min 1; stock 0.005; percent 0 } // default

min - minimum cost per trade; stock - cost per stock unit traded; percent - a percent of the total trade value deducted as a fee.

taxes { divs 25; gains 25; withhold divs } // default

divs - dividend tax percent; gains - capital gains tax percent; withhold - which taxes are withheld, can be divs/gains/both. When withhold is active for a specific tax type,
the gains/dividends will be after-tax at the moment of the tax event. When it's inactive, the liable tax will be payed at the end of the year.

Liquidation

When a portfolio has capital gains, usually the investor caries a tax libility for those gains, meaning that the net post tax libility value of
the portfolio is lower than its market value. By default, all metrics are based on the market value of the portfolio, i.g. pre-liquidation, but by setting
'liquidation on' we can have all metrics, including the equity curve, CAGR, StdDev etc, be based on the post-liquidation value.

SPY
SPY { liquidation on }

This is useful when comparing strategies which might have different tax liabilities, and comparing their market value will paint a skewed image of their post-tax value.

Math Operations

Any time-series can be transformed by math operations, which include: * / + -

SPY; SPY * 2
BND; BND + GLD
VNQ; VNQ - VNQI
IEF; IEF / IEI

Math operation can involve both time-series and constant numbers.Note that we can write multiple statement on the same line by seperating them with ';'.

Comparing

We can divide one portfolio over another to compare their equity charts on a relative basis

VTI / SPY
SPY:25|TLT:25|GLD:25|SHY:25 / SPY:60|BND:40

Note that this is in fact just a simple devision operator, which can be applied on any combination of time-series. What makes this unique is that unlike
a comparison of the VTI ticker history vs that of SPY, here we compare the full equite chart of buy and hold portfolios of these ETFs which includes
accounting for taxes, fees and all friction costs which affect the effective outcome.

Functions

Function allow to transform any time-series to another. A basic example is a Moving-Average

SPY
ma(SPY, 50)
ma(SPY, 200)

Function can be applied on any time series

SPY
ma(ma(SPY, 50), 20)
ma(VTI / SPY, 100)

Note that function on time-series always return a time-series.
The current set of builin functions include:

Builtin Functions

See builtins.bql for the full list of builtin functions with their code and documentation.

Variables

We can store values in variables and use them to simplify queries and avoid repetition.

Numeral and Vectoral Time-Series

Almost everything in BQL is a time-series. Most time-searies are numeral, that is for every date there is a single numeral value. BQL also supports vectoral-time-series, where
for every date there is a vector (array) of numeral values. Vectoral-time-series are usefull for certain kinds of functions and strategies where a lookback window is desired.
See for example usage of the cor() function defined below.

Custom Functions

We can define our own functions, both to simplify queries with repeating logic and to add new functionality.

func div(a, b) {
a / b
}
div(VB, SPY)

Simple single-statement function can also be written in short hand as:

func mul(a, b) => a * b
mul(SPY, SH) // an example of the hidden costs of short ETFs

We can even define ma() as a custom BQL function. While the actual builtin ma() is written in native code for maximum efficiency, this BQL implementation
is also a builtin function named _ma(), and can be used the same way as ma(). Note that BQL allows overriding builtin functions with custom ones.

Another example is a drawDown() function, which like cor() is a builtin function written in BQL.

func drawDown(x) {
((x - max(x)) / max(x)) * 100
}
drawDown(SPY)

Strategies

The basic Buy and Hold strategy is nice, but to achive more we want to use Strategies and Signals.

when SPY > ma(SPY, 50) hold SPY

The above strategy will hold SPY only when its value is above its 50 day moving average, otherwise it will hold cash.
Or we can be more explicit and choose what to hold otherwise

when SPY > ma(SPY, 50) hold SPY else BND:50|GLD:50

We can also view the signal itself, which translates into a binary 0/1 signal.

SPY; ma(SPY, 200); SPY > ma(SPY, 200)

For Loops

We can iterate over a range of values to cover more ground much faster.

for i in 1..10
when SPY > ma(SPY, i * 10) hold SPY

This is equivalent to writing 10 strategies with different lengths for the ma() function.
Note that a for-loop can only iterate a single statement at the moment.

We can even skip the entire for-in part and iterate implicitly:

when SPY > ma(SPY, 1..10 * 10) hold SPY

This iterates the entire statement as if it was in a for-loop, replacing the specified range with the value of the iteration.

Auto Optimization

Suppose you want to find the optimal value of a parameter for a strategy like this one:

when SPY > ma(SPY, ?) hold SPY

Well you can! Running the above query will iterate the strategy with different values for the free variable '?' using a simulated annealing algorithm to find
an approximate optimum quickly.

We can also limit the search to a specific range, or suggest the search to start from a specific value:

when SPY > ma(SPY, 1..100?) hold SPY

when SPY > ma(SPY, 200?) hold SPY

Notes:
- The number of iteration for each optimization is limited to 50.
- Due to the random nature of the simulated annealing algorithm, results are likely to vary between repeating executions.
- Only a single free variable can be optimized at the moment.
- By default, the algorithm optimizes for returns (cagr). We can choose between cagr/stddev/maxdd/sharp (all lower case) using the optimize config:

Trim and Align

By default, strategies with different starting dates will all start from a common starting date which allows them all to be compared on even grounds.
The downside of this is that we don't see what went on before that date, even though the data exists.

SPY; BND; GLD

We can disable this behaviour by setting 'trim off'

SPY; BND; GLD
trim off

While this allows to see the values of each strategy before the common starting date, it also causes the strategies to be on uneven ground as each
one began on a different date, and their performance metrics will also depend highly on those dates.

SPY; BND; GLD
trim off; align off

Even when trim is off, by default the strategies will be aligned to start at the same point as the common starting date. This will cause their dollar value
to be skewed; We can disable aligment using 'align off'.