Tutorials

Tutorial 1: How to use the platform?

The platform at AimsQuant is divided into two main sections, strategy editor (or the "editor") section and community section. In this tutorial, we will discuss the editor section of the platform. The editor section of the platform is divided into three major components

  1. Code Editor
  2. Settings Panel
  3. Toolbar

Code Editor

In code editor, user can define backtest settings and write strategy logic. Every strategy is subdivided into two function:

  1. initialize(): Used to define settings like universe, dates, commission model etc.
  2. ondata(): Used to write strategy logic. This function is called every time stamp and re-evaluates the strategy logic

Important:

User must create the above two functions

#Initialize function to set basic strategy parameters
function initialize(state)
    setcash(100000.0)
    setstartdate("2016-01-01")
    setenddate("2017-10-01")
    .
    setcancelpolicy("EOD")
    .
    .
    setcommission(("PerTrade", 0.02))
end

#Strategy logic
function ondata(data, state)
    price_history = dropnan(history(["TCS","WIPRO"], "Close", :Day, 22))
    log_returns = percentchange(price_history, :log)
    
    ts_end = timestamp(log_returns)[end]
    names = colnames(log_returns)
    vals_end = reshape(cumsum(values(log_returns), dims=1)[end, :], (1,length(names)))
    
    #Convert to timeseries
    total_return = TimeSeries.TimeArray([ts_end], vals_end, names)
    
    if values(total_return["TCS"])[1] > values(total_return["WIPRO"])[1]
        setholdingpct("WIPRO", 0.0)
        setholdingpct("TCS", 1.0)
    else
        setholdingpct("TCS", 0.0)
        setholdingpct("WIPRO", 1.0)
    end
end
                                                                
                                                            

User can also create sub-functions to break-down strategy logic into easy to comprehend sections. For ex: the function that calculates returns based on price data can be taken out of the ondata() function.

#Strategy logic
#Strategy logic
function ondata(data, state)
    price_history = dropnan(history(["TCS","WIPRO"], "Close", :Day, 22))
    totalreturn = calculate_totalreturn(price_history)
    
    if values(total_return["TCS"])[1] > values(total_return["WIPRO"])[1]
        setholdingpct("WIPRO", 0.0)
        setholdingpct("TCS", 1.0)
    else
        setholdingpct("TCS", 0.0)
        setholdingpct("WIPRO", 1.0)
    end
end

#Calculate total returns
function calculate_totalreturn(price_history)
    log_returns = percentchange(price_history, :log)
    
    ts_end = timestamp(log_returns)[end]
    names = colnames(log_returns)
    vals_end = reshape(cumsum(values(log_returns), dims=1)[end, :], (1,length(names)))
    
    #Convert to timeseries
    total_return = TimeSeries.TimeArray([ts_end], vals_end, names)
    
end
                                                                
                                                            

Settings Panel

To simplify the initialize() function and to avoid writing basic settings every time, user can use settings panel to set the backtest settings. Settings are divided into Basic and Advanced settings. Initial Cash, backtesting dates, universe selection can be done
All the backtest settings are listed in an easy to understand UI

User can create any combination of settings without writing any code in initialize()

Important:

initialize() must always be defined. However, it can be left empty

function initialize(state)
end
                                                                
                                                            

Important:

settings defined in initialize() WILL override the settings selected in settings panel

Toolbar

In addition to starting a backtest, Toolbar is used to create new strategies, cloning existing strategy, saving a strategy and inspecting previously run backtests for a strategy.

Important:

Strategy is saved every 5 seconds to avoid loss of work

Tutorial 2: Understanding the sample strategy

In this tutorial, we will create a sample strategy to test the platform, understand the flow of data and available functions in API. In this strategy, 100% of wealth is equally divided and invested in two stocks, TCS and WIPRO.

As mentioned in Tutorial #1, we need to define two functions for successful execution of a strategy/backtest.

In the section below, we show the components of strategy/backtest initialization

Set Cash to 10,00,000
#Set initial cash
setcash(1000000.0)
                                                                
                                                            
Set Commission to zero (Brokerage or commission charged by the broker)
#Set commission (no commission) 
setcommission(Commission(PerTrade, 0.0))
                                                                
                                                            
Set Slippage to zero (Slippage is difference in the execution price and the close price)
#Set slippage (no slippage)
setslippage(Slippage(Variable, 0.0))
                                                                
                                                            
Set universe to include two stocks (TCS and WIPRO)
#Set universe (mandatory before placing any orders)
setuniverse(["TCS", "WIPRO"])
                                                                
                                                            

in this section, we define the components of strategy logic

Fetch universe
#Get Universe
universe = getuniverse()
                                                                
                                                            
Set the holding in each stock (uniform portfolio)
#Set the holding in all stock in universe to 1/total number of stocks [uniform portfolio]
for stock in universe
    #Function is executed every Day/Week/Month based on rebalance frequency
    setholdingpct(stock, 1.0/length(universe))
end
                                                                
                                                            
Track the Portfolio value (Portfolio Value is plotted on the graph)
#Track the portfolio value
    track("Portfoliovalue", state.account.netvalue)
                                                                    
end
                                                                
                                                            
Log the Portfolio Value (Portfolio Value is printed in the log)
#User Logger to output information to console
    Logger.info("Portofolio value = $(state.account.netvalue)")                                                                    
end
                                                                
                                                            

Here is the complete strategy

#Sample strategy to invest 100% of wealth equally in two stocks
#Every strategy requires two mandatory functions
#1. initialize(): Function to initialize the settings and user defined parameters
#2. ondata(): Function to define strategy logic 

#Initialize the strategy with various settings and/or parameters
function initialize(state)

    #Set initial cash
    setcash(1000000.0)

    #Set commission (no commission) 
    setcommission(Commission(PerTrade, 0.0))

    #Set slippage (no slippage)
    setslippage(Slippage(Variable, 0.0))

    #Set universe (mandatory before placing any orders)
    setuniverse(["TCS", "WIPRO"])
end

#Define strategy logic here
#This function is called every DAY
function ondata(data, state)

    #Get Universe
    universe = getuniverse()

    #Set the holding in all stock in universe to 1/total number of stocks [uniform portfolio]
    for stock in universe
        #Function is executed every Day/Week/Month based on rebalance frequency
        setholdingpct(stock, 1.0/length(universe))
    end

    #Track the portfolio value
    track("Portfoliovalue", state.account.netvalue)
    
    #User Logger to output information to console    
    Logger.info("Portofolio value = $(state.account.netvalue)")
end
                                                                
                                                            

Important:

This is a sample strategy (only to be used for experiment and research purpose). It does NOT constitute an investment advice.

Tutorial 3: Creating a simple stock reversal strategy based on absolute momentum
Stock Reversal strategy invests in recent poor performers. It is based on hypothesis that there is an over-reaction when a stock underperforms and hence the stock recovers after a short-term underperformance.

In this strategy, we create a proxy for underperformance by calculating the 22 day returns of stocks. Subsequently, returns are sorted and 100% of wealth is uniformly distributed in least performing 5 stocks.


This strategy is applied only on NIFTY constituents as of 25/01/2017. Hence, it has some shortcomings like sample bias and survivorship bias.

In initialize(), we set universe as NIFTY constituents
##################
# Basic stock reversal strategy 
# Rebalances portfolio every week 
# Invest in bottom (least performing) 5 stocks of NIFTY based on
# last 22 days return
##################
# Initialize the strategy with various settings and/or parameters
function initialize(state)
    # NIFTY 50 stock universe as of 25/01/2017
    # This universe has Survivorship bias
    universe = ["ACC","ADANIPORTS","AMBUJACEM",
    "ASIANPAINT","AUROPHARMA","AXISBANK","BAJAJ_AUTO",
    "BANKBARODA","BHEL","BPCL",    "BHARTIARTL","INFRATEL",
    "BOSCHLTD","CIPLA","COALINDIA","DRREDDY","EICHERMOT",
    "GAIL","GRASIM","HCLTECH","HDFCBANK","HEROMOTOCO","HINDALCO",
    "HINDUNILVR","HDFC","ITC","ICICIBANK","IDEA",
    "INDUSINDBK","INFY","KOTAKBANK","LT","LUPIN","M_M",
    "MARUTI","NTPC","ONGC","POWERGRID","RELIANCE","SBIN",
    "SUNPHARMA","TCS","TATAMTRDVR","TATAMOTORS","TATAPOWER",
    "TATASTEEL","TECHM","ULTRACEMCO","WIPRO","YESBANK","ZEEL"]

    # Set universe (mandatory before placing any orders)
    # Dynamic universe is not allowed yet
    setuniverse(universe)
end
                                                                
                                                            

In ondata()

Fetch 22 day returns for all stocks in universe
# Get Universe
universe = getuniverse()
# Fetch prices for last 22 days
prices = history(universe, "Close", :Day, 22)
                                                                
                                                            
Compute log returns (by computing cumulative sum of log difference or log returns)
# Logic to calculate returns over last month
log_returns = percentchange(price_history, :log)
total_return = cumsum(values(log_returns), dims=1)[end, :]

                                                       
                                                        
Create 2-D array with ticker and stock returns
# Create vector with two columns (Name and Returns) 
rets = [String.(colnames(prices_history)) vec(total_returns)]
                                                                
                                                            
Sort returns and select first 5 names
# Sorted returns
sortedrets = sortslices(rets, dims=1, by=x->(x[2]))
# Get 5 names with lowest returns
topnames = sortedrets[1:5, 1]
                                                                
                                                            
Create a uniform portfolio (and liquidate stocks not in first 5 names)
# Liquidate from portfolio if not in bottom 5 anymore
for (stock, positions) in state.account.portfolio.positions
    if (stock.ticker in topnames)
        continue
    else
        setholdingpct(stock, 0.0)
    end
end
# Create momemtum portfolio
for (i,stock) in enumerate(topnames)
    setholdingpct(stock, 1.0/length(topnames)) 
end
                                                                
                                                            
Here is the complete strategy below:
##################
# Basic stock reversal strategy 
# Rebalances portfolio every week 
# Invest in bottom (least performing) 5 stocks of NIFTY based on
# last 22 days return
##################
# Initialize the strategy with various settings and/or parameters
function initialize(state)

    # NIFTY 50 stock universe as of 25/01/2017
    # This universe has Survivorship bias
    universe = ["ACC","ADANIPORTS","AMBUJACEM",
    "ASIANPAINT","AUROPHARMA","AXISBANK","BAJAJ_AUTO",
    "BANKBARODA","BHEL","BPCL",    "BHARTIARTL","INFRATEL",
    "BOSCHLTD","CIPLA","COALINDIA","DRREDDY","EICHERMOT",
    "GAIL","GRASIM","HCLTECH","HDFCBANK","HEROMOTOCO","HINDALCO",
    "HINDUNILVR","HDFC","ITC","ICICIBANK","IDEA",
    "INDUSINDBK","INFY","KOTAKBANK","LT","LUPIN","M_M",
    "MARUTI","NTPC","ONGC","POWERGRID","RELIANCE","SBIN",
    "SUNPHARMA","TCS","TATAMTRDVR","TATAMOTORS","TATAPOWER",
    "TATASTEEL","TECHM","ULTRACEMCO","WIPRO","YESBANK","ZEEL"]

    # Set universe (mandatory before placing any orders)
    # Dynamic universe is not allowed yet
    setuniverse(universe)
end

# Define strategy logic: Calculate 22 days return, sort 
# and invest 20% of wealth in bottom 5 stocks

# All order based functions are called 
# every DAY/WEEK/MONTH (depends on rebalance frequency)
# Default rebalance Frequency: Daily
function ondata(data, state)
    # Get Universe
    universe = getuniverse()

    # Fetch prices for last 22 days
    prices = history(universe, "Close", :Day, 22)

    # Logic to calculate returns over last month
    log_returns = percentchange(price_history, :log)
    total_return = cumsum(values(log_returns), dims=1)[end, :]

    # Create vector with two columns (Name and Returns) 
    rets = [String.(colnames(prices)) vec(total_return)]

    # Sorted returns
    sortedrets = sortslices(rets, dims=1, by=x->(x[2]))

    # Get 5 names with lowest retursn
    topnames = sortedrets[1:5, 1]

    # Liquidate from portfolio if not in bottom 5 anymore
    for (stock, positions) in state.account.portfolio.positions
        if (stock.ticker in topnames)
            continue
        else
            setholdingpct(stock, 0.0)
        end
    end

    # Create momemtum portfolio
    for (i,stock) in enumerate(topnames)
        setholdingpct(stock, 1.0/length(topnames)) 
    end

    # Track the portfolio value
    track("Portfolio Value", state.account.netvalue)

    # Output information to console
    Logger.info("Portofolio value = $(state.account.netvalue)")
end
                                                                
                                                            

Important:

This is a sample strategy (only to be used for experiment and research purpose). It does NOT constitute an investment advice.

Tutorial 4: How to use the Utility API to compute metrics?

Utility API exposes some of the commonly used metrics like price_returns, standard deviation and beta. The API is a wrapper around verbose calculations.


In this tutorial, we will recreate the reversal strategy in Tutorial #3.


In the reversal strategy, we created a proxy of poor performance by calculating total returns (log returns) over 22 days.

Without Utility API
universe = getuniverse()
#Fetch prices for last 22 days
prices = history(universe, "Close", :Day, 22)

#Logic to calculate returns over last month
log_returns = percentchange(price_history, :log)
ts_end = timestamp(log_returns)[end]
names = colnames(log_returns)
vals_end = reshape(cumsum(values(log_returns), dims=1)[end, :], (1,length(names)))

#Convert to timeseries
total_return = TimeSeries.TimeArray([ts_end], vals_end, names)
                                                                
                                                            
With Utility API
universe = getuniverse()
#Fetch total returns over last 22 days
total_return = UtilityAPI.price_returns(universe, "Close", :Day, window=22, total=true)
                                                                
                                                            
Other useful applications of Utility API
#Also, there are other options to change the return type (log or simple returns) and duration(daily or total) 
#To get, daily simple returns
daily_return = UtilityAPI.price_returns(universe, "Close", :Day, window=22, total=false, rettype=:simple)
                                                                
                                                            
In addition to price_returns, it can be used to calculate standard deviation and beta as well
#Beta of all stocks in universe over a period of 252 days using 
#"log" returns of "Close" price with "benchamrk" as NIFTY 50
bta = UtilityAPI.beta(universe, :Day, window=252, series="Close", benchmark="NIFTY_50", rettype=:log)

#Standard Deviation of all stocks in universe over a period of 22 days using
#"log" returns
std = UtilityAPI.stddev(universe, "Close", :Day, window=22, returns=true, rettype=:log)

#For standard deviation of Open PRICES (not returns), set "returns" as FALSE
std = UtilityAPI.stddev(universe, "Open", :Day, window=22, returns=false)
                                                                
                                                            
Tutorial 5: How to use the Optimization API for common financial optimization?

Optimization API exposes some of the commonly used optimization methods used in investment management. The API is a wrapper around verbose functions. It uses Julia JuMP library and Ipopt as an optimization engine.


In this tutorial, we will detail the available optimization types and various configuration parameters.

  1. Minimum absolute deviation
  2. Minimum semi-absolute deviation
  3. Mean-Variance
  4. Minimum volatility
  5. Minimum Loss
  6. Minimum Norm
Optimization API exposes various parameters to configure the optimization type, common financial constraints and user defined restrictions.
#Common API function
#Parameters are employed as required by the optimization type
#Default optimization type is minvol 
function optimize(symbols, 
    method::String="minvol", 
    window::Int=22;
    targeret::Float64=0.2,
    nfactors::Int=10,
    constraints::Constraints=Constraints(),
    initialportfolio::Vector=Vector(),
    linearrestrictions::Vector=LinearRestriction[])
end
                                                                
                                                            

To employ common financial constraints like exposure, leverage, trade-limit, turnover etc., API exposes the type Constraints. Also, to add linear restrictions, API exposes the type LinearRestrictions. Please visit the API to learn more about the functionality.


Here we will discuss the a few use cases of the optimization API.

Minimum Volatility with turnover and position limit

In minimum volatility, we solve for a portfolio with least standard deviation or variance of portfolio returns. There is no restriction on portfolio return but it can be added via LinearRestriction API. Below we will show a use case which employs both LinearRestriction and Constraints API

In the following minimum volatility optimization, we impose a restriction on position size such that each position is less than 10% of the total portfolio. In addition, we impose a single period turnover limit of 15%. This minimum volatility optimization employs Factor Analysis to decompose stock returns covariance into low dimensional factor covariance. It is mainly a mathematical technique to simplify the problem. We will discuss

Factor Analysis in a some other tutorial.
#Strategy Logic
function ondata(data, state)
    universe= getuniverse()

    #Compute initial portfolio 
    init_port=ones(length(universe))/length(universe)

    #Get Net Asset Value 
    nav = getportfoliovalue() 
    for (i,position) in getallpositions()
        init_port[i] = position.lastprice`*`position.quantity/nav
    end

    #Solve the optimization problem (method = "minvol")
    #Add constraint for turnover (<= 15%)
    #Add constraint on position limit (Max position size <= 10% of Portfolio)
    #nfactors is the number of factors to be used from Factor analysis (Num. Factors = 15)
    (obj, opt_port, status) = OptimizeAPI.optimize(universe, 
                                                    window=66, 
                                                    method="minvol",
                                                    nfactors=15,
                                                    constraints=Constraints(maxturnover=0.15, maxpositionlimit=0.1),
                                                    initialportfolio=init_port)

    if status == :Optimal
    #Set the target Portfolio as the optimized portfolio
    settargetportfolio(opt_port)
    end

end
                                                                
                                                            
Minimum Norm with beta restriction, leverage restrictions and target portfolio return

In minimum norm, we solve for least euclidean distance of target portfolio from initial portfolio subject to certain restrictions.

In the following minimum norm optimization, we impose a restriction portfolio beta such that portfolio beta is between 0.95 and 1.0. In addition, we impose a portfolio leverage condition such that portfolio leverage is between 0.95 and 1.05. This minimum volatility optimization employs Factor Analysis to decompose stock returns covariance into low dimensional factor covariance. It is mainly a mathematical technique to simplify the problem. We will detail Factor Analysis in a some other tutorial.

#Strategy Logic
function ondata(data, state)
    universe= getuniverse()

    #Compute initial portfolio 
    init_port=ones(length(universe))/length(universe)

    #Get Net Asset Value 
    nav = getportfoliovalue() 
    for (i,position) in getallpositions()
        init_port[i] = position.lastprice * position.quantity/nav
    end
    
    #Compute stock beta using UtilityAPI  
    bta = UtilityAPI.beta(universe, :Day, window=252)
    #Reshapae beta to 1-d array
    bta_arr = reshape(values(bta), (length(universe),))
    
    #66-day Historical returns (as proxy for forward returns)
    price_ret = UtilityAPI.price_returns(universe, "Close", :Day, window=66, total=true)
    ret_arr = reshape(values(price_ret), (length(universe),))
    
    #Solve the optimization problem (method = "minvol")
    #Add constraint on Portfolio Leverage (0.95 <= Portfolio Leverage <= 1.05) 
    #Add constraint on Portfolio Beta (0.95 <= Portfolio Beta <= 1.0)
    #Add constraint on Portfolio Returns (Portfolio Returns >= 0.05)

    l_restrictions = [LinearRestriction(bta_arr, 0.95, 1.0), LinearRestriction(ret_arr, 0.05, Inf)]

    (obj, opt_port, status) = OptimizeAPI.optimize(universe, 
                                                    method="minnorm",
                                                    constraints=Constraints(minleverage=0.95, maxleverage=1.05),
                                                    initialportfolio=init_port,
                                                    linearrestrictions=l_restrictions)

    if status == :Optimal
        setttargetportfolio(opt_port)
    end
    
end
                                                                
                                                            

Important:

Optimization API is in beta and still evolving. Please report any issues on the community.

Tutorial 6: Create a mean-variance portfolio using Optimization API

"What is a 'Mean-Variance Analysis'

A mean-variance analysis is the process of weighing risk (variance) against expected return. By looking at the expected return and variance of an asset, investors attempt to make more efficient investment choices – seeking the lowest variance for a given expected return or seeking the highest expected return for a given variance level." - Investopedia


In Mean-Variance Optimization means, we try to create portfolio that have the maximum mean (expected return) for a given variance of return (or standard deviation of returns) or the minimum variance of return for a given mean (expected return).

Mathematically, this problem can be represented as a

screenshot

where

  • w  is a vector of portfolio weights
  • Ω is the covariance matrix for the returns on the assets in the portfolio
  • R is a vector of expected returns.
  • w'Ωw is the variance of portfolio return.
  • R'w is the expected return on the portfolio.
  • μ is the target return of the portfolio
On the platform, we have created an optimization API (OptimizeAPI) that solves this problem and few variants of the same problem.
function ondata(data, state)
    universe = getuniverse()
    #Use optimization API to solve mean-variance problem (method = meanvar)
    #More parameters include constraints, initialportfolio and linearrestrictions [Tutorial #5]
    #Output: Tuple of objective value, optimal portfolio and status of optimization
    (obj, optimal_portfolio, status) = OptimizeAPI.optimize(universe, window=66, method="meanvar", targetret=0.2)

    if(status == :Optimal)
        #Rebalance the portfolio with settargetportfolio (Uses setholdingpct internally)
        settargetportfolio(optimal_portfolio)
    end
end
                                                                        
                                                                    

In other variant of Mean-Variance Optimization, we try to create portfolio by solving the following:

screenshot

where

  • w  is a vector of portfolio weights
  • Ω is the covariance matrix for the returns on the assets in the portfolio
  • R is a vector of expected returns.
  • w'Ωw is the variance of portfolio return.
  • R'w is the expected return on the portfolio.
  • λ is risk aversion parameter
function ondata(data, state)
    universe = getuniverse()
    #Use optimization API to solve mean-variance problem (method = "meanvar2")
    #Output: Tuple of objective value, optimal portfolio and status of optimization
    (obj, opt_port, status) = OptimizeAPI.optimize(universe, window=66, method="meanvar2", targetret=0.2, lamda=1.0)

    if(status == :Optimal)
        #Rebalance the portfolio with settargetportfolio (Uses setholdingpct internally) 
        settargetportfolio(opt_port)
    end
end
                                                                        
                                                                    

Important:

This is a sample strategy (only for experiment and research purpose) and DOES NOT constitute an investment advice