Back to Community
Fundamentals now available for real money trading

Good news! Fundamental data is now available for paper and real money trading through Interactive Brokers (IB).

We initially announced the availability of fundamental data in December and have continued to iterate on the feature. We added fundamental data to paper trading and for use within the Quantopian Open a few weeks ago. Now your algos can use fundamentals to trade real money through IB as well.

We have more work planned for fundamentals, so stay tuned as we add more improvements.

If you're interested in digging into some sample algorithms and getting started with fundamentals, here's a sampling of forum posts with algorithms using fundamentals.

Here is the API documentation and the reference document with details on over 600+ fundamental metrics.

Additionally, attached is a sample for a simple multi-factor algorithm using fundamental data. It's a nice template for getting started with value investing and fundamentals on Quantopian.

Clone Algorithm
290
Loading...
Backtest from to with initial capital
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
"""
Create a multifactor fundamentals weighting strategy - giving each fundamental
factor a ranking (highest number being the highest rank) and weighting each factor
equally
"""
import datetime
import pandas as pd


def initialize(context):
    # Dictionary of stocks and their respective weights
    context.stock_weights = {}
    
    # The algo will go long on the number of stocks set here
    context.num_stocks_to_buy = 10
    
    # Rebalance annually
    context.Rebalance_Days = 365
    context.rebalance_date = None
    context.rebalance_hour_start = 10
    context.rebalance_hour_end = 15
    
def before_trading_start(context): 
    """
    Called before the start of each trading day. 
    It updates our universe with the
    securities we want to trade on this particular day
    based on our ranking algorithm
    """
    
    # Number of stocks we'll gather data on and rank
    num_stocks = 1000
    
    # Setup query to fundamentals db to screen stocks. Query for the
    # fields specified (and use them for ranking)
    # Filter results based on the fields specified in the .filter() methods
    # ROIC and PE ratio are used in this sample as ranking metrics.
    # We limit the number of results to num_stocks and return the data
    # in descending order, i.e. highest to lowest in terms of the
    # ROIC metric.
    fundamental_df = get_fundamentals(
        query(
            # To add a metric. Start by typing "fundamentals."
            fundamentals.operation_ratios.roic,
            fundamentals.valuation_ratios.pe_ratio,
            fundamentals.operation_ratios.ebit_margin
        )
        .filter(fundamentals.valuation.market_cap > 1000e6)
        .filter(fundamentals.asset_classification.morningstar_sector_code != 103)
        .order_by(fundamentals.operation_ratios.roic.desc())
        .limit(num_stocks)
    )
    
    # Initialize for holding rankings
    rankings = {}
    # List of metrics where lower is better as opposed to higher is better.
    # Comma separated
    lower_the_better = ['pe_ratio']
    
    # Create a percentile ranking for each security.
    # Rank from 1 to N number of securities present in fundamental_df
    ranked_df = fundamental_df.fillna(0).rank(axis=1).apply(lambda x: x/len(fundamental_df.columns.values))

    # Weight each metric equally, find the sum of rankings, and 
    # give each stock the weighted sum of its ranks
    # Note, it's ranking descending for each metric (a limitation of this simple algo)
    weight = 1.0/len(fundamental_df.index) if len(fundamental_df.index) != 0 else 0
    for stock in ranked_df:
        sum_rank_for_stock = 0
        for r in fundamental_df.index:
            # For lower_the_better metrics, take the inverse
            if r in lower_the_better:
                sum_rank_for_stock += weight*(1 - ranked_df[stock].ix[r])
            else:
                sum_rank_for_stock += weight*ranked_df[stock].ix[r]
        rankings[stock] = sum_rank_for_stock
    
    # Order by rank and turn into a list and take only the top num_stocks_to_buy
    context.rankings = sorted(rankings, key = lambda x: rankings[x])
    context.rankings = context.rankings[-context.num_stocks_to_buy:]
    
    # Include the top ranked stocks in our tradeable universe
    update_universe(context.rankings)
    
def create_weights(context, stocks):
    """
    Takes in a list of securities and calculates
    the portfolio weighting percentage used for each stock in the portfolio 
    """
    if len(stocks) == 0:
        return 0 
    else:
        # Buy only 0.9 of portfolio value to avoid borrowing
        weight = .9/len(stocks)
        return weight
        
def handle_data(context, data):
    """
    Code logic to run during the trading day.
    handle_data() gets called every price bar. In this algorithm,
    rather than running through our trading logic every price bar, every day,
    we use scheduled_function() in initialize() to execute trades 1x per month
    """
    # Get the current exchange time, in the exchange timezone 
    exchange_time = pd.Timestamp(get_datetime()).tz_convert('US/Eastern')
    
    # Track leverage
    record_leverage(context, data)
    
    if context.rebalance_date == None or exchange_time > context.rebalance_date + datetime.timedelta(days=context.Rebalance_Days):
        
        # Do nothing if there are open orders:
        if has_orders(context):
            log.info('has open orders - doing nothing!')
            return
        rebalance(context, data, exchange_time)
    else:
        return
    
def rebalance(context, data, exchange_time):
    # Only during defined hours.
    if exchange_time.hour < context.rebalance_hour_start or exchange_time.hour > context.rebalance_hour_end:
       return

    # Track cash to avoid leverage
    cash = context.portfolio.cash
    
    # Exit all positions that have fallen out of the top rankings
    for stock in context.portfolio.positions:
        if stock not in context.rankings:
            if stock in data:
                order_target(stock, 0)
                cash += context.portfolio.positions[stock].amount
                log.info("Exiting security: %s" % stock)
            
    # Create weights for each stock
    weight = create_weights(context, context.rankings)

    # Rebalance all stocks to target weight of overall portfolio
    for stock in context.rankings:
        if weight != 0 and stock in data:
            notional = context.portfolio.portfolio_value * weight
            price = data[stock].price
            numshares = int(notional / price)
            
            if stock in data:
                order_target_percent(stock, weight)
                cash -= notional - context.portfolio.positions[stock].amount
                log.info("Placing order: %s" % stock)
    
    context.rebalance_date = exchange_time
    
def has_orders(context):
    # Return true if there are pending orders.
    has_orders = False
    for sec in context.rankings:
        orders = get_open_orders(sec)
        if orders:
            for oo in orders:                  
                message = 'Open order for {amount} shares in {stock}'  
                message = message.format(amount=oo.amount, stock=sec)  
                log.info(message)

            has_orders = True
    return has_orders

def record_leverage(context, data):
    P = context.portfolio    
    market_value = sum(data[i].price * abs(P.positions[i].amount) for i in data)    
    record(leverage=market_value / max(P.portfolio_value, 1))
There was a runtime error.
Disclaimer

The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances.

2 responses

awesome, was waiting for this to get seriously developing! Thanks

Yeah this is great. I can't wait for fundamentals inside research, there are so many visualizations that I want to try!